Files
LibretroAdmin b8176694ec Replace S-RTC and SPC7110 with snes9x2010's, kept as C
srtc.cpp/srtcemu.cpp and spc7110.cpp/spc7110emu.cpp/spc7110dec.cpp are
replaced by snes9x2010's srtc.c and spc7110.c (with their emu/dec
headers), compiled as C.

The decisive win on both is the RTC model: the byuu implementations
here call host time(0) on access, so the emulated clock is
nondeterministic -- movies desync, runahead and netplay diverge, and
two runs of the same inputs disagree. The 2010 versions seed once from
the host at init and then tick one second per emulated second on the
frame clock (S9xSRTCTick / S9xSPC7110RTCTick, wired into
HC_HCOUNTER_MAX_EVENT next to the SuperFX exec), which is the ares
model. The SPC7110 decompressor is the same byuu lineage on both sides
(verified by algorithm markers: context/evolution/probability
structure identical), so decompression accuracy is unchanged, and the
2010 file adds compile-time reverse-Morton lookup tables.

Bridges: SRTCPalFlag/SRTCEnabled, SPC7110Map (= Memory.Map),
SPC7110ROM, SPC7110ROMSize, SPC7110RTCEnabled, OpenBus, and the two
Map sentinels pinned to the memmap.h enum values with a comment. The
S-CPU DMA decompression special case in dma.cpp moves from the C++
s7emu.decomp.read() object to 2010's spc7110_decomp_read()/r4809/
r480a. Savestates stay compatible: SnapSRTCSnap and SnapSPC7110Snap
bind by name into the same-lineage snapshot structs, via the ported
Pre/PostSaveState pair.

Validation titles: Daikaijuu Monogatari II (S-RTC), Far East of Eden
Zero and Momotarou Dentetsu Happy (SPC7110+RTC).

Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings.
2026-08-02 17:15:02 +02:00

1499 lines
35 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 "sdd1.h"
#include "spc7110.h"
#ifdef DEBUGGER
#include "missing.h"
#endif
#define ADD_CYCLES(n) { CPU.Cycles += (n); }
extern uint8 *HDMAMemPointers[8];
extern int HDMA_ModeByteCounts[8];
static uint8 sdd1_decode_buffer[0x10000];
static inline bool8 addCyclesInDMA (uint8);
static inline bool8 HDMAReadLineCount (int);
static inline bool8 addCyclesInDMA (uint8 dma_channel)
{
// Add 8 cycles per byte, sync APU, and do HC related events.
// If HDMA was done in S9xDoHEventProcessing(), check if it used the same channel as DMA.
ADD_CYCLES(SLOW_ONE_CYCLE);
while (CPU.Cycles >= CPU.NextEvent)
S9xDoHEventProcessing();
if (CPU.HDMARanInDMA & (1 << dma_channel))
{
CPU.HDMARanInDMA = 0;
#ifdef DEBUGGER
printf("HDMA and DMA use the same channel %d!\n", dma_channel);
#endif
// If HDMA triggers in the middle of DMA transfer and it uses the same channel,
// it kills the DMA transfer immediately. $43x2 and $43x5 stop updating.
return (FALSE);
}
CPU.HDMARanInDMA = 0;
return (TRUE);
}
bool8 S9xDoDMA (uint8 Channel)
{
CPU.InDMA = TRUE;
CPU.InDMAorHDMA = TRUE;
CPU.CurrentDMAorHDMAChannel = Channel;
SDMA *d = &DMA[Channel];
// Check invalid DMA first
if ((d->ABank == 0x7E || d->ABank == 0x7F) && d->BAddress == 0x80 && !d->ReverseTransfer)
{
// Attempting a DMA from WRAM to $2180 will not work, WRAM will not be written.
// Attempting a DMA from $2180 to WRAM will similarly not work,
// the value written is (initially) the OpenBus value.
// In either case, the address in $2181-3 is not incremented.
// Does an invalid DMA actually take time?
// I'd say yes, since 'invalid' is probably just the WRAM chip
// not being able to read and write itself at the same time
// And no, PPU.WRAM should not be updated.
int32 c = d->TransferBytes;
// Writing $0000 to $43x5 actually results in a transfer of $10000 bytes, not 0.
if (c == 0)
c = 0x10000;
// 8 cycles per channel
ADD_CYCLES(SLOW_ONE_CYCLE);
// 8 cycles per byte
while (c)
{
d->TransferBytes--;
d->AAddress++;
c--;
if (!addCyclesInDMA(Channel))
{
CPU.InDMA = FALSE;
CPU.InDMAorHDMA = FALSE;
CPU.CurrentDMAorHDMAChannel = -1;
return (FALSE);
}
}
#ifdef DEBUGGER
if (Settings.TraceDMA)
{
sprintf(String, "DMA[%d]: WRAM Bank:%02X->$2180", Channel, d->ABank);
S9xMessage(S9X_TRACE, S9X_DMA_TRACE, String);
}
#endif
CPU.InDMA = FALSE;
CPU.InDMAorHDMA = FALSE;
CPU.CurrentDMAorHDMAChannel = -1;
return (TRUE);
}
// Prepare for accessing $2118-2119
switch (d->BAddress)
{
case 0x18:
case 0x19:
if (IPPU.RenderThisFrame)
FLUSH_REDRAW();
break;
}
int32 inc = d->AAddressFixed ? 0 : (!d->AAddressDecrement ? 1 : -1);
int32 count = d->TransferBytes;
// Writing $0000 to $43x5 actually results in a transfer of $10000 bytes, not 0.
if (count == 0)
count = 0x10000;
// Prepare for custom chip DMA
// S-DD1
uint8 *in_sdd1_dma = NULL;
if (Settings.SDD1)
{
if (d->AAddressFixed && Memory.FillRAM[0x4801] > 0)
{
// XXX: Should probably verify that we're DMAing from ROM?
// And somewhere we should make sure we're not running across a mapping boundary too.
// Hacky support for pre-decompressed S-DD1 data
inc = !d->AAddressDecrement ? 1 : -1;
uint8 *in_ptr = S9xGetBasePointer(((d->ABank << 16) | d->AAddress));
if (in_ptr)
{
in_ptr += d->AAddress;
SDD1_decompress(sdd1_decode_buffer, in_ptr, d->TransferBytes);
}
#ifdef DEBUGGER
else
{
sprintf(String, "S-DD1: DMA from non-block address $%02X:%04X", d->ABank, d->AAddress);
S9xMessage(S9X_WARNING, S9X_DMA_TRACE, String);
}
#endif
in_sdd1_dma = sdd1_decode_buffer;
}
Memory.FillRAM[0x4801] = 0;
}
// SPC7110
uint8 *spc7110_dma = NULL;
if (Settings.SPC7110)
{
if (d->AAddress == 0x4800 || d->ABank == 0x50)
{
spc7110_dma = new uint8[d->TransferBytes];
for (int i = 0; i < d->TransferBytes; i++)
spc7110_dma[i] = spc7110_decomp_read();
int32 icount = r4809 | (r480a << 8);
icount -= d->TransferBytes;
r4809 = icount & 0x00ff;
r480a = (icount & 0xff00) >> 8;
inc = 1;
d->AAddress -= count;
}
}
// SA-1
// The SA-1 CC1 character-conversion DMA special case is gone: the
// S-CPU's $40-$4x banks are handler-mapped, so DMA source reads go
// through S9xGetByte and hit S9xSA1ReadCC1 per byte (ares model).
#ifdef DEBUGGER
if (Settings.TraceDMA)
{
sprintf(String, "DMA[%d]: %s Mode:%d 0x%02X%04X->0x21%02X Bytes:%d (%s) V:%03d",
Channel, d->ReverseTransfer ? "PPU->CPU" : "CPU->PPU", d->TransferMode, d->ABank, d->AAddress, d->BAddress,
d->TransferBytes, d->AAddressFixed ? "fixed" : (d->AAddressDecrement ? "dec" : "inc"), CPU.V_Counter);
if (d->BAddress == 0x18 || d->BAddress == 0x19 || d->BAddress == 0x39 || d->BAddress == 0x3a)
sprintf(String, "%s VRAM: %04X (%d,%d) %s", String,
PPU.VMA.Address, PPU.VMA.Increment, PPU.VMA.FullGraphicCount, PPU.VMA.High ? "word" : "byte");
else
if (d->BAddress == 0x22 || d->BAddress == 0x3b)
sprintf(String, "%s CGRAM: %02X (%x)", String, PPU.CGADD, PPU.CGFLIP);
else
if (d->BAddress == 0x04 || d->BAddress == 0x38)
sprintf(String, "%s OBJADDR: %04X", String, PPU.OAMAddr);
S9xMessage(S9X_TRACE, S9X_DMA_TRACE, String);
}
#endif
// Do Transfer
uint8 Work;
// 8 cycles per channel
ADD_CYCLES(SLOW_ONE_CYCLE);
if (!d->ReverseTransfer)
{
// CPU -> PPU
int32 b = 0;
uint16 p = d->AAddress;
uint8 *base = S9xGetBasePointer((d->ABank << 16) + d->AAddress);
bool8 inWRAM_DMA;
int32 rem = count;
// Transfer per block if d->AAdressFixed is FALSE
count = d->AAddressFixed ? rem : (d->AAddressDecrement ? ((p & MEMMAP_MASK) + 1) : (MEMMAP_BLOCK_SIZE - (p & MEMMAP_MASK)));
// Settings for custom chip DMA
if (in_sdd1_dma)
{
base = in_sdd1_dma;
p = 0;
count = rem;
}
else
if (spc7110_dma)
{
base = spc7110_dma;
p = 0;
count = rem;
}
inWRAM_DMA = ((!in_sdd1_dma && !spc7110_dma) &&
(d->ABank == 0x7e || d->ABank == 0x7f || (!(d->ABank & 0x40) && d->AAddress < 0x2000)));
// 8 cycles per byte
#define UPDATE_COUNTERS \
d->TransferBytes--; \
d->AAddress += inc; \
p += inc; \
if (!addCyclesInDMA(Channel)) \
{ \
CPU.InDMA = FALSE; \
CPU.InDMAorHDMA = FALSE; \
CPU.InWRAMDMAorHDMA = FALSE; \
CPU.CurrentDMAorHDMAChannel = -1; \
return (FALSE); \
}
while (1)
{
if (count > rem)
count = rem;
rem -= count;
CPU.InWRAMDMAorHDMA = inWRAM_DMA;
if (!base)
{
// DMA SLOW PATH
if (d->TransferMode == 0 || d->TransferMode == 2 || d->TransferMode == 6)
{
do
{
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
} while (--count > 0);
}
else
if (d->TransferMode == 1 || d->TransferMode == 5)
{
// This is a variation on Duff's Device. It is legal C/C++.
switch (b)
{
default:
while (count > 1)
{
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
count--;
// Fall through
case 1:
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
count--;
}
}
if (count == 1)
{
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
b = 1;
}
else
b = 0;
}
else
if (d->TransferMode == 3 || d->TransferMode == 7)
{
switch (b)
{
default:
do
{
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 1;
break;
}
// Fall through
case 1:
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 2;
break;
}
// Fall through
case 2:
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 3;
break;
}
// Fall through
case 3:
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 0;
break;
}
} while (1);
}
}
else
if (d->TransferMode == 4)
{
switch (b)
{
default:
do
{
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 1;
break;
}
// Fall through
case 1:
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 2;
break;
}
// Fall through
case 2:
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2102 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 3;
break;
}
// Fall through
case 3:
Work = S9xGetByte((d->ABank << 16) + p);
S9xSetPPU(Work, 0x2103 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 0;
break;
}
} while (1);
}
}
#ifdef DEBUGGER
else
{
sprintf(String, "Unknown DMA transfer mode: %d on channel %d\n", d->TransferMode, Channel);
S9xMessage(S9X_TRACE, S9X_DMA_TRACE, String);
}
#endif
}
else
{
// DMA FAST PATH
if (d->TransferMode == 0 || d->TransferMode == 2 || d->TransferMode == 6)
{
switch (d->BAddress)
{
case 0x04: // OAMDATA
do
{
Work = *(base + p);
REGISTER_2104(Work);
UPDATE_COUNTERS;
} while (--count > 0);
break;
case 0x18: // VMDATAL
if (!PPU.VMA.FullGraphicCount)
{
do
{
Work = *(base + p);
REGISTER_2118_linear(Work);
UPDATE_COUNTERS;
} while (--count > 0);
}
else
{
do
{
Work = *(base + p);
REGISTER_2118_tile(Work);
UPDATE_COUNTERS;
} while (--count > 0);
}
break;
case 0x19: // VMDATAH
if (!PPU.VMA.FullGraphicCount)
{
do
{
Work = *(base + p);
REGISTER_2119_linear(Work);
UPDATE_COUNTERS;
} while (--count > 0);
}
else
{
do
{
Work = *(base + p);
REGISTER_2119_tile(Work);
UPDATE_COUNTERS;
} while (--count > 0);
}
break;
case 0x22: // CGDATA
do
{
Work = *(base + p);
REGISTER_2122(Work);
UPDATE_COUNTERS;
} while (--count > 0);
break;
case 0x80: // WMDATA
if (!CPU.InWRAMDMAorHDMA)
{
do
{
Work = *(base + p);
REGISTER_2180(Work);
UPDATE_COUNTERS;
} while (--count > 0);
}
else
{
do
{
UPDATE_COUNTERS;
} while (--count > 0);
}
break;
default:
do
{
Work = *(base + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
} while (--count > 0);
break;
}
}
else
if (d->TransferMode == 1 || d->TransferMode == 5)
{
if (d->BAddress == 0x18)
{
// VMDATAL
if (!PPU.VMA.FullGraphicCount)
{
switch (b)
{
default:
while (count > 1)
{
Work = *(base + p);
REGISTER_2118_linear(Work);
UPDATE_COUNTERS;
count--;
// Fall through
case 1:
OpenBus = *(base + p);
REGISTER_2119_linear(OpenBus);
UPDATE_COUNTERS;
count--;
}
}
if (count == 1)
{
Work = *(base + p);
REGISTER_2118_linear(Work);
UPDATE_COUNTERS;
b = 1;
}
else
b = 0;
}
else
{
switch (b)
{
default:
while (count > 1)
{
Work = *(base + p);
REGISTER_2118_tile(Work);
UPDATE_COUNTERS;
count--;
// Fall through
case 1:
Work = *(base + p);
REGISTER_2119_tile(Work);
UPDATE_COUNTERS;
count--;
}
}
if (count == 1)
{
Work = *(base + p);
REGISTER_2118_tile(Work);
UPDATE_COUNTERS;
b = 1;
}
else
b = 0;
}
}
else
{
// DMA mode 1 general case
switch (b)
{
default:
while (count > 1)
{
Work = *(base + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
count--;
// Fall through
case 1:
Work = *(base + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
count--;
}
}
if (count == 1)
{
Work = *(base + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
b = 1;
}
else
b = 0;
}
}
else
if (d->TransferMode == 3 || d->TransferMode == 7)
{
switch (b)
{
default:
do
{
Work = *(base + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 1;
break;
}
// Fall through
case 1:
Work = *(base + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 2;
break;
}
// Fall through
case 2:
Work = *(base + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 3;
break;
}
// Fall through
case 3:
Work = *(base + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 0;
break;
}
} while (1);
}
}
else
if (d->TransferMode == 4)
{
switch (b)
{
default:
do
{
Work = *(base + p);
S9xSetPPU(Work, 0x2100 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 1;
break;
}
// Fall through
case 1:
Work = *(base + p);
S9xSetPPU(Work, 0x2101 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 2;
break;
}
// Fall through
case 2:
Work = *(base + p);
S9xSetPPU(Work, 0x2102 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 3;
break;
}
// Fall through
case 3:
Work = *(base + p);
S9xSetPPU(Work, 0x2103 + d->BAddress);
UPDATE_COUNTERS;
if (--count <= 0)
{
b = 0;
break;
}
} while (1);
}
}
#ifdef DEBUGGER
else
{
sprintf(String, "Unknown DMA transfer mode: %d on channel %d\n", d->TransferMode, Channel);
S9xMessage(S9X_TRACE, S9X_DMA_TRACE, String);
}
#endif
}
if (rem <= 0)
break;
base = S9xGetBasePointer((d->ABank << 16) + d->AAddress);
count = MEMMAP_BLOCK_SIZE;
inWRAM_DMA = ((!in_sdd1_dma && !spc7110_dma) &&
(d->ABank == 0x7e || d->ABank == 0x7f || (!(d->ABank & 0x40) && d->AAddress < 0x2000)));
}
#undef UPDATE_COUNTERS
}
else
{
// PPU -> CPU
// 8 cycles per byte
#define UPDATE_COUNTERS \
d->TransferBytes--; \
d->AAddress += inc; \
if (!addCyclesInDMA(Channel)) \
{ \
CPU.InDMA = FALSE; \
CPU.InDMAorHDMA = FALSE; \
CPU.InWRAMDMAorHDMA = FALSE; \
CPU.CurrentDMAorHDMAChannel = -1; \
return (FALSE); \
}
if (d->BAddress > 0x80 - 4 && d->BAddress <= 0x83 && !(d->ABank & 0x40))
{
// REVERSE-DMA REALLY-SLOW PATH
do
{
switch (d->TransferMode)
{
case 0:
case 2:
case 6:
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
case 1:
case 5:
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
case 3:
case 7:
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
case 4:
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2102 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
CPU.InWRAMDMAorHDMA = (d->AAddress < 0x2000);
Work = S9xGetPPU(0x2103 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
default:
#ifdef DEBUGGER
sprintf(String, "Unknown DMA transfer mode: %d on channel %d\n", d->TransferMode, Channel);
S9xMessage(S9X_TRACE, S9X_DMA_TRACE, String);
#endif
while (count)
{
UPDATE_COUNTERS;
count--;
}
break;
}
} while (count);
}
else
{
// REVERSE-DMA FASTER PATH
CPU.InWRAMDMAorHDMA = (d->ABank == 0x7e || d->ABank == 0x7f);
do
{
switch (d->TransferMode)
{
case 0:
case 2:
case 6:
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
case 1:
case 5:
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
case 3:
case 7:
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
case 4:
Work = S9xGetPPU(0x2100 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
Work = S9xGetPPU(0x2101 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
Work = S9xGetPPU(0x2102 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
if (!--count)
break;
Work = S9xGetPPU(0x2103 + d->BAddress);
S9xSetByte(Work, (d->ABank << 16) + d->AAddress);
UPDATE_COUNTERS;
count--;
break;
default:
#ifdef DEBUGGER
sprintf(String, "Unknown DMA transfer mode: %d on channel %d\n", d->TransferMode, Channel);
S9xMessage(S9X_TRACE, S9X_DMA_TRACE, String);
#endif
while (count)
{
UPDATE_COUNTERS;
count--;
}
break;
}
} while (count);
}
}
if (CPU.NMIPending && (Timings.NMITriggerPos != 0xffff))
{
Timings.NMITriggerPos = CPU.Cycles + Timings.NMIDMADelay;
}
// Release the memory used in SPC7110 DMA
if (Settings.SPC7110)
{
if (spc7110_dma)
delete [] spc7110_dma;
}
#if 0
// sanity check
if (d->TransferBytes != 0)
fprintf(stderr,"DMA[%d] TransferBytes not 0! $21%02x Reverse:%d %04x\n", Channel, d->BAddress, d->ReverseTransfer, d->TransferBytes);
#endif
CPU.InDMA = FALSE;
CPU.InDMAorHDMA = FALSE;
CPU.InWRAMDMAorHDMA = FALSE;
CPU.CurrentDMAorHDMAChannel = -1;
return (TRUE);
}
static inline bool8 HDMAReadLineCount (int d)
{
// CPU.InDMA is set, so S9xGetXXX() / S9xSetXXX() incur no charges.
uint8 line;
line = S9xGetByte((DMA[d].ABank << 16) + DMA[d].Address);
ADD_CYCLES(SLOW_ONE_CYCLE);
if (!line)
{
DMA[d].Repeat = FALSE;
DMA[d].LineCount = 128;
if (DMA[d].HDMAIndirectAddressing)
{
if (PPU.HDMA & (0xfe << d))
{
DMA[d].Address++;
ADD_CYCLES(SLOW_ONE_CYCLE << 1);
}
else
ADD_CYCLES(SLOW_ONE_CYCLE);
DMA[d].IndirectAddress = S9xGetWord((DMA[d].ABank << 16) + DMA[d].Address);
DMA[d].Address++;
}
DMA[d].Address++;
HDMAMemPointers[d] = NULL;
return (FALSE);
}
else
if (line == 0x80)
{
DMA[d].Repeat = TRUE;
DMA[d].LineCount = 128;
}
else
{
DMA[d].Repeat = !(line & 0x80);
DMA[d].LineCount = line & 0x7f;
}
DMA[d].Address++;
DMA[d].DoTransfer = TRUE;
if (DMA[d].HDMAIndirectAddressing)
{
ADD_CYCLES(SLOW_ONE_CYCLE << 1);
DMA[d].IndirectAddress = S9xGetWord((DMA[d].ABank << 16) + DMA[d].Address);
DMA[d].Address += 2;
HDMAMemPointers[d] = S9xGetMemPointer((DMA[d].IndirectBank << 16) + DMA[d].IndirectAddress);
}
else
HDMAMemPointers[d] = S9xGetMemPointer((DMA[d].ABank << 16) + DMA[d].Address);
return (TRUE);
}
void S9xStartHDMA (void)
{
PPU.HDMA = Memory.FillRAM[0x420c];
#ifdef DEBUGGER
missing.hdma_this_frame = PPU.HDMA;
#endif
PPU.HDMAEnded = 0;
int32 tmpch;
CPU.InHDMA = TRUE;
CPU.InDMAorHDMA = TRUE;
tmpch = CPU.CurrentDMAorHDMAChannel;
// XXX: Not quite right...
if (PPU.HDMA != 0)
ADD_CYCLES(Timings.DMACPUSync);
for (uint8 i = 0; i < 8; i++)
{
if (PPU.HDMA & (1 << i))
{
CPU.CurrentDMAorHDMAChannel = i;
DMA[i].Address = DMA[i].AAddress;
if (!HDMAReadLineCount(i))
{
PPU.HDMA &= ~(1 << i);
PPU.HDMAEnded |= (1 << i);
}
}
else
DMA[i].DoTransfer = FALSE;
}
CPU.InHDMA = FALSE;
CPU.InDMAorHDMA = CPU.InDMA;
CPU.HDMARanInDMA = CPU.InDMA ? PPU.HDMA : 0;
CPU.CurrentDMAorHDMAChannel = tmpch;
}
uint8 S9xDoHDMA (uint8 byte)
{
struct SDMA *p;
uint32 ShiftedIBank;
uint16 IAddr;
bool8 temp;
int32 tmpch;
int d;
uint8 mask;
CPU.InHDMA = TRUE;
CPU.InDMAorHDMA = TRUE;
CPU.HDMARanInDMA = CPU.InDMA ? byte : 0;
temp = CPU.InWRAMDMAorHDMA;
tmpch = CPU.CurrentDMAorHDMAChannel;
// XXX: Not quite right...
ADD_CYCLES(Timings.DMACPUSync);
for (mask = 1, p = &DMA[0], d = 0; mask; mask <<= 1, p++, d++)
{
if (byte & mask)
{
CPU.InWRAMDMAorHDMA = FALSE;
CPU.CurrentDMAorHDMAChannel = d;
if (p->HDMAIndirectAddressing)
{
ShiftedIBank = (p->IndirectBank << 16);
IAddr = p->IndirectAddress;
}
else
{
ShiftedIBank = (p->ABank << 16);
IAddr = p->Address;
}
if (!HDMAMemPointers[d])
HDMAMemPointers[d] = S9xGetMemPointer(ShiftedIBank + IAddr);
if (p->DoTransfer)
{
// XXX: Hack for Uniracers, because we don't understand
// OAM Address Invalidation
if (p->BAddress == 0x04)
{
if (SNESGameFixes.Uniracers)
{
PPU.OAMAddr = 0x10c;
PPU.OAMFlip = 0;
}
}
#ifdef DEBUGGER
if (Settings.TraceHDMA && p->DoTransfer)
{
sprintf(String, "H-DMA[%d] %s (%d) 0x%06X->0x21%02X %s, Count: %3d, Rep: %s, V-LINE: %3ld %02X%04X",
p-DMA, p->ReverseTransfer? "read" : "write",
p->TransferMode, ShiftedIBank+IAddr, p->BAddress,
p->HDMAIndirectAddressing ? "ind" : "abs",
p->LineCount,
p->Repeat ? "yes" : "no ", (long) CPU.V_Counter,
p->ABank, p->Address);
S9xMessage(S9X_TRACE, S9X_HDMA_TRACE, String);
}
#endif
if (!p->ReverseTransfer)
{
if ((IAddr & MEMMAP_MASK) + HDMA_ModeByteCounts[p->TransferMode] >= MEMMAP_BLOCK_SIZE)
{
// HDMA REALLY-SLOW PATH
HDMAMemPointers[d] = NULL;
#define DOBYTE(Addr, RegOff) \
CPU.InWRAMDMAorHDMA = (ShiftedIBank == 0x7e0000 || ShiftedIBank == 0x7f0000 || \
(!(ShiftedIBank & 0x400000) && ((uint16) (Addr)) < 0x2000)); \
S9xSetPPU(S9xGetByte(ShiftedIBank + ((uint16) (Addr))), 0x2100 + p->BAddress + (RegOff));
switch (p->TransferMode)
{
case 0:
DOBYTE(IAddr, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 5:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 2, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 3, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 1:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 2:
case 6:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 3:
case 7:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 2, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 3, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 4:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 2, 2);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 3, 3);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
}
#undef DOBYTE
}
else
{
CPU.InWRAMDMAorHDMA = (ShiftedIBank == 0x7e0000 || ShiftedIBank == 0x7f0000 ||
(!(ShiftedIBank & 0x400000) && IAddr < 0x2000));
if (!HDMAMemPointers[d])
{
// HDMA SLOW PATH
uint32 Addr = ShiftedIBank + IAddr;
switch (p->TransferMode)
{
case 0:
S9xSetPPU(S9xGetByte(Addr), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 5:
S9xSetPPU(S9xGetByte(Addr + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 1), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
Addr += 2;
/* fall through */
case 1:
S9xSetPPU(S9xGetByte(Addr + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 1), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 2:
case 6:
S9xSetPPU(S9xGetByte(Addr + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 1), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 3:
case 7:
S9xSetPPU(S9xGetByte(Addr + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 1), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 2), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 3), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 4:
S9xSetPPU(S9xGetByte(Addr + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 1), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 2), 0x2102 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(S9xGetByte(Addr + 3), 0x2103 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
}
}
else
{
// HDMA FAST PATH
switch (p->TransferMode)
{
case 0:
S9xSetPPU(*HDMAMemPointers[d]++, 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 5:
S9xSetPPU(*(HDMAMemPointers[d] + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 1), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
HDMAMemPointers[d] += 2;
/* fall through */
case 1:
S9xSetPPU(*(HDMAMemPointers[d] + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
// XXX: All HDMA should read to MDR first. This one just
// happens to fix Speedy Gonzales.
OpenBus = *(HDMAMemPointers[d] + 1);
S9xSetPPU(OpenBus, 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
HDMAMemPointers[d] += 2;
break;
case 2:
case 6:
S9xSetPPU(*(HDMAMemPointers[d] + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 1), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
HDMAMemPointers[d] += 2;
break;
case 3:
case 7:
S9xSetPPU(*(HDMAMemPointers[d] + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 1), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 2), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 3), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
HDMAMemPointers[d] += 4;
break;
case 4:
S9xSetPPU(*(HDMAMemPointers[d] + 0), 0x2100 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 1), 0x2101 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 2), 0x2102 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
S9xSetPPU(*(HDMAMemPointers[d] + 3), 0x2103 + p->BAddress);
ADD_CYCLES(SLOW_ONE_CYCLE);
HDMAMemPointers[d] += 4;
break;
}
}
}
}
else
{
// REVERSE HDMA REALLY-SLOW PATH
// anomie says: Since this is apparently never used
// (otherwise we would have noticed before now), let's not bother with faster paths.
HDMAMemPointers[d] = NULL;
#define DOBYTE(Addr, RegOff) \
CPU.InWRAMDMAorHDMA = (ShiftedIBank == 0x7e0000 || ShiftedIBank == 0x7f0000 || \
(!(ShiftedIBank & 0x400000) && ((uint16) (Addr)) < 0x2000)); \
S9xSetByte(S9xGetPPU(0x2100 + p->BAddress + (RegOff)), ShiftedIBank + ((uint16) (Addr)));
switch (p->TransferMode)
{
case 0:
DOBYTE(IAddr, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 5:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 2, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 3, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 1:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 2:
case 6:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 3:
case 7:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 2, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 3, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
case 4:
DOBYTE(IAddr + 0, 0);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 1, 1);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 2, 2);
ADD_CYCLES(SLOW_ONE_CYCLE);
DOBYTE(IAddr + 3, 3);
ADD_CYCLES(SLOW_ONE_CYCLE);
break;
}
#undef DOBYTE
}
}
}
}
for (mask = 1, p = &DMA[0], d = 0; mask; mask <<= 1, p++, d++)
{
if (byte & mask)
{
if (p->DoTransfer)
{
if (p->HDMAIndirectAddressing)
p->IndirectAddress += HDMA_ModeByteCounts[p->TransferMode];
else
p->Address += HDMA_ModeByteCounts[p->TransferMode];
}
p->DoTransfer = !p->Repeat;
if (!--p->LineCount)
{
if (!HDMAReadLineCount(d))
{
byte &= ~mask;
PPU.HDMAEnded |= mask;
p->DoTransfer = FALSE;
}
}
else
ADD_CYCLES(SLOW_ONE_CYCLE);
}
}
CPU.InHDMA = FALSE;
CPU.InDMAorHDMA = CPU.InDMA;
CPU.InWRAMDMAorHDMA = temp;
CPU.CurrentDMAorHDMAChannel = tmpch;
return (byte);
}
void S9xResetDMA (void)
{
for (int d = 0; d < 8; d++)
{
DMA[d].ReverseTransfer = TRUE;
DMA[d].HDMAIndirectAddressing = TRUE;
DMA[d].AAddressFixed = TRUE;
DMA[d].AAddressDecrement = TRUE;
DMA[d].TransferMode = 7;
DMA[d].BAddress = 0xff;
DMA[d].AAddress = 0xffff;
DMA[d].ABank = 0xff;
DMA[d].DMACount_Or_HDMAIndirectAddress = 0xffff;
DMA[d].IndirectBank = 0xff;
DMA[d].Address = 0xffff;
DMA[d].Repeat = FALSE;
DMA[d].LineCount = 0x7f;
DMA[d].UnknownByte = 0xff;
DMA[d].DoTransfer = FALSE;
DMA[d].UnusedBit43x0 = 1;
}
}