File: io.c

package info (click to toggle)
nestra 0.66-1
  • links: PTS
  • area: contrib
  • in suites: potato
  • size: 300 kB
  • ctags: 462
  • sloc: ansic: 2,808; asm: 1,337; makefile: 65
file content (367 lines) | stat: -rw-r--r-- 13,419 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
/* Nestra io.c */
/* Public Domain */

/* This file consists of functions which are called by the generated code
   to do I/O operations. */

#include <stdio.h>
#include "mapper.h"
#include "io.h"
#include "globals.h"
#include "consts.h"

int vbl=0;
int hvscroll=0;
int vramlatch=0;
int hscrollval,vscrollval;
int sprite0hit;

/* Declaration of global variables */
unsigned char vram[16384];
unsigned int controller1=0,controller2=0;
unsigned char spriteram[256];
unsigned int hvmirror=0;
unsigned int nomirror=0;
unsigned int osmirror=0;
unsigned short int hscroll[240];
unsigned short int vscroll[240];
unsigned char linereg[240];
unsigned int CLOCK; /* Current scanline position */
unsigned char hscrollreg,vscrollreg;

static int last_clock; /* For vblank bit */

/* This is called whenever the game reads from 2xxx or 4xxx */
void input(int addr)
{
  int x;
  static signed char vram_read;
  unsigned char *ptr;
  INRET=0;
  
  /* Read PPU status register */
  if(addr==0x2002)
  {
    sprite0hit=spriteram[0]*341+341+85+spriteram[3]+40; /* +40 is to account for the latency of the PPU; from the time it reads the background tile to the time the flag is set is about 40 cycles, give or take a few. */
    if(RAM[0x2000]&0x20) ptr=VRAM+((spriteram[1]&0xFE)<<4)+((spriteram[1]&1)<<12); /* 8x16 sprites */
    else ptr=VRAM+(spriteram[1]<<4)+((RAM[0x2000]&0x08)<<9); /* 8x8 sprites */
    if((RAM[0x2000]&0x20)&&(((long long *)ptr)[0]|((long long *)ptr)[8])==0)
      {sprite0hit+=8*341;ptr+=16;}
    while((ptr[0]|ptr[8])==0&&sprite0hit<spriteram[0]*341+5797) {sprite0hit+=341;ptr++;}
    /*if((vbl&&CLOCK>=VBL&&CLOCK<29695)|(CLOCK<VBL&&CLOCK>27393)) {INRET|=(signed char)0x80;vbl--;} /* I'm sure this isn't really accurate.  The vblank flag is set before the NMI is triggered, but the timing here is just a guess; also a read should always clear the flag. */
    if(CLOCK>=27393) if(last_clock>=CLOCK||last_clock<=27393) vbl=1;
    last_clock=CLOCK;
    if(vbl&&CLOCK>27393) {INRET|=(signed char)0x80;vbl--;}
    /*if((CLOCK*3>=sprite0hit)&&(CLOCK<29695)) INRET|=(signed char)0x40;*/
    /*if((CLOCK*3>=sprite0hit)&&(CLOCK<VBL)) INRET|=(signed char)0x40;  /* When does sprite0hit go off? */
    if((CLOCK*3>=sprite0hit)&&(CLOCK<27742)) INRET|=(signed char)0x40;  /* Pick a number, any number... hmm... this one seems to work okay, I think I'll use it. */
    if(CLOCK>VBL&&!(RAM[0x2000]&0x80))
    {
      /* This is totally wierd, but SMB and Zelda depend on it. */
      RAM[0x2000]&=0xFE;
      for(x=0;x<240;x++) hscroll[x]=hscrollval&255;
      for(x=0;x<240;x++) linereg[x]=RAM[0x2000];
      /*printf("Read: %4x=%2x (scan %d)\n",addr,INRET&0xff,CLOCK);*/
    }
    hvscroll=0;
    /*printf("Read: %4x=%2x (scan %d)\n",addr,INRET&0xff,CLOCK);*/
  }
  
  /* Read from VRAM */
  if(addr==0x2007)
  {
    INRET=vram_read;
    VRAMPTR&=0x3fff;
    if(osmirror&&VRAMPTR>=0x2000&&VRAMPTR<=0x2FFF) vram_read=VRAM[VRAMPTR&0x23FF]; /* one screen */
    else if(VRAMPTR>0x3f00) vram_read=VRAM[VRAMPTR&0x3f1f];
    else if(!nomirror&&hvmirror&&VRAMPTR>=0x2400&&VRAMPTR<0x2c00) vram_read=VRAM[VRAMPTR-0x400];
    else vram_read=VRAM[VRAMPTR];
    VRAMPTR+=1<<(((*((unsigned char *)REG1)&4)>>2)*5); /* bit 2 of $2000 controls increment */
    if(VRAMPTR>0x3FFF) VRAMPTR&=0x3fff;
    /*printf("VRAM Read: %4x=%2x (scan %d)\n",VRAMPTR,INRET,CLOCK);/* */

    /* This is the so-called mid-hblank update.  If an address is written
       into 2006 followed by reads of 2007 during refresh, then the
       address is loaded into the PPU and used as the start address of the
       next scanline.  This is used to scroll the background vertically
       on a portion of the screen.  We need to convert the scanline
       address into horizontal/vertical offsets. */
    if(CLOCK<VBL&&(RAM[0x2001]&8)!=0)
    {
      for(x=(CLOCK*3/HCYCLES)+1;x<240;x++)
      {
        hscroll[x]=(hscroll[x]&0xff)|((VRAMPTR&0x400)>>2);
        vscroll[x]=(480+480+(((VRAMPTR&0x800)>>11)*240)+((VRAMPTR&0x3e0)>>2)-((CLOCK*3/HCYCLES)+1) )%480;
      }
    
      drawimage(CLOCK*3);
      vline=(VRAMPTR&0x3e0)>>5;
      vscan=0;

      /*printf("Read: %4x (scan %d, sprite0 %d) 2000=%2x 2001=%2x vbl=%d\n",addr,CLOCK,sprite0hit*HCYCLES,RAM[0x2000],RAM[0x2001],vbl);/* */
      /*printf("vram read during refresh! (%4x)\n",VRAMPTR); /* For debugging */
      /*printf("v=%d oldv=%d\n",vscroll[CLOCK/HCYCLES/8],vscroll[CLOCK/HCYCLES/8-1]);/**/
    }
  }

  /* Read joypad controller */
  if(addr==0x4016)
  {
    INRET=(RAM[0x4016]&1);
    RAM[0x4016]>>=1;
  }

  /* debug stuff:
  printf("Read: %4x=%2x (scan %d)\n",addr,INRET&0xff,CLOCK);/*
  printf("\nstack: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n",
    (&addr)[1],(&addr)[2],(&addr)[3],(&addr)[4],(&addr)[5],(&addr)[6],
    (&addr)[7],(&addr)[8],(&addr)[9],(&addr)[10],(&addr)[11],(&addr)[12] );
  /**/
}

/* This is called whenever the game writes to 2xxx or 4xxx */
void output(int addr,int val)
{
  int x;
  int scanline;
  static int spriteaddr;

  val&=0xFF;
  if((scanline=(CLOCK*3/HCYCLES)+1)>=240) scanline=0;

  /* Select pattern table */
  if(addr==0x2000)
  {
    drawimage(CLOCK*3);
    if(hvmirror||nomirror) vwrap^=((val^RAM[0x2000])>>1)&1; /* Ugly kludge - bit 1 of 2000 does not take effect until the next frame so this supresses it using the vertical wraparound bit.  This could be done a better way. */
    RAM[0x2000]=val;
    hscrollval=((RAM[0x2000]&1)<<8)|(hscrollval&255);
    vscrollval=(RAM[0x2000]&2)*120+(vscrollval%240);
    /*printf("vrom base:0x%4x\n",0x2000+((RAM[0x2000]&3)<<10));/**/
    for(x=scanline;x<240;x++) hscroll[x]=hscrollval;
    for(x=scanline;x<240;x++) linereg[x]=RAM[0x2000];
    /*printf("Write: %4x,%2x (scan %d)\n",addr,val,CLOCK);/**/
  }

  if(addr==0x2001)
  {
    drawimage(CLOCK*3);
    RAM[0x2001]=val;
  }

  /* Set horizontal/vertical scroll */
  if(addr==0x2005)
  {
    if(hvscroll^=1)
    {
      drawimage(CLOCK*3);
      hscrollreg=val;
      hscrollval=((RAM[0x2000]&1)<<8)+val;
      for(x=scanline;x<240;x++) hscroll[x]=hscrollval;
      /*printf("hscroll: %d\n",hscrollval);*/
    }
    else
    {
      vscrollreg=val;
     /* A little kludge here... It appears that the PPU will actually
        accept bogus vscroll values above 240, wrapping around to 0
        when it reaches 256, without the usual page flip at line 240.
        The val+224 hack below is to compensate for this, so that the
        rest of the lines end up where they're supposed to be. */
      vscrollval=((RAM[0x2000]&2)*120+((val<240)?val:val+224))%480;
      if(scanline<1)
        for(x=scanline;x<240;x++) vscroll[x]=vscrollval;
       /* Note: Unlike the h-scroll, the v-scroll register only gets read
          on the first scanline.  To create split-screen vertical scrolling,
          2006/2007 must be used to directly update the PPU registers. */

      /* More weirdness */
      vramlatch=(vramlatch&0x3c00)|(val<<2);

      /*printf("vscroll: %d\n",vscrollval);*/
    }
  }
  
  /* Load VRAM target address */
  if(addr==0x2006)
  {
    drawimage(CLOCK*3);
    /*VRAMPTR=((VRAMPTR&0x3f)<<8)|(val&0xff);*/

    /* It appears that h/v scroll and the VRAM address registers share
       a common toggle-bit which deterines which byte is written to. */
    if(hvscroll^=1)
    {
      vramlatch=(vramlatch&0xFF)|((val&0xff)<<8);
    }
    else
    {
      vramlatch=(vramlatch&0xFF00)|(val&0xff);
    }
    /* For mid-hblank updates: */
    /* Note: When the VRAM address register is loaded, the scanline number
       is derived from the top bits of the address.  When the address
       register load is followed by a read of 2005, then the scanline
       number is reset to zero and the scan starts with the top of the
       current char/tile line. (see above) */
    if(hvscroll) {
      /* Set page only on first write */
      RAM[0x2000]=(RAM[0x2000]&0xFC)|((vramlatch&0xC00)>>10); /* This is guesswork, but seems to function correctly. */
      vscan=vramlatch>>12;
      vwrap=0;
      /*vscrollreg=(vscrollreg&0x3F)|((VRAMPTR&3)<<6);*/
    }
    else
    {
      /* Set offset on second write */
      hscrollreg=(hscrollreg&7)|((vramlatch&31)<<3);
      vline=(vramlatch&0x3e0)>>5;
      vscan=vramlatch>>12;
      vwrap=0;
    }
    hscrollval=((RAM[0x2000]&1)<<8)+hscrollreg;
    for(x=scanline;x<240;x++) hscroll[x]=hscrollval;

    /*if(CLOCK<VBL)printf("hbl update: %4x %d\n",VRAMPTR,CLOCK);/**/
    /*if(CLOCK<VBL)printf("hbl update: %4x %d /%d /%d \n",VRAMPTR,CLOCK,vramlatch,hvscroll);/**/
    /*VRAMPTR&=0x3fff;*/
    VRAMPTR=vramlatch&0x3fff;
  }
  /* Argh... This 2006 shit is complicated.  It seems that writing to 2006
     alters the low two bits of 2000, and furthermore makes it so that the
     next write to 2005 will set the horizontal scroll.  This is in addition
     to the direct effect it has on the horizontal scroll.  The effect on
     2000 happens even during vblank, such that accessing the vram during
     vblank will cause the next frame to start at whatever vram page
     was selected, unless the program writes to register 2000 to change it.
     I probably don't have this completely correct. */


  /* Write VRAM */
  if(addr==0x2007)
  {
    /* A little bit about VRAM:
       
       The NES has two pages of internal VRAM.  The emulator always stores
       the data internally at 2000 and 2400, however the page at 2400 may
       be mapped to 2800 in the PPU address space, with the page at 2400
       mirroring 2000, or vice versa.
       
       0000-1FFF is mapped in from the game cartridge, and may be RAM or ROM.
       
       3Fxx holds the color pallette.
    */

    VRAMPTR&=0x3fff;
    /*mmc2_latch(VRAMPTR);*/
    /*if(CLOCK<VBL&&(RAM[0x2001]&8)) printf("vram write during refresh! "); /* For debugging */
    /*printf("VRAM: %4x=%2x (+%d) scan %d\n",VRAMPTR,val,1<<(((*((unsigned char *)REG1)&4)>>2)*5),CLOCK); /* */
    if((VRAMPTR&0x3f0f)==0x3f00)
      VRAM[0x3f00]=VRAM[0x3f10]=(unsigned char)val; /* Background color is mirrored between pallettes */
    else if((VRAMPTR&0x3f00)==0x3f00)
      {
      /*if(CLOCK>=VBL) /* FIXME - it seems it may actually be possible to change the color palette during refresh.  The display code is not set up to handle this. */
      VRAM[VRAMPTR&0x3f1f]=(unsigned char)val; /* Write to color pallette */
      update_colors(); /* FIXME - This is probably going to look like shit on 8-bit displays */
      }
    else if(VRAMPTR>=0x2000&&VRAMPTR<0x3000)
    {
      if(nomirror)
        VRAM[VRAMPTR]=(unsigned char)val;
      else if(osmirror)
        VRAM[VRAMPTR&0x23FF]=(unsigned char)val; /* One-Screen Mirroring */
      else if(!hvmirror)
        VRAM[VRAMPTR]=VRAM[VRAMPTR^0x800]=(unsigned char)val;
      else if(hvmirror)
      {
        if(VRAMPTR>=0x2000&&VRAMPTR<0x2400) VRAM[VRAMPTR]=VRAM[VRAMPTR^0x800]=(unsigned char)val;
        if(VRAMPTR>=0x2400&&VRAMPTR<0x2800) VRAM[VRAMPTR-0x400]=VRAM[VRAMPTR+0x400]=(unsigned char)val;
        if(VRAMPTR>=0x2800&&VRAMPTR<0x2c00) VRAM[VRAMPTR-0x400]=VRAM[VRAMPTR+0x400]=(unsigned char)val;
        if(VRAMPTR>=0x2c00&&VRAMPTR<0x3000) VRAM[VRAMPTR]=VRAM[VRAMPTR^0x800]=(unsigned char)val;
      }
    }
    else VRAM[VRAMPTR]=(unsigned char)val;
    
    VRAMPTR+=1<<(((*((unsigned char *)REG1)&4)>>2)*5); /* bit 2 of $2000 controls increment */
    if(VRAMPTR>0x3FFF){ /*printf("help - vramptr >0x3fff!\n");/*exit(1);*/VRAMPTR&=0x3fff;}
  }
  
  /* Sprite DMA */
  if(addr==0x4014)
    /* I'm not entirely sure of this, but it seems accurate. */
    {
      drawimage(CLOCK*3);
      if(val<0x80)
        memcpy(spriteram,RAM+(val<<8),256);
      else
        memcpy(spriteram,MAPTABLE[val>>4]+(val<<8),256);
      CLOCK+=514;
      CTNI+=514;
    }

  if(addr==0x2003) spriteaddr=val;
  if(addr==0x2004) spriteram[spriteaddr]=val;

  /* Reset controller */
  if((addr|1)==0x4017) {
    RAM[0x4016]=controller1;
    RAM[0x4017]=controller2;
  }

  /* more debugging stuff:
  printf("Write: %4x,%2x (scan %d)\n",addr,val,CLOCK);/*
  printf("stack: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n",
    (&addr)[1],(&addr)[2],(&addr)[3],(&addr)[4],(&addr)[5],(&addr)[6],
    (&addr)[7],(&addr)[8],(&addr)[9],(&addr)[10],(&addr)[11],(&addr)[12] );
  /**/

}

/* This determines whether an NMI should occur and returns 1 if so. */
/* This function is called even when interrupts are off, and also */
/* refreshes the screen as necessary. */
int donmi()
{
  int x;

  /*printf("donmi: at %d\n",CLOCK);*/

  CLOCK=VBL+7; /* 7 cycle interrupt latency */
  CTNI=-CPF+7;

  if(last_clock>=CLOCK||last_clock<=27393) vbl=1;
  last_clock=CLOCK;

  if(oldxcode) UpdateDisplay_orig();
  else UpdateDisplay();

  /*printf("donmi: stack at %x\n",STACKPTR);*/

  /* reset scroll registers */
  /*hvscroll=0;*/
  for(x=0;x<240;x++)
  {
    hscroll[x]=hscrollval;
    vscroll[x]=vscrollval;
    linereg[x]=RAM[0x2000];
  }

  /* Is an NMI to be generated?  Return 1 if so. */
  if(((*((unsigned char *)(REG1)))&0x80)!=0) return 1;
  else return 0;
}

/* this is left in here for debugging purposes but is not normally called: */
trace(int s)
{
/*
  printf("branch, stack: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n",
    (&s)[1],(&s)[2],(&s)[3],(&s)[4],(&s)[5],(&s)[6],
    (&s)[7],(&s)[8],(&s)[9],(&s)[10],(&s)[11],(&s)[12] );
*/
  unsigned int x;
  char hex[17]="0123456789ABCDEF";
  x=((int *)(&s))[4];
  printf("%c%c%c%c\n",hex[x>>12],hex[(x&0xf00)>>8],hex[(x&0xf0)>>4],hex[x&0xf]);
  fflush(stdout);
}