diff --git a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs index 32565b7cad..bb896e4648 100644 --- a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs +++ b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs @@ -197,7 +197,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 /*TYA [implied]*/ new Uop[] { Uop.Imp_TYA, Uop.End }, /*STA addr,Y [absolute indexed WRITE]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_STA, Uop.End }, /*TXS [implied]*/ new Uop[] { Uop.Imp_TXS, Uop.End }, - /*SHS* addr,X [absolute indexed WRITE X] [unofficial] [NOT IMPLEMENTED - TRICKY, AND NO TEST]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_ERROR, Uop.End }, + /*SHS* addr,Y [absolute indexed WRITE X] [unofficial] [NOT IMPLEMENTED - TRICKY, AND NO TEST]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_ERROR, Uop.End }, /*SHY** [absolute indexed WRITE] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_SHY, Uop.End }, /*STA addr,X [absolute indexed WRITE]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_STA, Uop.End }, /*SHX* addr,Y [absolute indexed WRITE Y] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_SHX, Uop.End }, @@ -231,7 +231,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 /*CLV [implied]*/ new Uop[] { Uop.Imp_CLV, Uop.End }, /*LDA addr,Y* [absolute indexed READ Y]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_READ_Stage4, Uop.AbsIdx_READ_Stage5_LDA, Uop.End }, /*TSX [implied]*/ new Uop[] { Uop.Imp_TSX, Uop.End }, - /*LAS* addr,X [absolute indexed READ X] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_READ_Stage4, Uop.AbsIdx_READ_Stage5_ERROR, Uop.End }, + /*LAS* addr,Y [absolute indexed READ Y] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_READ_Stage4, Uop.AbsIdx_READ_Stage5_ERROR, Uop.End }, /*LDY addr,X* [absolute indexed READ X]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_READ_Stage4, Uop.AbsIdx_READ_Stage5_LDY, Uop.End }, /*LDA addr,X* [absolute indexed READ X]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_READ_Stage4, Uop.AbsIdx_READ_Stage5_LDA, Uop.End }, /*LDX addr,Y* [absolute indexed READ Y]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_READ_Stage4, Uop.AbsIdx_READ_Stage5_LDX, Uop.End }, @@ -940,9 +940,10 @@ namespace BizHawk.Emulation.Cores.Components.M6502 rdy_freeze = !RDY; if (RDY) { - if (alu_temp.Bit(8)) + ReadMemory((ushort)ea); + if (alu_temp.Bit(8)) ea = (ushort)(ea + 0x100); - ReadMemory((ushort)ea); + } } @@ -1090,9 +1091,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 void IndIdx_RMW_Stage8() { WriteMemory((ushort)ea, (byte)alu_temp); - - - } + } void RelBranch_Stage2_BVS() { branch_taken = FlagV == true; @@ -2267,8 +2266,9 @@ namespace BizHawk.Emulation.Cores.Components.M6502 rdy_freeze = !RDY; if (RDY) { - //bleh.. redundant code to make sure we dont clobber alu_temp before using it to decide whether to change ea - if (alu_temp.Bit(8)) + //bleh.. redundant code to make sure we dont clobber alu_temp before using it to decide whether to change ea + + if (alu_temp.Bit(8)) { alu_temp = ReadMemory((ushort)ea); ea = (ushort)(ea + 0x100); @@ -2298,14 +2298,16 @@ namespace BizHawk.Emulation.Cores.Components.M6502 } void AbsIdx_WRITE_Stage5_ERROR() { - rdy_freeze = !RDY; - if (RDY) - { - alu_temp = ReadMemory((ushort)ea); - //throw new InvalidOperationException("UNSUPPORTED OPCODE [probably SHS] PLEASE REPORT"); - } + //rdy_freeze = !RDY; + //if (RDY) + //{ + //alu_temp = ReadMemory((ushort)ea); + //throw new InvalidOperationException("UNSUPPORTED OPCODE [probably SHS] PLEASE REPORT"); + //} + S = (byte)(X & A); + WriteMemory((ushort)ea, (byte)(S & opcode3)); - } + } void AbsIdx_RMW_Stage5() { rdy_freeze = !RDY; @@ -2524,8 +2526,12 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (RDY) { alu_temp = ReadMemory((ushort)ea); - //throw new InvalidOperationException("UNSUPPORTED OPCODE [probably LAS] PLEASE REPORT"); - } + // Alyosha: wish me luck! + S &= (byte)alu_temp; + X = S; + A = S; + P = (byte)((P & 0x7D) | TableNZ[S]); + } } void AbsInd_JMP_Stage4() @@ -2959,9 +2965,11 @@ namespace BizHawk.Emulation.Cores.Components.M6502 public void ExecuteOne() { - if (!rdy_freeze) + + TotalExecutedCycles++; + if (!rdy_freeze) { - TotalExecutedCycles++; + interrupt_pending |= Interrupted; } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs index 76985296f3..e54bc3a1d6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs @@ -153,7 +153,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("VRAMBuffer", ref VRAMBuffer); ser.Sync("ppu_addr_temp", ref ppu_addr_temp); - ser.Sync("OAM", ref OAM, false); + ser.Sync("Read_Value", ref read_value); + ser.Sync("Prev_soam_index", ref soam_index_prev); + ser.Sync("Spr_Zero_Go", ref sprite_zero_go); + ser.Sync("Spr_zero_in_Range", ref sprite_zero_in_range); + + ser.Sync("OAM", ref OAM, false); ser.Sync("PALRAM", ref PALRAM, false); ser.Sync("Reg2002_objoverflow", ref Reg2002_objoverflow); @@ -210,7 +215,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES for (int i = 0; i < x; i++) { ppur.status.cycle++; - + is_even_cycle = !is_even_cycle; //might not actually run a cpu cycle if there are none to be run right now nes.RunCpuOne(); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs index 203f0820d6..e6d210d1ba 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs @@ -395,7 +395,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES OAM[reg_2003] = value; reg_2003++; } - byte read_2004() { return OAM[reg_2003]; } + byte read_2004() + { + if (ppur.status.sl < 241) + { + if (ppur.status.cycle < 64) + { + return 0xFF; // during this time all reads return FF + } + else if (ppur.status.cycle < 256) + { + return read_value; + } + else if (ppur.status.cycle < 320) + { + return read_value; + } + else + { + return soam[0]; + } + } else + { + return OAM[reg_2003]; + } + } byte peek_2004() { return OAM[reg_2003]; } //SCROLL (write) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs index f06cd93649..c39bf92beb 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs @@ -5,6 +5,7 @@ //TODO - correctly emulate PPU OFF state using BizHawk.Common; +using System; namespace BizHawk.Emulation.Cores.Nintendo.NES { @@ -19,7 +20,37 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public short[] xbuf = new short[256*240]; - int ppu_addr_temp; + // values here are used in sprite evaluation + public bool sprite_eval_write; + public byte read_value; + public int soam_index; + public int soam_index_prev; + public int soam_m_index; + public int oam_index; + public int read_value_aux; + public int soam_m_index_aux; + public int oam_index_aux; + public bool is_even_cycle; + public bool sprite_zero_in_range=false; + public bool sprite_zero_go = false; + public int yp; + public int spriteHeight; + public int o_bug; // this is incramented when checks for sprite overflow start, mirroring a hardware bug + public byte[] soam = new byte[512]; // in a real nes, this would only be 32, but we wish to allow more then 8 sprites per scanline + + struct TempOAM + { + public byte oam_y; + public byte oam_ind; + public byte oam_attr; + public byte oam_x; + public byte patterns_0; + public byte patterns_1; + } + + TempOAM[] t_oam = new TempOAM[64]; + + int ppu_addr_temp; void Read_bgdata(ref BGDataRecord bgdata) { for (int i = 0; i < 8; i++) @@ -82,14 +113,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } //switch(cycle) } - unsafe struct TempOAM - { - public fixed byte oam[4]; - public fixed byte patterns[2]; - public byte index; - public byte present; - } - //TODO - check flashing sirens in werewolf short PaletteAdjustPixel(int pixel) { @@ -130,11 +153,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //this seems to run just before the dummy scanline begins clear_2002(); - TempOAM* oams = stackalloc TempOAM[128]; - int* oamcounts = stackalloc int[2]; - int oamslot=0; - int oamcount=0; - idleSynch ^= true; //render 241 scanlines (including 1 dummy at beginning) @@ -144,22 +162,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ppur.status.sl = sl; - int yp = sl - 1; + soam_index = 0; + soam_m_index = 0; + soam_m_index_aux = 0; + oam_index_aux = 0; + oam_index = 0; + o_bug = 0; + is_even_cycle = true; + sprite_eval_write = true; + sprite_zero_go = false; + if (sprite_zero_in_range) + sprite_zero_go = true; + + sprite_zero_in_range = false; + + yp = sl - 1; ppuphase = PPUPHASE.BG; if (NTViewCallback != null && yp == NTViewCallback.Scanline) NTViewCallback.Callback(); if (PPUViewCallback != null && yp == PPUViewCallback.Scanline) PPUViewCallback.Callback(); - //twiddle the oam buffers - int scanslot = oamslot ^ 1; - int renderslot = oamslot; - oamslot ^= 1; - - oamcount = oamcounts[renderslot]; - - //ok, we're also going to draw here. - //unless we're on the first dummy scanline - if (sl != 0) + //ok, we're also going to draw here. + //unless we're on the first dummy scanline + if (sl != 0) { //the main scanline rendering loop: //32 times, we will fetch a tile and then render 8 pixels. @@ -168,19 +193,115 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES for (int xt = 0; xt < 32; xt++) { int xstart = xt << 3; - oamcount = oamcounts[renderslot]; int target = yp_shift + xstart; int rasterpos = xstart; - //check all the conditions that can cause things to render in these 8px - bool renderspritenow = reg_2001.show_obj && (xt > 0 || reg_2001.show_obj_leftmost); + spriteHeight = reg_2000.obj_size_16 ? 16 : 8; + + //check all the conditions that can cause things to render in these 8px + bool renderspritenow = reg_2001.show_obj && (xt > 0 || reg_2001.show_obj_leftmost); bool renderbgnow = reg_2001.show_bg && (xt > 0 || reg_2001.show_bg_leftmost); for (int xp = 0; xp < 8; xp++, rasterpos++) { - //process the current clock's worth of bg data fetching - //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work due to the cpu not running while the sprite renders below - Read_bgdata(xp, ref bgdata[xt + 2]); + ////////////////////////////////////////////////// + //Sprite Evaluation Start + ////////////////////////////////////////////////// + if (ppur.status.cycle <= 63 && !is_even_cycle) + { + // the first 64 cycles of each scanline are used to initialize sceondary OAM + // the actual effect setting a flag that always returns 0xFF from a OAM read + // this is a bit of a shortcut to save some instructions + // data is read from OAM as normal but never used + soam[soam_index] = 0xFF; + soam_index++; + } + if (ppur.status.cycle == 64) + soam_index = 0; + + // otherwise, scan through OAM and test if sprites are in range + // if they are, they get copied to the secondary OAM + if (ppur.status.cycle >= 64) + { + if (oam_index==64) + { + oam_index_aux = 0; + oam_index = 0; + sprite_eval_write = false; + } + + if (is_even_cycle) + { + read_value = OAM[oam_index * 4+soam_m_index]; + + if (oam_index_aux>63) + oam_index_aux = 63; + + read_value_aux = OAM[oam_index_aux * 4 + soam_m_index_aux]; + } + else if (sprite_eval_write) + { + if (soam_index >= 8) + { + // this code mirrors sprite overflow bug behaviour + // see http://wiki.nesdev.com/w/index.php/PPU_sprite_evaluation + if (yp >= read_value && yp < read_value + spriteHeight && reg_2001.PPUON) + { + Reg2002_objoverflow = true; + } + else + { + soam_m_index++; + oam_index++; + if (soam_m_index == 4) + soam_m_index = 0; + + } + + } + + //look for sprites + soam[soam_index * 4] = OAM[oam_index_aux * 4]; + if (yp >= read_value_aux && yp < read_value_aux + spriteHeight && soam_m_index_aux == 0) + { + //a flag gets set if sprite zero is in range + if (oam_index_aux == 0) + sprite_zero_in_range = true; + + soam_m_index_aux++; + + } else if (soam_m_index_aux > 0 && soam_m_index_aux < 4) + { + soam[soam_index * 4 + soam_m_index_aux] = OAM[oam_index_aux * 4 + soam_m_index_aux]; + soam_m_index_aux++; + if (soam_m_index_aux == 4) + { + oam_index_aux++; + soam_index++; + soam_m_index_aux = 0; + } + } else + { + oam_index_aux++; + } + + if (soam_index<8) + { + soam_m_index = soam_m_index_aux; + oam_index = oam_index_aux; + } + + + } + + } + ////////////////////////////////////////////////// + //Sprite Evaluation End + ////////////////////////////////////////////////// + + //process the current clock's worth of bg data fetching + //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work due to the cpu not running while the sprite renders below + Read_bgdata(xp, ref bgdata[xt + 2]); //bg pos is different from raster pos due to its offsetability. //so adjust for that here @@ -219,44 +340,43 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (!nes.Settings.DispBackground) pixelcolor = 0x8000; //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later - - //look for a sprite to be drawn - bool havepixel = false; - int renderslot_shift = renderslot << 6; - for (int s = 0; s < oamcount; s++) + + //look for a sprite to be drawn + bool havepixel = false; + for (int s = 0; s < soam_index_prev; s++) { - TempOAM* oam = &oams[renderslot_shift + s]; - int x = oam->oam[3]; + int x = t_oam[s].oam_x; if (rasterpos >= x && rasterpos < x + 8) { //build the pixel. //fetch the LSB of the patterns - int spixel = oam->patterns[0] & 1; - spixel |= (oam->patterns[1] & 1) << 1; + int spixel = t_oam[s].patterns_0 & 1; + spixel |= (t_oam[s].patterns_1 & 1) << 1; - //shift down the patterns so the next pixel is in the LSB - oam->patterns[0] >>= 1; - oam->patterns[1] >>= 1; + //shift down the patterns so the next pixel is in the LSB + t_oam[s].patterns_0 >>= 1; + t_oam[s].patterns_1 >>= 1; - //bail out if we already have a pixel from a higher priority sprite. - //notice that we continue looping anyway, so that we can shift down the patterns - //transparent pixel bailout - if (!renderspritenow || havepixel || spixel == 0) continue; + //bail out if we already have a pixel from a higher priority sprite. + //notice that we continue looping anyway, so that we can shift down the patterns + //transparent pixel bailout + if (!renderspritenow || havepixel || spixel == 0) continue; havepixel = true; - + //TODO - make sure we dont trigger spritehit if the edges are masked for either BG or OBJ //spritehit: //1. is it sprite#0? //2. is the bg pixel nonzero? //then, it is spritehit. - Reg2002_objhit |= (oam->index == 0 && pixel != 0 && rasterpos < 255); + Reg2002_objhit |= (sprite_zero_go && s==0 && pixel != 0 && rasterpos < 255); + //priority handling, if in front of BG: - bool drawsprite = !(((oam->oam[2] & 0x20) != 0) && ((pixel & 3) != 0)); + bool drawsprite = !(((t_oam[s].oam_attr & 0x20) != 0) && ((pixel & 3) != 0)); if (drawsprite && nes.Settings.DispSprites) { //bring in the palette bits and palettize - spixel |= (oam->oam[2] & 3) << 2; + spixel |= (t_oam[s].oam_attr & 3) << 2; //save it for use in the framebuffer pixelcolor = PALRAM[0x10 + spixel]; } @@ -273,77 +393,50 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } else for (int xt = 0; xt < 32; xt++) - Read_bgdata(ref bgdata[xt + 2]); + Read_bgdata(ref bgdata[xt + 2]); - //look for sprites (was supposed to run concurrent with bg rendering) - oamcounts[scanslot] = 0; - oamcount = 0; - int spriteHeight = reg_2000.obj_size_16 ? 16 : 8; - - int scanslot_lshift = scanslot << 6; - - for (int i = 0; i < 64; i++) - { - oams[scanslot_lshift + i].present = 0; - int spr = i * 4; - if (yp >= OAM[spr] && yp < OAM[spr] + spriteHeight) - { - //if we already have maxsprites, then this new one causes an overflow, - //set the flag and bail out. - //should we set this flag anyway?? - if (oamcount >= 8 && reg_2001.PPUON) - { - Reg2002_objoverflow = true; - if(!nes.Settings.AllowMoreThanEightSprites) - break; - } - //just copy some bytes into the internal sprite buffer - TempOAM* oam = &oams[scanslot_lshift + oamcount]; - for (int j = 0; j < 4; j++) - oam->oam[j] = OAM[spr + j]; - oam->present = 1; - //note that we stuff the oam index into [6]. - //i need to turn this into a struct so we can have fewer magic numbers - oams[scanslot_lshift + oamcount].index = (byte)i; - oamcount++; - } - } - oamcounts[scanslot] = oamcount; + // normally only 8 sprites are allowed, but with a particular setting we can have more then that + soam_index_prev = soam_index; + if (soam_index_prev > 8 && !nes.Settings.AllowMoreThanEightSprites) + soam_index_prev = 8; ppuphase = PPUPHASE.OBJ; - //fetch sprite patterns - int oam_todo = oamcount; - if (oam_todo < 8) - oam_todo = 8; - for (int s = 0; s < oam_todo; s++) + spriteHeight = reg_2000.obj_size_16 ? 16 : 8; + + // if there are less then 8 evaluated sprites, we still process 8 sprites + int bound; + + if (soam_index_prev>8) + { + bound = soam_index_prev; + } else + { + bound = 8; + } + + for (int s = 0; s < bound; s++) { //if this is a real sprite sprite, then it is not above the 8 sprite limit. //this is how we support the no 8 sprite limit feature. //not that at some point we may need a virtual CALL_PPUREAD which just peeks and doesnt increment any counters //this could be handy for the debugging tools also bool realSprite = (s < 8); - bool junksprite = (s >= oamcount || !reg_2001.PPUON); + bool junksprite = (!reg_2001.PPUON); + bool extra_sprite = (s >= 8); - TempOAM* oam = &oams[scanslot_lshift + s]; - int line = yp - oam->oam[0]; - if ((oam->oam[2] & 0x80) != 0) //vflip + t_oam[s].oam_y = soam[s*4]; + t_oam[s].oam_ind = soam[s * 4+1]; + t_oam[s].oam_attr = soam[s * 4+2]; + t_oam[s].oam_x = soam[s * 4+3]; + + int line = yp - t_oam[s].oam_y; + if ((t_oam[s].oam_attr & 0x80) != 0) //vflip line = spriteHeight - line - 1; - int patternNumber = oam->oam[1]; + int patternNumber = t_oam[s].oam_ind; int patternAddress; - //create deterministic dummy fetch pattern. - if (oam->present == 0) - { - //according to nintendulator: - //* On the first empty sprite slot, read the Y-coordinate of sprite #63 followed by $FF for the remaining 7 cycles - //* On all subsequent empty sprite slots, read $FF for all 8 reads - //well, we shall just read $FF and that is good enough for now to make mmc3 work - patternNumber = 0xFF; - line = 0; - } - //8x16 sprite handling: if (reg_2000.obj_size_16) { @@ -368,60 +461,96 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (sl == 0 && ppur.status.cycle == 304) { runppu(1); - if (reg_2001.PPUON) ppur.install_latches(); + read_value = t_oam[s].oam_y; + if (reg_2001.PPUON) ppur.install_latches(); runppu(1); - garbage_todo = 0; + read_value = t_oam[s].oam_ind; + garbage_todo = 0; } if ((sl != 0) && ppur.status.cycle == 256) { runppu(1); - //at 257: 3d world runner is ugly if we do this at 256 - if (reg_2001.PPUON) ppur.install_h_latches(); + read_value = t_oam[s].oam_y; + //at 257: 3d world runner is ugly if we do this at 256 + if (reg_2001.PPUON) ppur.install_h_latches(); runppu(1); - garbage_todo = 0; + read_value = t_oam[s].oam_ind; + garbage_todo = 0; } } - if (realSprite) runppu(garbage_todo); + if (realSprite) + { + for (int i=0;ipatterns[0] = ppubus_read(addr, true); - if (realSprite) runppu(kFetchTime); - addr += 8; - oam->patterns[1] = ppubus_read(addr, true); - if (realSprite) runppu(kFetchTime); + // hflip + if ((t_oam[s].oam_attr & 0x40) == 0) + { + t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0]; + t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1]; + } + + } - // hflip - if ((oam->oam[2] & 0x40) == 0) - { - oam->patterns[0] = BitReverse.Byte8[oam->patterns[0]]; - oam->patterns[1] = BitReverse.Byte8[oam->patterns[1]]; - } - } } // sprite pattern fetch loop ppuphase = PPUPHASE.BG; // fetch BG: two tiles for next line for (int xt = 0; xt < 2; xt++) - Read_bgdata(ref bgdata[xt]); + { + Read_bgdata(ref bgdata[xt]); + } + // this sequence is tuned to pass 10-even_odd_timing.nes runppu(kFetchTime);