mirror of
https://github.com/libretro/snes9x.git
synced 2026-10-17 15:25:43 +00:00
S9xMode7VertResample expands a frame to twice its height in place,
writing rows 2y and 2y+1 for every source row. On an interlaced frame
that is wrong twice over: GFX.PPL is already GFX.RealPPL << 1, so the
frame occupies 2 * PPU.ScreenHeight physical rows before the pass runs,
and doubling again asks for four times the height. At 239-line overscan
that is 956 rows against the 510 GFXScreenBuffer holds, and because the
walk runs bottom-up it writes the out-of-range rows first - roughly
850 KB past the end of the buffer, through whatever the allocator put
after it. In RetroArch that neighbour can be the video driver's own
allocations, which is what "the core is corrupting Vulkan font glyphs"
looks like from the outside.
Interlace already supplies the vertical resolution this pass exists to
synthesise, so skip it when the stride says the frame is interlaced,
and add a capacity check so no future arming path can overrun either.
Found with AddressSanitizer using a stress ROM that forces BGMODE 7
with overscan, interlace and pseudo-hires - Killer Instinct never
enters Mode 7, so nothing in the existing test material reached this
code:
WRITE of size 2048 at 0x7f28705f5800
#2 S9xMode7VertResample ../ppu.cpp:1811
#3 S9xEndScreenRefresh ../gfx.cpp:226
0x7f28705f5800 is located 4096 bytes before 12616192-byte region
snes9x2010 carries the same pass and the same defect; it is fixed there
separately.
Killer Instinct is bit-identical on four input scripts, and the full
Mode 7 x NTSC-filter option matrix - 25 combinations - is clean under
ASan afterwards.
2030 lines
49 KiB
C++
2030 lines
49 KiB
C++
/*****************************************************************************\
|
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
|
This file is licensed under the Snes9x License.
|
|
For further information, consult the LICENSE file in the root directory.
|
|
\*****************************************************************************/
|
|
|
|
#include "snes9x.h"
|
|
#include "memmap.h"
|
|
#include "dma.h"
|
|
#include "apu/apu.h"
|
|
#include "fxemu.h"
|
|
#include "sdd1.h"
|
|
#include "srtc.h"
|
|
#include "controls.h"
|
|
#include "display.h"
|
|
#ifdef DEBUGGER
|
|
#include "debug.h"
|
|
#include "missing.h"
|
|
#endif
|
|
|
|
extern uint8 *HDMAMemPointers[8];
|
|
|
|
|
|
static inline void S9xLatchCounters (bool force)
|
|
{
|
|
if (force || (Memory.FillRAM[0x4213] & 0x80))
|
|
{
|
|
// Latch h and v counters, like the gun
|
|
#ifdef DEBUGGER
|
|
missing.h_v_latch = 1;
|
|
#endif
|
|
|
|
PPU.HVBeamCounterLatched = 1;
|
|
PPU.VBeamPosLatched = (uint16) CPU.V_Counter;
|
|
|
|
// From byuu:
|
|
// All dots are 4 cycles long, except dots 322 and 326. dots 322 and 326 are 6 cycles long.
|
|
// This holds true for all scanlines except scanline 240 on non-interlace odd frames.
|
|
// The reason for this is because this scanline is only 1360 cycles long,
|
|
// instead of 1364 like all other scanlines.
|
|
// This makes the effective range of hscan_pos 0-339 at all times.
|
|
int32 hc = CPU.Cycles;
|
|
|
|
if (Timings.H_Max == Timings.H_Max_Master) // 1364
|
|
{
|
|
if (hc >= 1292)
|
|
hc -= (ONE_DOT_CYCLE / 2);
|
|
if (hc >= 1308)
|
|
hc -= (ONE_DOT_CYCLE / 2);
|
|
}
|
|
|
|
PPU.HBeamPosLatched = (uint16) (hc / ONE_DOT_CYCLE);
|
|
|
|
Memory.FillRAM[0x213f] |= 0x40;
|
|
}
|
|
|
|
if (CPU.V_Counter > PPU.GunVLatch || (CPU.V_Counter == PPU.GunVLatch && CPU.Cycles >= PPU.GunHLatch * ONE_DOT_CYCLE))
|
|
PPU.GunVLatch = 1000;
|
|
}
|
|
|
|
static inline void S9xTryGunLatch (bool force)
|
|
{
|
|
if (CPU.V_Counter > PPU.GunVLatch || (CPU.V_Counter == PPU.GunVLatch && CPU.Cycles >= PPU.GunHLatch * ONE_DOT_CYCLE))
|
|
{
|
|
if (force || (Memory.FillRAM[0x4213] & 0x80))
|
|
{
|
|
#ifdef DEBUGGER
|
|
missing.h_v_latch = 1;
|
|
#endif
|
|
|
|
PPU.HVBeamCounterLatched = 1;
|
|
PPU.VBeamPosLatched = (uint16) PPU.GunVLatch;
|
|
PPU.HBeamPosLatched = (uint16) PPU.GunHLatch;
|
|
|
|
Memory.FillRAM[0x213f] |= 0x40;
|
|
}
|
|
|
|
PPU.GunVLatch = 1000;
|
|
}
|
|
}
|
|
|
|
static int CyclesUntilNext (int hc, int vc)
|
|
{
|
|
int32 total = 0;
|
|
int vpos = CPU.V_Counter;
|
|
|
|
if (vc - vpos > 0)
|
|
{
|
|
// It's still in this frame */
|
|
// Add number of lines
|
|
total += (vc - vpos) * Timings.H_Max_Master;
|
|
// If line 240 is in there and we're odd, subtract a dot
|
|
if (vpos <= 240 && vc > 240 && S9xInterlaceField() & !IPPU.Interlace)
|
|
total -= ONE_DOT_CYCLE;
|
|
}
|
|
else
|
|
{
|
|
if (vc == vpos && (hc > CPU.Cycles))
|
|
{
|
|
return hc;
|
|
}
|
|
|
|
total += (Timings.V_Max - vpos) * Timings.H_Max_Master;
|
|
if (vpos <= 240 && S9xInterlaceField() && !IPPU.Interlace)
|
|
total -= ONE_DOT_CYCLE;
|
|
|
|
total += (vc) * Timings.H_Max_Master;
|
|
if (vc > 240 && !S9xInterlaceField() && !IPPU.Interlace)
|
|
total -= ONE_DOT_CYCLE;
|
|
}
|
|
|
|
total += hc;
|
|
|
|
return total;
|
|
}
|
|
|
|
void S9xUpdateIRQPositions (bool initial)
|
|
{
|
|
PPU.HTimerPosition = PPU.IRQHBeamPos * ONE_DOT_CYCLE + Timings.IRQTriggerCycles;
|
|
PPU.HTimerPosition -= PPU.IRQHBeamPos ? 0 : ONE_DOT_CYCLE;
|
|
PPU.HTimerPosition += PPU.IRQHBeamPos > 322 ? (ONE_DOT_CYCLE / 2) : 0;
|
|
PPU.HTimerPosition += PPU.IRQHBeamPos > 326 ? (ONE_DOT_CYCLE / 2) : 0;
|
|
PPU.VTimerPosition = PPU.IRQVBeamPos;
|
|
|
|
if (PPU.VTimerEnabled && (PPU.VTimerPosition >= (Timings.V_Max + (IPPU.Interlace ? 1 : 0))))
|
|
{
|
|
Timings.NextIRQTimer = 0x0fffffff;
|
|
}
|
|
else if (!PPU.HTimerEnabled && !PPU.VTimerEnabled)
|
|
{
|
|
Timings.NextIRQTimer = 0x0fffffff;
|
|
}
|
|
else if (PPU.HTimerEnabled && !PPU.VTimerEnabled)
|
|
{
|
|
int v_pos = CPU.V_Counter;
|
|
|
|
Timings.NextIRQTimer = PPU.HTimerPosition;
|
|
if (CPU.Cycles > Timings.NextIRQTimer - Timings.IRQTriggerCycles)
|
|
{
|
|
Timings.NextIRQTimer += Timings.H_Max;
|
|
v_pos++;
|
|
}
|
|
|
|
// Check for short dot scanline
|
|
if (v_pos == 240 && S9xInterlaceField() && !IPPU.Interlace)
|
|
{
|
|
Timings.NextIRQTimer -= PPU.IRQHBeamPos <= 322 ? ONE_DOT_CYCLE / 2 : 0;
|
|
Timings.NextIRQTimer -= PPU.IRQHBeamPos <= 326 ? ONE_DOT_CYCLE / 2 : 0;
|
|
}
|
|
}
|
|
else if (!PPU.HTimerEnabled && PPU.VTimerEnabled)
|
|
{
|
|
if (CPU.V_Counter == PPU.VTimerPosition && initial)
|
|
Timings.NextIRQTimer = CPU.Cycles + Timings.IRQTriggerCycles - ONE_DOT_CYCLE;
|
|
else
|
|
Timings.NextIRQTimer = CyclesUntilNext (Timings.IRQTriggerCycles - ONE_DOT_CYCLE, PPU.VTimerPosition);
|
|
}
|
|
else
|
|
{
|
|
Timings.NextIRQTimer = CyclesUntilNext (PPU.HTimerPosition, PPU.VTimerPosition);
|
|
|
|
// Check for short dot scanline
|
|
int field = S9xInterlaceField();
|
|
|
|
if (PPU.VTimerPosition < CPU.V_Counter ||
|
|
(PPU.VTimerPosition == CPU.V_Counter && Timings.NextIRQTimer > Timings.H_Max))
|
|
{
|
|
field = !field;
|
|
}
|
|
|
|
if (PPU.VTimerPosition == 240 && field && !IPPU.Interlace)
|
|
{
|
|
Timings.NextIRQTimer -= PPU.IRQHBeamPos <= 322 ? ONE_DOT_CYCLE / 2 : 0;
|
|
Timings.NextIRQTimer -= PPU.IRQHBeamPos <= 326 ? ONE_DOT_CYCLE / 2 : 0;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUGGER
|
|
S9xTraceFormattedMessage("--- IRQ Timer HC:%d VC:%d set %d cycles HTimer:%d Pos:%04d->%04d VTimer:%d Pos:%03d->%03d", CPU.Cycles, CPU.V_Counter,
|
|
Timings.NextIRQTimer, PPU.HTimerEnabled, PPU.IRQHBeamPos, PPU.HTimerPosition, PPU.VTimerEnabled, PPU.IRQVBeamPos, PPU.VTimerPosition);
|
|
#endif
|
|
}
|
|
|
|
void S9xFixColourBrightness (void)
|
|
{
|
|
IPPU.XB = mul_brightness[PPU.Brightness];
|
|
|
|
for (int i = 0; i < 64; i++)
|
|
{
|
|
if (i > IPPU.XB[0x1f])
|
|
brightness_cap[i] = IPPU.XB[0x1f];
|
|
else
|
|
brightness_cap[i] = i;
|
|
}
|
|
|
|
for (int i = 0; i < 256; i++)
|
|
{
|
|
IPPU.Red[i] = IPPU.XB[(PPU.CGDATA[i]) & 0x1f];
|
|
IPPU.Green[i] = IPPU.XB[(PPU.CGDATA[i] >> 5) & 0x1f];
|
|
IPPU.Blue[i] = IPPU.XB[(PPU.CGDATA[i] >> 10) & 0x1f];
|
|
IPPU.ScreenColors[i] = BUILD_PIXEL(IPPU.Red[i], IPPU.Green[i], IPPU.Blue[i]);
|
|
}
|
|
}
|
|
|
|
void S9xSetPPU (uint8 Byte, uint16 Address)
|
|
{
|
|
// MAP_PPU: $2000-$3FFF
|
|
|
|
if (CPU.InDMAorHDMA)
|
|
{
|
|
if (CPU.CurrentDMAorHDMAChannel >= 0 && DMA[CPU.CurrentDMAorHDMAChannel].ReverseTransfer)
|
|
{
|
|
// S9xSetPPU() is called to write to DMA[].AAddress
|
|
if ((Address & 0xff00) == 0x2100)
|
|
{
|
|
// Cannot access to Address Bus B ($2100-$21ff) via (H)DMA
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// 0x2000-0x3FFF is connected to Address Bus A
|
|
// SA1, SuperFX and SRTC are mapped here
|
|
// I don't bother for now...
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// S9xSetPPU() is called to read from $21xx
|
|
// Take care of DMA wrapping
|
|
if (Address > 0x21ff)
|
|
Address = 0x2100 + (Address & 0xff);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUGGER
|
|
if (CPU.InHDMA)
|
|
S9xTraceFormattedMessage("--- HDMA PPU %04X -> %02X", Address, Byte);
|
|
#endif
|
|
|
|
if (Settings.MSU1 && (Address & 0xfff8) == 0x2000) // MSU-1
|
|
S9xMSU1WritePort(Address & 7, Byte);
|
|
else
|
|
if ((Address & 0xffc0) == 0x2140) // APUIO0, APUIO1, APUIO2, APUIO3
|
|
// write_port will run the APU until given clock before writing value
|
|
S9xAPUWritePort(Address & 3, Byte);
|
|
else
|
|
if (Address <= 0x2183)
|
|
{
|
|
switch (Address)
|
|
{
|
|
case 0x2100: // INIDISP
|
|
if (Byte != Memory.FillRAM[0x2100])
|
|
{
|
|
FLUSH_REDRAW();
|
|
|
|
if (PPU.Brightness != (Byte & 0xf))
|
|
{
|
|
IPPU.ColorsChanged = TRUE;
|
|
PPU.Brightness = Byte & 0xf;
|
|
S9xFixColourBrightness();
|
|
S9xBuildDirectColourMaps();
|
|
if (PPU.Brightness > IPPU.MaxBrightness)
|
|
IPPU.MaxBrightness = PPU.Brightness;
|
|
}
|
|
|
|
if ((Memory.FillRAM[0x2100] & 0x80) != (Byte & 0x80))
|
|
{
|
|
IPPU.ColorsChanged = TRUE;
|
|
PPU.ForcedBlanking = (Byte >> 7) & 1;
|
|
}
|
|
}
|
|
|
|
if ((Memory.FillRAM[0x2100] & 0x80) && CPU.V_Counter == PPU.ScreenHeight + FIRST_VISIBLE_LINE)
|
|
{
|
|
PPU.OAMAddr = PPU.SavedOAMAddr;
|
|
|
|
uint8 tmp = 0;
|
|
if (PPU.OAMPriorityRotation)
|
|
tmp = (PPU.OAMAddr & 0xfe) >> 1;
|
|
if ((PPU.OAMFlip & 1) || PPU.FirstSprite != tmp)
|
|
{
|
|
PPU.FirstSprite = tmp;
|
|
IPPU.OBJChanged = TRUE;
|
|
}
|
|
|
|
PPU.OAMFlip = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2101: // OBSEL
|
|
if (Byte != Memory.FillRAM[0x2101])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.OBJNameBase = (Byte & 3) << 14;
|
|
PPU.OBJNameSelect = ((Byte >> 3) & 3) << 13;
|
|
PPU.OBJSizeSelect = (Byte >> 5) & 7;
|
|
IPPU.OBJChanged = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2102: // OAMADDL
|
|
PPU.OAMAddr = ((Memory.FillRAM[0x2103] & 1) << 8) | Byte;
|
|
PPU.OAMFlip = 0;
|
|
PPU.OAMReadFlip = 0;
|
|
PPU.SavedOAMAddr = PPU.OAMAddr;
|
|
if (PPU.OAMPriorityRotation && PPU.FirstSprite != (PPU.OAMAddr >> 1))
|
|
{
|
|
PPU.FirstSprite = (PPU.OAMAddr & 0xfe) >> 1;
|
|
IPPU.OBJChanged = TRUE;
|
|
#ifdef DEBUGGER
|
|
missing.sprite_priority_rotation = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2103: // OAMADDH
|
|
PPU.OAMAddr = ((Byte & 1) << 8) | Memory.FillRAM[0x2102];
|
|
PPU.OAMPriorityRotation = (Byte & 0x80) ? 1 : 0;
|
|
if (PPU.OAMPriorityRotation)
|
|
{
|
|
if (PPU.FirstSprite != (PPU.OAMAddr >> 1))
|
|
{
|
|
PPU.FirstSprite = (PPU.OAMAddr & 0xfe) >> 1;
|
|
IPPU.OBJChanged = TRUE;
|
|
#ifdef DEBUGGER
|
|
missing.sprite_priority_rotation = 1;
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PPU.FirstSprite != 0)
|
|
{
|
|
PPU.FirstSprite = 0;
|
|
IPPU.OBJChanged = TRUE;
|
|
#ifdef DEBUGGER
|
|
missing.sprite_priority_rotation = 1;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
PPU.OAMFlip = 0;
|
|
PPU.OAMReadFlip = 0;
|
|
PPU.SavedOAMAddr = PPU.OAMAddr;
|
|
|
|
break;
|
|
|
|
case 0x2104: // OAMDATA
|
|
REGISTER_2104(Byte);
|
|
break;
|
|
|
|
case 0x2105: // BGMODE
|
|
if (Byte != Memory.FillRAM[0x2105])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.BG[0].BGSize = (Byte >> 4) & 1;
|
|
PPU.BG[1].BGSize = (Byte >> 5) & 1;
|
|
PPU.BG[2].BGSize = (Byte >> 6) & 1;
|
|
PPU.BG[3].BGSize = (Byte >> 7) & 1;
|
|
PPU.BGMode = Byte & 7;
|
|
// BJ: BG3Priority only takes effect if BGMode == 1 and the bit is set
|
|
PPU.BG3Priority = ((Byte & 0x0f) == 0x09);
|
|
if (PPU.BGMode == 6 || PPU.BGMode == 5 || PPU.BGMode == 7)
|
|
IPPU.Interlace = Memory.FillRAM[0x2133] & 1;
|
|
else
|
|
IPPU.Interlace = 0;
|
|
#ifdef DEBUGGER
|
|
missing.modes[PPU.BGMode] = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2106: // MOSAIC
|
|
if (Byte != Memory.FillRAM[0x2106])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.MosaicStart = CPU.V_Counter;
|
|
if (PPU.MosaicStart > PPU.ScreenHeight)
|
|
PPU.MosaicStart = 0;
|
|
PPU.Mosaic = (Byte >> 4) + 1;
|
|
PPU.BGMosaic[0] = (Byte & 1);
|
|
PPU.BGMosaic[1] = (Byte & 2);
|
|
PPU.BGMosaic[2] = (Byte & 4);
|
|
PPU.BGMosaic[3] = (Byte & 8);
|
|
#ifdef DEBUGGER
|
|
if ((Byte & 0xf0) && (Byte & 0x0f))
|
|
missing.mosaic = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2107: // BG1SC
|
|
if (Byte != Memory.FillRAM[0x2107])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.BG[0].SCSize = Byte & 3;
|
|
PPU.BG[0].SCBase = (Byte & 0x7c) << 8;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2108: // BG2SC
|
|
if (Byte != Memory.FillRAM[0x2108])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.BG[1].SCSize = Byte & 3;
|
|
PPU.BG[1].SCBase = (Byte & 0x7c) << 8;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2109: // BG3SC
|
|
if (Byte != Memory.FillRAM[0x2109])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.BG[2].SCSize = Byte & 3;
|
|
PPU.BG[2].SCBase = (Byte & 0x7c) << 8;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x210a: // BG4SC
|
|
if (Byte != Memory.FillRAM[0x210a])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.BG[3].SCSize = Byte & 3;
|
|
PPU.BG[3].SCBase = (Byte & 0x7c) << 8;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x210b: // BG12NBA
|
|
if (Byte != Memory.FillRAM[0x210b])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.BG[0].NameBase = (Byte & 7) << 12;
|
|
PPU.BG[1].NameBase = ((Byte >> 4) & 7) << 12;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x210c: // BG34NBA
|
|
if (Byte != Memory.FillRAM[0x210c])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.BG[2].NameBase = (Byte & 7) << 12;
|
|
PPU.BG[3].NameBase = ((Byte >> 4) & 7) << 12;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x210d: // BG1HOFS, M7HOFS
|
|
PPU.BG[0].HOffset = (Byte << 8) | (PPU.BGnxOFSbyte & ~7) | ((PPU.BG[0].HOffset >> 8) & 7);
|
|
PPU.M7HOFS = (Byte << 8) | PPU.M7byte;
|
|
PPU.BGnxOFSbyte = Byte;
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x210e: // BG1VOFS, M7VOFS
|
|
PPU.BG[0].VOffset = (Byte << 8) | PPU.BGnxOFSbyte;
|
|
PPU.M7VOFS = (Byte << 8) | PPU.M7byte;
|
|
PPU.BGnxOFSbyte = Byte;
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x210f: // BG2HOFS
|
|
PPU.BG[1].HOffset = (Byte << 8) | (PPU.BGnxOFSbyte & ~7) | ((PPU.BG[1].HOffset >> 8) & 7);
|
|
PPU.BGnxOFSbyte = Byte;
|
|
break;
|
|
|
|
case 0x2110: // BG2VOFS
|
|
PPU.BG[1].VOffset = (Byte << 8) | PPU.BGnxOFSbyte;
|
|
PPU.BGnxOFSbyte = Byte;
|
|
break;
|
|
|
|
case 0x2111: // BG3HOFS
|
|
PPU.BG[2].HOffset = (Byte << 8) | (PPU.BGnxOFSbyte & ~7) | ((PPU.BG[2].HOffset >> 8) & 7);
|
|
PPU.BGnxOFSbyte = Byte;
|
|
break;
|
|
|
|
case 0x2112: // BG3VOFS
|
|
PPU.BG[2].VOffset = (Byte << 8) | PPU.BGnxOFSbyte;
|
|
PPU.BGnxOFSbyte = Byte;
|
|
break;
|
|
|
|
case 0x2113: // BG4HOFS
|
|
PPU.BG[3].HOffset = (Byte << 8) | (PPU.BGnxOFSbyte & ~7) | ((PPU.BG[3].HOffset >> 8) & 7);
|
|
PPU.BGnxOFSbyte = Byte;
|
|
break;
|
|
|
|
case 0x2114: // BG4VOFS
|
|
PPU.BG[3].VOffset = (Byte << 8) | PPU.BGnxOFSbyte;
|
|
PPU.BGnxOFSbyte = Byte;
|
|
break;
|
|
|
|
case 0x2115: // VMAIN
|
|
PPU.VMA.High = (Byte & 0x80) == 0 ? FALSE : TRUE;
|
|
switch (Byte & 3)
|
|
{
|
|
case 0: PPU.VMA.Increment = 1; break;
|
|
case 1: PPU.VMA.Increment = 32; break;
|
|
case 2: PPU.VMA.Increment = 128; break;
|
|
case 3: PPU.VMA.Increment = 128; break;
|
|
}
|
|
|
|
if (Byte & 0x0c)
|
|
{
|
|
static uint16 Shift[4] = { 0, 5, 6, 7 };
|
|
static uint16 IncCount[4] = { 0, 32, 64, 128 };
|
|
|
|
uint8 i = (Byte & 0x0c) >> 2;
|
|
PPU.VMA.FullGraphicCount = IncCount[i];
|
|
PPU.VMA.Mask1 = IncCount[i] * 8 - 1;
|
|
PPU.VMA.Shift = Shift[i];
|
|
#ifdef DEBUGGER
|
|
missing.vram_full_graphic_inc = (Byte & 0x0c) >> 2;
|
|
#endif
|
|
}
|
|
else
|
|
PPU.VMA.FullGraphicCount = 0;
|
|
#ifdef DEBUGGER
|
|
if (Byte & 3)
|
|
missing.vram_inc = Byte & 3;
|
|
#endif
|
|
break;
|
|
|
|
case 0x2116: // VMADDL
|
|
PPU.VMA.Address &= 0xff00;
|
|
PPU.VMA.Address |= Byte;
|
|
|
|
S9xUpdateVRAMReadBuffer();
|
|
|
|
break;
|
|
|
|
case 0x2117: // VMADDH
|
|
PPU.VMA.Address &= 0x00ff;
|
|
PPU.VMA.Address |= Byte << 8;
|
|
|
|
S9xUpdateVRAMReadBuffer();
|
|
|
|
break;
|
|
|
|
case 0x2118: // VMDATAL
|
|
REGISTER_2118(Byte);
|
|
break;
|
|
|
|
case 0x2119: // VMDATAH
|
|
REGISTER_2119(Byte);
|
|
break;
|
|
|
|
case 0x211a: // M7SEL
|
|
if (Byte != Memory.FillRAM[0x211a])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.Mode7Repeat = Byte >> 6;
|
|
if (PPU.Mode7Repeat == 1)
|
|
PPU.Mode7Repeat = 0;
|
|
PPU.Mode7VFlip = (Byte & 2) >> 1;
|
|
PPU.Mode7HFlip = Byte & 1;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x211b: // M7A
|
|
PPU.MatrixA = PPU.M7byte | (Byte << 8);
|
|
PPU.Need16x8Mulitply = TRUE;
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x211c: // M7B
|
|
PPU.MatrixB = PPU.M7byte | (Byte << 8);
|
|
PPU.Need16x8Mulitply = TRUE;
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x211d: // M7C
|
|
PPU.MatrixC = PPU.M7byte | (Byte << 8);
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x211e: // M7D
|
|
PPU.MatrixD = PPU.M7byte | (Byte << 8);
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x211f: // M7X
|
|
PPU.CentreX = PPU.M7byte | (Byte << 8);
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x2120: // M7Y
|
|
PPU.CentreY = PPU.M7byte | (Byte << 8);
|
|
PPU.M7byte = Byte;
|
|
break;
|
|
|
|
case 0x2121: // CGADD
|
|
PPU.CGFLIP = 0;
|
|
PPU.CGFLIPRead = 0;
|
|
PPU.CGADD = Byte;
|
|
break;
|
|
|
|
case 0x2122: // CGDATA
|
|
REGISTER_2122(Byte);
|
|
break;
|
|
|
|
case 0x2123: // W12SEL
|
|
if (Byte != Memory.FillRAM[0x2123])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.ClipWindow1Enable[0] = !!(Byte & 0x02);
|
|
PPU.ClipWindow1Enable[1] = !!(Byte & 0x20);
|
|
PPU.ClipWindow2Enable[0] = !!(Byte & 0x08);
|
|
PPU.ClipWindow2Enable[1] = !!(Byte & 0x80);
|
|
PPU.ClipWindow1Inside[0] = !(Byte & 0x01);
|
|
PPU.ClipWindow1Inside[1] = !(Byte & 0x10);
|
|
PPU.ClipWindow2Inside[0] = !(Byte & 0x04);
|
|
PPU.ClipWindow2Inside[1] = !(Byte & 0x40);
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
#ifdef DEBUGGER
|
|
if (Byte & 0x80)
|
|
missing.window2[1] = 1;
|
|
if (Byte & 0x20)
|
|
missing.window1[1] = 1;
|
|
if (Byte & 0x08)
|
|
missing.window2[0] = 1;
|
|
if (Byte & 0x02)
|
|
missing.window1[0] = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2124: // W34SEL
|
|
if (Byte != Memory.FillRAM[0x2124])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.ClipWindow1Enable[2] = !!(Byte & 0x02);
|
|
PPU.ClipWindow1Enable[3] = !!(Byte & 0x20);
|
|
PPU.ClipWindow2Enable[2] = !!(Byte & 0x08);
|
|
PPU.ClipWindow2Enable[3] = !!(Byte & 0x80);
|
|
PPU.ClipWindow1Inside[2] = !(Byte & 0x01);
|
|
PPU.ClipWindow1Inside[3] = !(Byte & 0x10);
|
|
PPU.ClipWindow2Inside[2] = !(Byte & 0x04);
|
|
PPU.ClipWindow2Inside[3] = !(Byte & 0x40);
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
#ifdef DEBUGGER
|
|
if (Byte & 0x80)
|
|
missing.window2[3] = 1;
|
|
if (Byte & 0x20)
|
|
missing.window1[3] = 1;
|
|
if (Byte & 0x08)
|
|
missing.window2[2] = 1;
|
|
if (Byte & 0x02)
|
|
missing.window1[2] = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2125: // WOBJSEL
|
|
if (Byte != Memory.FillRAM[0x2125])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.ClipWindow1Enable[4] = !!(Byte & 0x02);
|
|
PPU.ClipWindow1Enable[5] = !!(Byte & 0x20);
|
|
PPU.ClipWindow2Enable[4] = !!(Byte & 0x08);
|
|
PPU.ClipWindow2Enable[5] = !!(Byte & 0x80);
|
|
PPU.ClipWindow1Inside[4] = !(Byte & 0x01);
|
|
PPU.ClipWindow1Inside[5] = !(Byte & 0x10);
|
|
PPU.ClipWindow2Inside[4] = !(Byte & 0x04);
|
|
PPU.ClipWindow2Inside[5] = !(Byte & 0x40);
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
#ifdef DEBUGGER
|
|
if (Byte & 0x80)
|
|
missing.window2[5] = 1;
|
|
if (Byte & 0x20)
|
|
missing.window1[5] = 1;
|
|
if (Byte & 0x08)
|
|
missing.window2[4] = 1;
|
|
if (Byte & 0x02)
|
|
missing.window1[4] = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2126: // WH0
|
|
if (Byte != Memory.FillRAM[0x2126])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.Window1Left = Byte;
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2127: // WH1
|
|
if (Byte != Memory.FillRAM[0x2127])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.Window1Right = Byte;
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2128: // WH2
|
|
if (Byte != Memory.FillRAM[0x2128])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.Window2Left = Byte;
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2129: // WH3
|
|
if (Byte != Memory.FillRAM[0x2129])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.Window2Right = Byte;
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x212a: // WBGLOG
|
|
if (Byte != Memory.FillRAM[0x212a])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.ClipWindowOverlapLogic[0] = (Byte & 0x03);
|
|
PPU.ClipWindowOverlapLogic[1] = (Byte & 0x0c) >> 2;
|
|
PPU.ClipWindowOverlapLogic[2] = (Byte & 0x30) >> 4;
|
|
PPU.ClipWindowOverlapLogic[3] = (Byte & 0xc0) >> 6;
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x212b: // WOBJLOG
|
|
if (Byte != Memory.FillRAM[0x212b])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.ClipWindowOverlapLogic[4] = (Byte & 0x03);
|
|
PPU.ClipWindowOverlapLogic[5] = (Byte & 0x0c) >> 2;
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x212c: // TM
|
|
if (Byte != Memory.FillRAM[0x212c])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x212d: // TS
|
|
if (Byte != Memory.FillRAM[0x212d])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
#ifdef DEBUGGER
|
|
if (Byte & 0x1f)
|
|
missing.subscreen = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x212e: // TMW
|
|
if (Byte != Memory.FillRAM[0x212e])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x212f: // TSW
|
|
if (Byte != Memory.FillRAM[0x212f])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2130: // CGWSEL
|
|
if (Byte != Memory.FillRAM[0x2130])
|
|
{
|
|
FLUSH_REDRAW();
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
#ifdef DEBUGGER
|
|
if ((Byte & 1) && (PPU.BGMode == 3 || PPU.BGMode == 4 || PPU.BGMode == 7))
|
|
missing.direct = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2131: // CGADSUB
|
|
if (Byte != Memory.FillRAM[0x2131])
|
|
{
|
|
FLUSH_REDRAW();
|
|
#ifdef DEBUGGER
|
|
if (Byte & 0x80)
|
|
{
|
|
if (Memory.FillRAM[0x2130] & 0x02)
|
|
missing.subscreen_sub = 1;
|
|
else
|
|
missing.fixed_colour_sub = 1;
|
|
}
|
|
else
|
|
{
|
|
if (Memory.FillRAM[0x2130] & 0x02)
|
|
missing.subscreen_add = 1;
|
|
else
|
|
missing.fixed_colour_add = 1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2132: // COLDATA
|
|
if (Byte != Memory.FillRAM[0x2132])
|
|
{
|
|
FLUSH_REDRAW();
|
|
if (Byte & 0x80)
|
|
PPU.FixedColourBlue = Byte & 0x1f;
|
|
if (Byte & 0x40)
|
|
PPU.FixedColourGreen = Byte & 0x1f;
|
|
if (Byte & 0x20)
|
|
PPU.FixedColourRed = Byte & 0x1f;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2133: // SETINI
|
|
if (Byte != Memory.FillRAM[0x2133])
|
|
{
|
|
if ((Memory.FillRAM[0x2133] ^ Byte) & 8)
|
|
{
|
|
FLUSH_REDRAW();
|
|
IPPU.PseudoHires = Byte & 8;
|
|
}
|
|
|
|
if (Byte & 0x04)
|
|
{
|
|
PPU.ScreenHeight = SNES_HEIGHT_EXTENDED;
|
|
if (IPPU.DoubleHeightPixels)
|
|
IPPU.RenderedScreenHeight = PPU.ScreenHeight << 1;
|
|
else
|
|
IPPU.RenderedScreenHeight = PPU.ScreenHeight;
|
|
#ifdef DEBUGGER
|
|
missing.lines_239 = 1;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
PPU.ScreenHeight = SNES_HEIGHT;
|
|
if (IPPU.DoubleHeightPixels)
|
|
IPPU.RenderedScreenHeight = PPU.ScreenHeight << 1;
|
|
else
|
|
IPPU.RenderedScreenHeight = PPU.ScreenHeight;
|
|
}
|
|
|
|
if ((Memory.FillRAM[0x2133] ^ Byte) & 3)
|
|
{
|
|
FLUSH_REDRAW();
|
|
if ((Memory.FillRAM[0x2133] ^ Byte) & 2)
|
|
IPPU.OBJChanged = TRUE;
|
|
|
|
IPPU.Interlace = Byte & 1;
|
|
IPPU.InterlaceOBJ = Byte & 2;
|
|
}
|
|
#ifdef DEBUGGER
|
|
if (Byte & 0x40)
|
|
missing.mode7_bgmode = 1;
|
|
if (Byte & 0x08)
|
|
missing.pseudo_512 = 1;
|
|
if (Byte & 0x02)
|
|
missing.sprite_double_height = 1;
|
|
if (Byte & 0x01)
|
|
missing.interlace = 1;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2134: // MPYL
|
|
case 0x2135: // MPYM
|
|
case 0x2136: // MPYH
|
|
case 0x2137: // SLHV
|
|
case 0x2138: // OAMDATAREAD
|
|
case 0x2139: // VMDATALREAD
|
|
case 0x213a: // VMDATAHREAD
|
|
case 0x213b: // CGDATAREAD
|
|
case 0x213c: // OPHCT
|
|
case 0x213d: // OPVCT
|
|
case 0x213e: // STAT77
|
|
case 0x213f: // STAT78
|
|
return;
|
|
|
|
case 0x2180: // WMDATA
|
|
if (!CPU.InWRAMDMAorHDMA)
|
|
REGISTER_2180(Byte);
|
|
break;
|
|
|
|
case 0x2181: // WMADDL
|
|
if (!CPU.InWRAMDMAorHDMA)
|
|
{
|
|
PPU.WRAM &= 0x1ff00;
|
|
PPU.WRAM |= Byte;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2182: // WMADDM
|
|
if (!CPU.InWRAMDMAorHDMA)
|
|
{
|
|
PPU.WRAM &= 0x100ff;
|
|
PPU.WRAM |= Byte << 8;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2183: // WMADDH
|
|
if (!CPU.InWRAMDMAorHDMA)
|
|
{
|
|
PPU.WRAM &= 0x0ffff;
|
|
PPU.WRAM |= Byte << 16;
|
|
PPU.WRAM &= 0x1ffff;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Settings.SuperFX && Address >= 0x3000 && Address <= 0x32ff)
|
|
{
|
|
S9xSetSuperFX(Byte, Address);
|
|
return;
|
|
}
|
|
else
|
|
if (Settings.SA1 && Address >= 0x2200)
|
|
{
|
|
if (Address <= 0x23ff)
|
|
S9xSetSA1(Byte, Address);
|
|
else
|
|
Memory.FillRAM[Address] = Byte;
|
|
return;
|
|
}
|
|
else
|
|
if (Settings.BS && Address >= 0x2188 && Address <= 0x219f)
|
|
S9xSetBSXPPU(Byte, Address);
|
|
else
|
|
if (Settings.SRTC && Address == 0x2801)
|
|
S9xSetSRTC(Byte, Address);
|
|
#ifdef DEBUGGER
|
|
else
|
|
{
|
|
missing.unknownppu_write = Address;
|
|
if (Settings.TraceUnknownRegisters)
|
|
{
|
|
sprintf(String, "Unknown register write: $%02X->$%04X\n", Byte, Address);
|
|
S9xMessage(S9X_TRACE, S9X_PPU_TRACE, String);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Memory.FillRAM[Address] = Byte;
|
|
}
|
|
|
|
uint8 S9xGetPPU (uint16 Address)
|
|
{
|
|
// MAP_PPU: $2000-$3FFF
|
|
if (Settings.MSU1 && (Address & 0xfff8) == 0x2000)
|
|
return (S9xMSU1ReadPort(Address & 7));
|
|
else
|
|
if (Address < 0x2100)
|
|
return (OpenBus);
|
|
|
|
if (CPU.InDMAorHDMA)
|
|
{
|
|
if (CPU.CurrentDMAorHDMAChannel >= 0 && !DMA[CPU.CurrentDMAorHDMAChannel].ReverseTransfer)
|
|
{
|
|
// S9xGetPPU() is called to read from DMA[].AAddress
|
|
if ((Address & 0xff00) == 0x2100)
|
|
// Cannot access to Address Bus B ($2100-$21FF) via (H)DMA
|
|
return (OpenBus);
|
|
else
|
|
// $2200-$3FFF are connected to Address Bus A
|
|
// SA1, SuperFX and SRTC are mapped here
|
|
// I don't bother for now...
|
|
return (OpenBus);
|
|
}
|
|
else
|
|
{
|
|
// S9xGetPPU() is called to write to $21xx
|
|
// Take care of DMA wrapping
|
|
if (Address > 0x21ff)
|
|
Address = 0x2100 + (Address & 0xff);
|
|
}
|
|
}
|
|
|
|
if ((Address & 0xffc0) == 0x2140) // APUIO0, APUIO1, APUIO2, APUIO3
|
|
// read_port will run the APU until given APU time before reading value
|
|
return (S9xAPUReadPort(Address & 3));
|
|
else
|
|
if (Address <= 0x2183)
|
|
{
|
|
uint8 byte;
|
|
|
|
switch (Address)
|
|
{
|
|
case 0x2104: // OAMDATA
|
|
case 0x2105: // BGMODE
|
|
case 0x2106: // MOSAIC
|
|
case 0x2108: // BG2SC
|
|
case 0x2109: // BG3SC
|
|
case 0x210a: // BG4SC
|
|
case 0x2114: // BG4VOFS
|
|
case 0x2115: // VMAIN
|
|
case 0x2116: // VMADDL
|
|
case 0x2118: // VMDATAL
|
|
case 0x2119: // VMDATAH
|
|
case 0x211a: // M7SEL
|
|
case 0x2124: // W34SEL
|
|
case 0x2125: // WOBJSEL
|
|
case 0x2126: // WH0
|
|
case 0x2128: // WH2
|
|
case 0x2129: // WH3
|
|
case 0x212a: // WBGLOG
|
|
return (PPU.OpenBus1);
|
|
|
|
case 0x2134: // MPYL
|
|
case 0x2135: // MPYM
|
|
case 0x2136: // MPYH
|
|
if (PPU.Need16x8Mulitply)
|
|
{
|
|
int32 r = (int32) PPU.MatrixA * (int32) (PPU.MatrixB >> 8);
|
|
Memory.FillRAM[0x2134] = (uint8) r;
|
|
Memory.FillRAM[0x2135] = (uint8) (r >> 8);
|
|
Memory.FillRAM[0x2136] = (uint8) (r >> 16);
|
|
PPU.Need16x8Mulitply = FALSE;
|
|
}
|
|
#ifdef DEBUGGER
|
|
missing.matrix_multiply = 1;
|
|
#endif
|
|
return (PPU.OpenBus1 = Memory.FillRAM[Address]);
|
|
|
|
case 0x2137: // SLHV
|
|
S9xLatchCounters(0);
|
|
return (PPU.OpenBus1);
|
|
|
|
case 0x2138: // OAMDATAREAD
|
|
if (PPU.OAMAddr & 0x100)
|
|
{
|
|
if (!(PPU.OAMFlip & 1))
|
|
byte = PPU.OAMData[(PPU.OAMAddr & 0x10f) << 1];
|
|
else
|
|
{
|
|
byte = PPU.OAMData[((PPU.OAMAddr & 0x10f) << 1) + 1];
|
|
PPU.OAMAddr = (PPU.OAMAddr + 1) & 0x1ff;
|
|
if (PPU.OAMPriorityRotation && PPU.FirstSprite != (PPU.OAMAddr >> 1))
|
|
{
|
|
PPU.FirstSprite = (PPU.OAMAddr & 0xfe) >> 1;
|
|
IPPU.OBJChanged = TRUE;
|
|
#ifdef DEBUGGER
|
|
missing.sprite_priority_rotation = 1;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(PPU.OAMFlip & 1))
|
|
byte = PPU.OAMData[PPU.OAMAddr << 1];
|
|
else
|
|
{
|
|
byte = PPU.OAMData[(PPU.OAMAddr << 1) + 1];
|
|
++PPU.OAMAddr;
|
|
if (PPU.OAMPriorityRotation && PPU.FirstSprite != (PPU.OAMAddr >> 1))
|
|
{
|
|
PPU.FirstSprite = (PPU.OAMAddr & 0xfe) >> 1;
|
|
IPPU.OBJChanged = TRUE;
|
|
#ifdef DEBUGGER
|
|
missing.sprite_priority_rotation = 1;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
PPU.OAMFlip ^= 1;
|
|
#ifdef DEBUGGER
|
|
missing.oam_read = 1;
|
|
#endif
|
|
return (PPU.OpenBus1 = byte);
|
|
|
|
case 0x2139: // VMDATALREAD
|
|
byte = PPU.VRAMReadBuffer & 0xff;
|
|
if (!PPU.VMA.High)
|
|
{
|
|
S9xUpdateVRAMReadBuffer();
|
|
|
|
PPU.VMA.Address += PPU.VMA.Increment;
|
|
}
|
|
|
|
#ifdef DEBUGGER
|
|
missing.vram_read = 1;
|
|
#endif
|
|
return (PPU.OpenBus1 = byte);
|
|
|
|
case 0x213a: // VMDATAHREAD
|
|
byte = (PPU.VRAMReadBuffer >> 8) & 0xff;
|
|
if (PPU.VMA.High)
|
|
{
|
|
S9xUpdateVRAMReadBuffer();
|
|
|
|
PPU.VMA.Address += PPU.VMA.Increment;
|
|
}
|
|
#ifdef DEBUGGER
|
|
missing.vram_read = 1;
|
|
#endif
|
|
return (PPU.OpenBus1 = byte);
|
|
|
|
case 0x213b: // CGDATAREAD
|
|
if (PPU.CGFLIPRead)
|
|
byte = (PPU.OpenBus2 & 0x80) | ((PPU.CGDATA[PPU.CGADD++] >> 8) & 0x7f);
|
|
else
|
|
byte = PPU.CGDATA[PPU.CGADD] & 0xff;
|
|
PPU.CGFLIPRead ^= 1;
|
|
#ifdef DEBUGGER
|
|
missing.cgram_read = 1;
|
|
#endif
|
|
return (PPU.OpenBus2 = byte);
|
|
|
|
case 0x213c: // OPHCT
|
|
S9xTryGunLatch(false);
|
|
if (PPU.HBeamFlip)
|
|
byte = (PPU.OpenBus2 & 0xfe) | ((PPU.HBeamPosLatched >> 8) & 0x01);
|
|
else
|
|
byte = (uint8) PPU.HBeamPosLatched;
|
|
PPU.HBeamFlip ^= 1;
|
|
#ifdef DEBUGGER
|
|
missing.h_counter_read = 1;
|
|
#endif
|
|
return (PPU.OpenBus2 = byte);
|
|
|
|
case 0x213d: // OPVCT
|
|
S9xTryGunLatch(false);
|
|
if (PPU.VBeamFlip)
|
|
byte = (PPU.OpenBus2 & 0xfe) | ((PPU.VBeamPosLatched >> 8) & 0x01);
|
|
else
|
|
byte = (uint8) PPU.VBeamPosLatched;
|
|
PPU.VBeamFlip ^= 1;
|
|
#ifdef DEBUGGER
|
|
missing.v_counter_read = 1;
|
|
#endif
|
|
return (PPU.OpenBus2 = byte);
|
|
|
|
case 0x213e: // STAT77
|
|
FLUSH_REDRAW();
|
|
byte = (PPU.OpenBus1 & 0x10) | PPU.RangeTimeOver | Model->_5C77;
|
|
return (PPU.OpenBus1 = byte);
|
|
|
|
case 0x213f: // STAT78
|
|
S9xTryGunLatch(false);
|
|
PPU.VBeamFlip = PPU.HBeamFlip = 0;
|
|
byte = (PPU.OpenBus2 & 0x20) | (Memory.FillRAM[0x213f] & 0xc0) | (Settings.PAL ? 0x10 : 0) | Model->_5C78;
|
|
Memory.FillRAM[0x213f] &= ~0x40;
|
|
return (PPU.OpenBus2 = byte);
|
|
|
|
case 0x2180: // WMDATA
|
|
if (!CPU.InWRAMDMAorHDMA)
|
|
{
|
|
byte = Memory.RAM[PPU.WRAM++];
|
|
PPU.WRAM &= 0x1ffff;
|
|
}
|
|
else
|
|
byte = OpenBus;
|
|
#ifdef DEBUGGER
|
|
missing.wram_read = 1;
|
|
#endif
|
|
return (byte);
|
|
|
|
default:
|
|
return (OpenBus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Settings.SuperFX && Address >= 0x3000 && Address <= 0x32ff)
|
|
return (S9xGetSuperFX(Address));
|
|
else
|
|
if (Settings.SA1 && Address >= 0x2200)
|
|
return (S9xGetSA1(Address));
|
|
else
|
|
if (Settings.BS && Address >= 0x2188 && Address <= 0x219f)
|
|
return (S9xGetBSXPPU(Address));
|
|
else
|
|
if (Settings.SRTC && Address == 0x2800)
|
|
return (S9xGetSRTC(Address));
|
|
else
|
|
switch (Address)
|
|
{
|
|
case 0x21c2:
|
|
if (Model->_5C77 == 2)
|
|
return (0x20);
|
|
return (OpenBus);
|
|
|
|
case 0x21c3:
|
|
if (Model->_5C77 == 2)
|
|
return (0);
|
|
return (OpenBus);
|
|
|
|
default:
|
|
return (OpenBus);
|
|
}
|
|
}
|
|
}
|
|
|
|
void S9xSetCPU (uint8 Byte, uint16 Address)
|
|
{
|
|
if (Address < 0x4200)
|
|
{
|
|
switch (Address)
|
|
{
|
|
case 0x4016: // JOYSER0
|
|
S9xSetJoypadLatch(Byte & 1);
|
|
break;
|
|
|
|
case 0x4017: // JOYSER1
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
if ((Address & 0xff80) == 0x4300)
|
|
{
|
|
if (CPU.InDMAorHDMA)
|
|
return;
|
|
|
|
int d = (Address >> 4) & 0x7;
|
|
|
|
switch (Address & 0xf)
|
|
{
|
|
case 0x0: // 0x43x0: DMAPx
|
|
DMA[d].ReverseTransfer = (Byte & 0x80) ? TRUE : FALSE;
|
|
DMA[d].HDMAIndirectAddressing = (Byte & 0x40) ? TRUE : FALSE;
|
|
DMA[d].UnusedBit43x0 = (Byte & 0x20) ? TRUE : FALSE;
|
|
DMA[d].AAddressDecrement = (Byte & 0x10) ? TRUE : FALSE;
|
|
DMA[d].AAddressFixed = (Byte & 0x08) ? TRUE : FALSE;
|
|
DMA[d].TransferMode = (Byte & 7);
|
|
return;
|
|
|
|
case 0x1: // 0x43x1: BBADx
|
|
DMA[d].BAddress = Byte;
|
|
return;
|
|
|
|
case 0x2: // 0x43x2: A1TxL
|
|
DMA[d].AAddress &= 0xff00;
|
|
DMA[d].AAddress |= Byte;
|
|
return;
|
|
|
|
case 0x3: // 0x43x3: A1TxH
|
|
DMA[d].AAddress &= 0xff;
|
|
DMA[d].AAddress |= Byte << 8;
|
|
return;
|
|
|
|
case 0x4: // 0x43x4: A1Bx
|
|
DMA[d].ABank = Byte;
|
|
HDMAMemPointers[d] = NULL;
|
|
return;
|
|
|
|
case 0x5: // 0x43x5: DASxL
|
|
DMA[d].DMACount_Or_HDMAIndirectAddress &= 0xff00;
|
|
DMA[d].DMACount_Or_HDMAIndirectAddress |= Byte;
|
|
HDMAMemPointers[d] = NULL;
|
|
return;
|
|
|
|
case 0x6: // 0x43x6: DASxH
|
|
DMA[d].DMACount_Or_HDMAIndirectAddress &= 0xff;
|
|
DMA[d].DMACount_Or_HDMAIndirectAddress |= Byte << 8;
|
|
HDMAMemPointers[d] = NULL;
|
|
return;
|
|
|
|
case 0x7: // 0x43x7: DASBx
|
|
DMA[d].IndirectBank = Byte;
|
|
HDMAMemPointers[d] = NULL;
|
|
return;
|
|
|
|
case 0x8: // 0x43x8: A2AxL
|
|
DMA[d].Address &= 0xff00;
|
|
DMA[d].Address |= Byte;
|
|
HDMAMemPointers[d] = NULL;
|
|
return;
|
|
|
|
case 0x9: // 0x43x9: A2AxH
|
|
DMA[d].Address &= 0xff;
|
|
DMA[d].Address |= Byte << 8;
|
|
HDMAMemPointers[d] = NULL;
|
|
return;
|
|
|
|
case 0xa: // 0x43xa: NLTRx
|
|
if (Byte & 0x7f)
|
|
{
|
|
DMA[d].LineCount = Byte & 0x7f;
|
|
DMA[d].Repeat = !(Byte & 0x80);
|
|
}
|
|
else
|
|
{
|
|
DMA[d].LineCount = 128;
|
|
DMA[d].Repeat = !!(Byte & 0x80);
|
|
}
|
|
|
|
return;
|
|
|
|
case 0xb: // 0x43xb: ????x
|
|
case 0xf: // 0x43xf: mirror of 0x43xb
|
|
DMA[d].UnknownByte = Byte;
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint16 pos;
|
|
|
|
switch (Address)
|
|
{
|
|
case 0x4200: // NMITIMEN
|
|
#ifdef DEBUGGER
|
|
if (Settings.TraceHCEvent)
|
|
S9xTraceFormattedMessage("Write to 0x4200. Byte is %2x was %2x\n", Byte, Memory.FillRAM[Address]);
|
|
#endif
|
|
|
|
if (Byte == Memory.FillRAM[0x4200])
|
|
break;
|
|
|
|
if (Byte & 0x20)
|
|
{
|
|
PPU.VTimerEnabled = TRUE;
|
|
|
|
#ifdef DEBUGGER
|
|
missing.virq = 1;
|
|
missing.virq_pos = PPU.IRQVBeamPos;
|
|
#endif
|
|
}
|
|
else
|
|
PPU.VTimerEnabled = FALSE;
|
|
|
|
if (Byte & 0x10)
|
|
{
|
|
PPU.HTimerEnabled = TRUE;
|
|
|
|
#ifdef DEBUGGER
|
|
missing.hirq = 1;
|
|
missing.hirq_pos = PPU.IRQHBeamPos;
|
|
#endif
|
|
}
|
|
else
|
|
PPU.HTimerEnabled = FALSE;
|
|
|
|
if (!(Byte & 0x10) && !(Byte & 0x20))
|
|
{
|
|
CPU.IRQLine = FALSE;
|
|
CPU.IRQTransition = FALSE;
|
|
}
|
|
|
|
if ((Byte & 0x30) != (Memory.FillRAM[0x4200] & 0x30))
|
|
{
|
|
// Only allow instantaneous IRQ if turning it completely on or off
|
|
if ((Byte & 0x30) == 0 || (Memory.FillRAM[0x4200] & 0x30) == 0)
|
|
S9xUpdateIRQPositions(true);
|
|
else
|
|
S9xUpdateIRQPositions(false);
|
|
}
|
|
|
|
// NMI can trigger immediately during VBlank as long as NMI_read ($4210) wasn't cleard.
|
|
if ((Byte & 0x80) && !(Memory.FillRAM[0x4200] & 0x80) &&
|
|
(CPU.V_Counter >= PPU.ScreenHeight + FIRST_VISIBLE_LINE) && (Memory.FillRAM[0x4210] & 0x80))
|
|
{
|
|
// FIXME: triggered at HC+=6, checked just before the final CPU cycle,
|
|
// then, when to call S9xOpcode_NMI()?
|
|
Timings.IRQFlagChanging |= IRQ_TRIGGER_NMI;
|
|
|
|
#ifdef DEBUGGER
|
|
if (Settings.TraceHCEvent)
|
|
S9xTraceFormattedMessage("NMI Triggered on low-to-high occurring at next HC=%d\n", Timings.NMITriggerPos);
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUGGER
|
|
S9xTraceFormattedMessage("--- IRQ Timer Enable HTimer:%d Pos:%04d VTimer:%d Pos:%03d",
|
|
PPU.HTimerEnabled, PPU.HTimerPosition, PPU.VTimerEnabled, PPU.VTimerPosition);
|
|
#endif
|
|
|
|
break;
|
|
|
|
case 0x4201: // WRIO
|
|
if ((Byte & 0x80) == 0 && (Memory.FillRAM[0x4213] & 0x80) == 0x80)
|
|
S9xLatchCounters(1);
|
|
else
|
|
S9xTryGunLatch((Byte & 0x80) ? true : false);
|
|
Memory.FillRAM[0x4201] = Memory.FillRAM[0x4213] = Byte;
|
|
break;
|
|
|
|
case 0x4202: // WRMPYA
|
|
break;
|
|
|
|
case 0x4203: // WRMPYB
|
|
{
|
|
uint32 res = Memory.FillRAM[0x4202] * Byte;
|
|
// FIXME: The update occurs 8 machine cycles after $4203 is set.
|
|
Memory.FillRAM[0x4216] = (uint8) res;
|
|
Memory.FillRAM[0x4217] = (uint8) (res >> 8);
|
|
break;
|
|
}
|
|
|
|
case 0x4204: // WRDIVL
|
|
case 0x4205: // WRDIVH
|
|
break;
|
|
|
|
case 0x4206: // WRDIVB
|
|
{
|
|
uint16 a = Memory.FillRAM[0x4204] + (Memory.FillRAM[0x4205] << 8);
|
|
uint16 div = Byte ? a / Byte : 0xffff;
|
|
uint16 rem = Byte ? a % Byte : a;
|
|
// FIXME: The update occurs 16 machine cycles after $4206 is set.
|
|
Memory.FillRAM[0x4214] = (uint8) div;
|
|
Memory.FillRAM[0x4215] = div >> 8;
|
|
Memory.FillRAM[0x4216] = (uint8) rem;
|
|
Memory.FillRAM[0x4217] = rem >> 8;
|
|
break;
|
|
}
|
|
|
|
case 0x4207: // HTIMEL
|
|
pos = PPU.IRQHBeamPos;
|
|
PPU.IRQHBeamPos = (PPU.IRQHBeamPos & 0xff00) | Byte;
|
|
if (PPU.IRQHBeamPos != pos)
|
|
S9xUpdateIRQPositions(false);
|
|
#ifdef DEBUGGER
|
|
missing.hirq_pos = PPU.IRQHBeamPos;
|
|
#endif
|
|
break;
|
|
|
|
case 0x4208: // HTIMEH
|
|
pos = PPU.IRQHBeamPos;
|
|
PPU.IRQHBeamPos = (PPU.IRQHBeamPos & 0xff) | ((Byte & 1) << 8);
|
|
if (PPU.IRQHBeamPos != pos)
|
|
S9xUpdateIRQPositions(false);
|
|
#ifdef DEBUGGER
|
|
missing.hirq_pos = PPU.IRQHBeamPos;
|
|
#endif
|
|
break;
|
|
|
|
case 0x4209: // VTIMEL
|
|
pos = PPU.IRQVBeamPos;
|
|
PPU.IRQVBeamPos = (PPU.IRQVBeamPos & 0xff00) | Byte;
|
|
if (PPU.IRQVBeamPos != pos)
|
|
S9xUpdateIRQPositions(true);
|
|
#ifdef DEBUGGER
|
|
missing.virq_pos = PPU.IRQVBeamPos;
|
|
#endif
|
|
break;
|
|
|
|
case 0x420a: // VTIMEH
|
|
pos = PPU.IRQVBeamPos;
|
|
PPU.IRQVBeamPos = (PPU.IRQVBeamPos & 0xff) | ((Byte & 1) << 8);
|
|
if (PPU.IRQVBeamPos != pos)
|
|
S9xUpdateIRQPositions(true);
|
|
#ifdef DEBUGGER
|
|
missing.virq_pos = PPU.IRQVBeamPos;
|
|
#endif
|
|
break;
|
|
|
|
case 0x420b: // MDMAEN
|
|
if (CPU.InDMAorHDMA)
|
|
return;
|
|
// XXX: Not quite right...
|
|
if (Byte) {
|
|
CPU.Cycles += Timings.DMACPUSync;
|
|
}
|
|
if (Byte & 0x01)
|
|
S9xDoDMA(0);
|
|
if (Byte & 0x02)
|
|
S9xDoDMA(1);
|
|
if (Byte & 0x04)
|
|
S9xDoDMA(2);
|
|
if (Byte & 0x08)
|
|
S9xDoDMA(3);
|
|
if (Byte & 0x10)
|
|
S9xDoDMA(4);
|
|
if (Byte & 0x20)
|
|
S9xDoDMA(5);
|
|
if (Byte & 0x40)
|
|
S9xDoDMA(6);
|
|
if (Byte & 0x80)
|
|
S9xDoDMA(7);
|
|
#ifdef DEBUGGER
|
|
missing.dma_this_frame = Byte;
|
|
missing.dma_channels = Byte;
|
|
#endif
|
|
break;
|
|
|
|
case 0x420c: // HDMAEN
|
|
if (CPU.InDMAorHDMA)
|
|
return;
|
|
Memory.FillRAM[0x420c] = Byte;
|
|
// Yoshi's Island, Genjyu Ryodan, Mortal Kombat, Tales of Phantasia
|
|
PPU.HDMA = Byte & ~PPU.HDMAEnded;
|
|
#ifdef DEBUGGER
|
|
missing.hdma_this_frame |= Byte;
|
|
missing.hdma_channels |= Byte;
|
|
#endif
|
|
break;
|
|
|
|
case 0x420d: // MEMSEL
|
|
if ((Byte & 1) != (Memory.FillRAM[0x420d] & 1))
|
|
{
|
|
if (Byte & 1)
|
|
{
|
|
CPU.FastROMSpeed = ONE_CYCLE;
|
|
#ifdef DEBUGGER
|
|
missing.fast_rom = 1;
|
|
#endif
|
|
}
|
|
else
|
|
CPU.FastROMSpeed = SLOW_ONE_CYCLE;
|
|
// we might currently be in FastROMSpeed region, S9xSetPCBase will update CPU.MemSpeed
|
|
S9xSetPCBase(Registers.PBPC);
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x4210: // RDNMI
|
|
case 0x4211: // TIMEUP
|
|
case 0x4212: // HVBJOY
|
|
case 0x4213: // RDIO
|
|
case 0x4214: // RDDIVL
|
|
case 0x4215: // RDDIVH
|
|
case 0x4216: // RDMPYL
|
|
case 0x4217: // RDMPYH
|
|
case 0x4218: // JOY1L
|
|
case 0x4219: // JOY1H
|
|
case 0x421a: // JOY2L
|
|
case 0x421b: // JOY2H
|
|
case 0x421c: // JOY3L
|
|
case 0x421d: // JOY3H
|
|
case 0x421e: // JOY4L
|
|
case 0x421f: // JOY4H
|
|
return;
|
|
|
|
default:
|
|
if (Settings.SPC7110 && Address >= 0x4800)
|
|
S9xSetSPC7110(Byte, Address);
|
|
else
|
|
if (Settings.SDD1 && Address >= 0x4804 && Address <= 0x4807)
|
|
S9xSetSDD1MemoryMap(Address - 0x4804, Byte & 7);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Memory.FillRAM[Address] = Byte;
|
|
}
|
|
|
|
uint8 S9xGetCPU (uint16 Address)
|
|
{
|
|
if (Address < 0x4200)
|
|
{
|
|
#ifdef SNES_JOY_READ_CALLBACKS
|
|
extern bool8 pad_read;
|
|
if (Address == 0x4016 || Address == 0x4017)
|
|
{
|
|
S9xOnSNESPadRead();
|
|
pad_read = TRUE;
|
|
}
|
|
#endif
|
|
|
|
switch (Address)
|
|
{
|
|
case 0x4016: // JOYSER0
|
|
case 0x4017: // JOYSER1
|
|
return (S9xReadJOYSERn(Address));
|
|
|
|
default:
|
|
return (OpenBus);
|
|
}
|
|
}
|
|
else
|
|
if ((Address & 0xff80) == 0x4300)
|
|
{
|
|
if (CPU.InDMAorHDMA)
|
|
return (OpenBus);
|
|
|
|
int d = (Address >> 4) & 0x7;
|
|
|
|
switch (Address & 0xf)
|
|
{
|
|
case 0x0: // 0x43x0: DMAPx
|
|
return ((DMA[d].ReverseTransfer ? 0x80 : 0) |
|
|
(DMA[d].HDMAIndirectAddressing ? 0x40 : 0) |
|
|
(DMA[d].UnusedBit43x0 ? 0x20 : 0) |
|
|
(DMA[d].AAddressDecrement ? 0x10 : 0) |
|
|
(DMA[d].AAddressFixed ? 0x08 : 0) |
|
|
(DMA[d].TransferMode & 7));
|
|
|
|
case 0x1: // 0x43x1: BBADx
|
|
return (DMA[d].BAddress);
|
|
|
|
case 0x2: // 0x43x2: A1TxL
|
|
return (DMA[d].AAddress & 0xff);
|
|
|
|
case 0x3: // 0x43x3: A1TxH
|
|
return (DMA[d].AAddress >> 8);
|
|
|
|
case 0x4: // 0x43x4: A1Bx
|
|
return (DMA[d].ABank);
|
|
|
|
case 0x5: // 0x43x5: DASxL
|
|
return (DMA[d].DMACount_Or_HDMAIndirectAddress & 0xff);
|
|
|
|
case 0x6: // 0x43x6: DASxH
|
|
return (DMA[d].DMACount_Or_HDMAIndirectAddress >> 8);
|
|
|
|
case 0x7: // 0x43x7: DASBx
|
|
return (DMA[d].IndirectBank);
|
|
|
|
case 0x8: // 0x43x8: A2AxL
|
|
return (DMA[d].Address & 0xff);
|
|
|
|
case 0x9: // 0x43x9: A2AxH
|
|
return (DMA[d].Address >> 8);
|
|
|
|
case 0xa: // 0x43xa: NLTRx
|
|
return (DMA[d].LineCount ^ (DMA[d].Repeat ? 0x00 : 0x80));
|
|
|
|
case 0xb: // 0x43xb: ????x
|
|
case 0xf: // 0x43xf: mirror of 0x43xb
|
|
return (DMA[d].UnknownByte);
|
|
|
|
default:
|
|
return (OpenBus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint8 byte;
|
|
|
|
switch (Address)
|
|
{
|
|
case 0x4210: // RDNMI
|
|
byte = Memory.FillRAM[0x4210];
|
|
Memory.FillRAM[0x4210] = Model->_5A22;
|
|
return ((byte & 0x80) | (OpenBus & 0x70) | Model->_5A22);
|
|
|
|
case 0x4211: // TIMEUP
|
|
// The main loop only latches CPU.IRQLine at opcode boundaries, so a $4211 read landing after the
|
|
// H/V-timer trigger cycle but before that boundary would miss the IRQ; latch it here as hardware would
|
|
// (Traverse: Starlight & Prairie polls $4210 as a 16-bit read and waits on this flag in bit 15).
|
|
if (!CPU.IRQLine && Timings.NextIRQTimer != 0x0fffffff &&
|
|
CPU.Cycles >= Timings.NextIRQTimer)
|
|
{
|
|
S9xUpdateIRQPositions(false);
|
|
CPU.IRQLine = TRUE;
|
|
}
|
|
byte = 0;
|
|
if (CPU.IRQLine)
|
|
{
|
|
byte = 0x80;
|
|
CPU.IRQLine = FALSE;
|
|
CPU.IRQTransition = FALSE;
|
|
}
|
|
|
|
return (byte | (OpenBus & 0x7f));
|
|
|
|
case 0x4212: // HVBJOY
|
|
return (REGISTER_4212() | (OpenBus & 0x3e));
|
|
|
|
case 0x4213: // RDIO
|
|
return (Memory.FillRAM[0x4213]);
|
|
|
|
case 0x4214: // RDDIVL
|
|
case 0x4215: // RDDIVH
|
|
case 0x4216: // RDMPYL
|
|
case 0x4217: // RDMPYH
|
|
return (Memory.FillRAM[Address]);
|
|
|
|
case 0x4218: // JOY1L
|
|
case 0x4219: // JOY1H
|
|
case 0x421a: // JOY2L
|
|
case 0x421b: // JOY2H
|
|
case 0x421c: // JOY3L
|
|
case 0x421d: // JOY3H
|
|
case 0x421e: // JOY4L
|
|
case 0x421f: // JOY4H
|
|
#ifdef SNES_JOY_READ_CALLBACKS
|
|
extern bool8 pad_read;
|
|
if (Memory.FillRAM[0x4200] & 1)
|
|
{
|
|
S9xOnSNESPadRead();
|
|
pad_read = TRUE;
|
|
}
|
|
#endif
|
|
return (Memory.FillRAM[Address]);
|
|
|
|
default:
|
|
if (Settings.SPC7110 && Address >= 0x4800)
|
|
return (S9xGetSPC7110(Address));
|
|
if (Settings.SDD1 && Address >= 0x4800 && Address <= 0x4807)
|
|
return (Memory.FillRAM[Address]);
|
|
return (OpenBus);
|
|
}
|
|
}
|
|
}
|
|
|
|
void S9xResetPPU (void)
|
|
{
|
|
S9xSoftResetPPU();
|
|
S9xControlsReset();
|
|
PPU.M7HOFS = 0;
|
|
PPU.M7VOFS = 0;
|
|
PPU.M7byte = 0;
|
|
}
|
|
|
|
void S9xResetPPUFast (void)
|
|
{
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
IPPU.ColorsChanged = TRUE;
|
|
IPPU.OBJChanged = TRUE;
|
|
memset(IPPU.TileCached[TILE_2BIT], 0, MAX_2BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_4BIT], 0, MAX_4BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_8BIT], 0, MAX_8BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_2BIT_EVEN], 0, MAX_2BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_2BIT_ODD], 0, MAX_2BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_4BIT_EVEN], 0, MAX_4BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_4BIT_ODD], 0, MAX_4BIT_TILES);
|
|
}
|
|
|
|
void S9xMode7VertResample (void)
|
|
{
|
|
int32 y, m7_start;
|
|
uint32 ppl;
|
|
uint32 width;
|
|
|
|
if (IPPU.M7VertStartY < 0)
|
|
return;
|
|
|
|
/* (snes9x2010 aborts its software-framebuffer hook here; this port
|
|
has no such hook -- GFX.Screen is always the persistent buffer,
|
|
sized MAX_SNES_WIDTH_4X x (MAX_SNES_HEIGHT + 64).) */
|
|
m7_start = IPPU.M7VertStartY;
|
|
ppl = GFX.PPL;
|
|
width = IPPU.RenderedScreenWidth;
|
|
|
|
/* An interlaced frame is already two fields tall: the renderer writes
|
|
PPU.ScreenHeight rows at twice the normal stride, so the frame
|
|
already occupies 2 * PPU.ScreenHeight physical rows. Doubling again
|
|
here would need four times the height - 956 rows at 239-line
|
|
overscan against the 510 the buffer holds - and the bottom-up walk
|
|
writes those rows before anything notices, roughly 850 KB past the
|
|
end of GFXScreenBuffer and straight through whatever the allocator
|
|
put after it. Interlace already provides the vertical resolution
|
|
this pass exists to synthesise, so skip it. */
|
|
if (ppl != (uint32) GFX.RealPPL)
|
|
return;
|
|
|
|
/* Backstop for any future path that arms the pass with a taller frame
|
|
or a wider stride: refuse rather than write past the buffer. The
|
|
usable region below GFX.Screen is (MAX_SNES_HEIGHT + 32) rows of
|
|
RealPPL, the 64 rows of slack in GFXScreenBuffer less the 32-row
|
|
offset GFX.Screen sits at. */
|
|
{
|
|
uint32 max_rows = ((uint32) GFX.RealPPL * (MAX_SNES_HEIGHT + 32)) / ppl;
|
|
|
|
if ((uint32) PPU.ScreenHeight * 2 > max_rows)
|
|
return;
|
|
}
|
|
|
|
/* Bottom-up walk over the original PPU.ScreenHeight rows. */
|
|
for (y = (int32)PPU.ScreenHeight - 1; y >= 0; y--)
|
|
{
|
|
uint16 *src = GFX.Screen + (uint32)y * ppl;
|
|
uint16 *dst_even = GFX.Screen + (uint32)(2 * y ) * ppl;
|
|
uint16 *dst_odd = GFX.Screen + (uint32)(2 * y + 1) * ppl;
|
|
|
|
if (y >= m7_start && y + 1 < (int32)PPU.ScreenHeight)
|
|
{
|
|
/* M7 plane: bilinear-Y. dst_even = src; dst_odd =
|
|
* (src + src_below) / 2 per channel. RGB565 is unpacked
|
|
* with a fast trick: average packed values by taking the
|
|
* low bits separately to avoid cross-channel carry. */
|
|
uint16 *src_below = GFX.Screen + (uint32)(y + 1) * ppl;
|
|
uint32 x;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
uint16 a = src[x];
|
|
uint16 b = src_below[x];
|
|
/* Blend each channel of RGB565 independently
|
|
* with the same overflow-safe, floor-exact
|
|
* halving-add identity used by the tile
|
|
* compositor (src/tile.c tile_color_add_half)
|
|
* and the pseudo-hires post-pass in libretro.c:
|
|
* ((a & 0xF7DE) >> 1) + ((b & 0xF7DE) >> 1)
|
|
* + (a & b & 0x0821)
|
|
* The final term restores the per-channel low
|
|
* bit that both operands share, making the
|
|
* result bit-exact to per-channel
|
|
* floor((ca + cb) / 2). Without it the red and
|
|
* blue LSBs (bits 11 and 0; green's field LSB is
|
|
* always 0 here) are dropped whenever both source
|
|
* pixels are odd, biasing interpolated Mode 7
|
|
* scanlines one level dark. */
|
|
uint16 blend = ((a & 0xF7DE) >> 1) + ((b & 0xF7DE) >> 1)
|
|
+ (a & b & 0x0821);
|
|
dst_odd[x] = blend;
|
|
}
|
|
memmove(dst_even, src, width * sizeof(uint16));
|
|
}
|
|
else
|
|
{
|
|
/* HUD region or last M7 row: row replication. memcpy is
|
|
* safe for dst_odd (no overlap with src for y >= 1; for
|
|
* y = 0 dst_odd row 1 doesn't overlap src row 0). memmove
|
|
* for dst_even because dst_even == src when y == 0. */
|
|
memcpy (dst_odd, src, width * sizeof(uint16));
|
|
memmove(dst_even, src, width * sizeof(uint16));
|
|
}
|
|
}
|
|
|
|
IPPU.RenderedScreenHeight = PPU.ScreenHeight * 2;
|
|
}
|
|
|
|
void S9xSoftResetPPU (void)
|
|
{
|
|
S9xControlsSoftReset();
|
|
|
|
PPU.VMA.High = 0;
|
|
PPU.VMA.Increment = 1;
|
|
PPU.VMA.Address = 0;
|
|
PPU.VMA.FullGraphicCount = 0;
|
|
PPU.VMA.Shift = 0;
|
|
|
|
PPU.WRAM = 0;
|
|
|
|
for (int c = 0; c < 4; c++)
|
|
{
|
|
PPU.BG[c].SCBase = 0;
|
|
PPU.BG[c].HOffset = 0;
|
|
PPU.BG[c].VOffset = 0;
|
|
PPU.BG[c].BGSize = 0;
|
|
PPU.BG[c].NameBase = 0;
|
|
PPU.BG[c].SCSize = 0;
|
|
}
|
|
|
|
PPU.BGMode = 0;
|
|
PPU.BG3Priority = 0;
|
|
|
|
PPU.CGFLIP = 0;
|
|
PPU.CGFLIPRead = 0;
|
|
PPU.CGADD = 0;
|
|
|
|
for (int c = 0; c < 256; c++)
|
|
{
|
|
IPPU.Red[c] = (c & 7) << 2;
|
|
IPPU.Green[c] = ((c >> 3) & 7) << 2;
|
|
IPPU.Blue[c] = ((c >> 6) & 2) << 3;
|
|
PPU.CGDATA[c] = IPPU.Red[c] | (IPPU.Green[c] << 5) | (IPPU.Blue[c] << 10);
|
|
}
|
|
|
|
for (int c = 0; c < 128; c++)
|
|
{
|
|
PPU.OBJ[c].HPos = 0;
|
|
PPU.OBJ[c].VPos = 0;
|
|
PPU.OBJ[c].HFlip = 0;
|
|
PPU.OBJ[c].VFlip = 0;
|
|
PPU.OBJ[c].Name = 0;
|
|
PPU.OBJ[c].Priority = 0;
|
|
PPU.OBJ[c].Palette = 0;
|
|
PPU.OBJ[c].Size = 0;
|
|
}
|
|
|
|
PPU.OBJThroughMain = FALSE;
|
|
PPU.OBJThroughSub = FALSE;
|
|
PPU.OBJAddition = FALSE;
|
|
PPU.OBJNameBase = 0;
|
|
PPU.OBJNameSelect = 0;
|
|
PPU.OBJSizeSelect = 0;
|
|
|
|
PPU.OAMAddr = 0;
|
|
PPU.SavedOAMAddr = 0;
|
|
PPU.OAMPriorityRotation = 0;
|
|
PPU.OAMFlip = 0;
|
|
PPU.OAMReadFlip = 0;
|
|
PPU.OAMTileAddress = 0;
|
|
PPU.OAMWriteRegister = 0;
|
|
memset(PPU.OAMData, 0, 512 + 32);
|
|
|
|
PPU.FirstSprite = 0;
|
|
PPU.LastSprite = 127;
|
|
PPU.RangeTimeOver = 0;
|
|
|
|
PPU.HTimerEnabled = FALSE;
|
|
PPU.VTimerEnabled = FALSE;
|
|
PPU.HTimerPosition = Timings.H_Max + 1;
|
|
PPU.VTimerPosition = Timings.V_Max + 1;
|
|
PPU.IRQHBeamPos = 0x1ff;
|
|
PPU.IRQVBeamPos = 0x1ff;
|
|
|
|
PPU.HBeamFlip = 0;
|
|
PPU.VBeamFlip = 0;
|
|
PPU.HBeamPosLatched = 0;
|
|
PPU.VBeamPosLatched = 0;
|
|
PPU.GunHLatch = 0;
|
|
PPU.GunVLatch = 1000;
|
|
PPU.HVBeamCounterLatched = 0;
|
|
|
|
PPU.Mode7HFlip = FALSE;
|
|
PPU.Mode7VFlip = FALSE;
|
|
PPU.Mode7Repeat = 0;
|
|
PPU.MatrixA = 0;
|
|
PPU.MatrixB = 0;
|
|
PPU.MatrixC = 0;
|
|
PPU.MatrixD = 0;
|
|
PPU.CentreX = 0;
|
|
PPU.CentreY = 0;
|
|
|
|
PPU.Mosaic = 0;
|
|
PPU.BGMosaic[0] = FALSE;
|
|
PPU.BGMosaic[1] = FALSE;
|
|
PPU.BGMosaic[2] = FALSE;
|
|
PPU.BGMosaic[3] = FALSE;
|
|
|
|
PPU.Window1Left = 1;
|
|
PPU.Window1Right = 0;
|
|
PPU.Window2Left = 1;
|
|
PPU.Window2Right = 0;
|
|
PPU.RecomputeClipWindows = TRUE;
|
|
|
|
for (int c = 0; c < 6; c++)
|
|
{
|
|
PPU.ClipCounts[c] = 0;
|
|
PPU.ClipWindowOverlapLogic[c] = CLIP_OR;
|
|
PPU.ClipWindow1Enable[c] = FALSE;
|
|
PPU.ClipWindow2Enable[c] = FALSE;
|
|
PPU.ClipWindow1Inside[c] = TRUE;
|
|
PPU.ClipWindow2Inside[c] = TRUE;
|
|
}
|
|
|
|
PPU.ForcedBlanking = TRUE;
|
|
|
|
PPU.FixedColourRed = 0;
|
|
PPU.FixedColourGreen = 0;
|
|
PPU.FixedColourBlue = 0;
|
|
PPU.Brightness = 0;
|
|
PPU.ScreenHeight = SNES_HEIGHT;
|
|
|
|
PPU.Need16x8Mulitply = FALSE;
|
|
PPU.BGnxOFSbyte = 0;
|
|
|
|
PPU.HDMA = 0;
|
|
PPU.HDMAEnded = 0;
|
|
|
|
PPU.OpenBus1 = 0;
|
|
PPU.OpenBus2 = 0;
|
|
|
|
for (int c = 0; c < 2; c++)
|
|
memset(&IPPU.Clip[c], 0, sizeof(struct ClipData));
|
|
IPPU.ColorsChanged = TRUE;
|
|
IPPU.OBJChanged = TRUE;
|
|
memset(IPPU.TileCached[TILE_2BIT], 0, MAX_2BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_4BIT], 0, MAX_4BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_8BIT], 0, MAX_8BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_2BIT_EVEN], 0, MAX_2BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_2BIT_ODD], 0, MAX_2BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_4BIT_EVEN], 0, MAX_4BIT_TILES);
|
|
memset(IPPU.TileCached[TILE_4BIT_ODD], 0, MAX_4BIT_TILES);
|
|
PPU.VRAMReadBuffer = 0; // XXX: FIXME: anything better?
|
|
GFX.DoInterlace = 0;
|
|
IPPU.Interlace = FALSE;
|
|
IPPU.InterlaceOBJ = FALSE;
|
|
IPPU.DoubleWidthPixels = FALSE;
|
|
IPPU.DoubleHeightPixels = FALSE;
|
|
IPPU.CurrentLine = 0;
|
|
IPPU.PreviousLine = 0;
|
|
IPPU.XB = NULL;
|
|
for (int c = 0; c < 256; c++)
|
|
IPPU.ScreenColors[c] = c;
|
|
IPPU.MaxBrightness = 0;
|
|
IPPU.RenderThisFrame = TRUE;
|
|
IPPU.RenderedScreenWidth = SNES_WIDTH;
|
|
IPPU.RenderedScreenHeight = SNES_HEIGHT;
|
|
IPPU.FrameCount = 0;
|
|
IPPU.RenderedFramesCount = 0;
|
|
IPPU.DisplayedRenderedFrameCount = 0;
|
|
IPPU.SkippedFrames = 0;
|
|
IPPU.FrameSkip = 0;
|
|
|
|
S9xFixColourBrightness();
|
|
S9xBuildDirectColourMaps();
|
|
|
|
for (int c = 0; c < 0x8000; c += 0x100)
|
|
memset(&Memory.FillRAM[c], c >> 8, 0x100);
|
|
memset(&Memory.FillRAM[0x2100], 0, 0x100);
|
|
memset(&Memory.FillRAM[0x4200], 0, 0x100);
|
|
memset(&Memory.FillRAM[0x4000], 0, 0x100);
|
|
// For BS Suttehakkun 2...
|
|
memset(&Memory.FillRAM[0x1000], 0, 0x1000);
|
|
|
|
Memory.FillRAM[0x4201] = Memory.FillRAM[0x4213] = 0xff;
|
|
Memory.FillRAM[0x2126] = Memory.FillRAM[0x2128] = 1;
|
|
}
|