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);
}
|