/*****************************************************************************\ 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" static inline void ADD_CYCLES(int32_t n) { CPU.Cycles += n; } extern uint8_t *HDMAMemPointers[8]; extern int HDMA_ModeByteCounts[8]; static uint8_t sdd1_decode_buffer[0x10000]; static inline bool addCyclesInDMA(uint8_t 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; // 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; } bool S9xDoDMA(uint8_t Channel) { CPU.InDMA = 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_t c = d->DMACount_Or_HDMAIndirectAddress; // Writing $0000 to $43x5 actually results in a transfer of $10000 bytes, not 0. if (!c) c = 0x10000; // 8 cycles per channel ADD_CYCLES(SLOW_ONE_CYCLE); // 8 cycles per byte while (c) { --d->DMACount_Or_HDMAIndirectAddress; ++d->AAddress; --c; if (!addCyclesInDMA(Channel)) { CPU.InDMA = false; CPU.InDMAorHDMA = false; CPU.CurrentDMAorHDMAChannel = -1; return false; } } CPU.InDMA = false; CPU.InDMAorHDMA = false; CPU.CurrentDMAorHDMAChannel = -1; return true; } // Prepare for accessing $2118-2119 int32_t inc = d->AAddressFixed ? 0 : (!d->AAddressDecrement ? 1 : -1); int32_t count = d->DMACount_Or_HDMAIndirectAddress; // Writing $0000 to $43x5 actually results in a transfer of $10000 bytes, not 0. if (!count) count = 0x10000; // Prepare for custom chip DMA // S-DD1 uint8_t *in_sdd1_dma = nullptr; 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_t *in_ptr = S9xGetBasePointer((d->ABank << 16) | d->AAddress); if (in_ptr) in_ptr += d->AAddress; in_sdd1_dma = sdd1_decode_buffer; } Memory.FillRAM[0x4801] = 0; } // Do Transfer uint8_t Work; // 8 cycles per channel ADD_CYCLES(SLOW_ONE_CYCLE); if (!d->ReverseTransfer) { // CPU -> PPU int32_t b = 0; uint16_t p = d->AAddress; uint8_t *base = S9xGetBasePointer((d->ABank << 16) + d->AAddress); int32_t 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; } bool inWRAM_DMA = !in_sdd1_dma && (d->ABank == 0x7e || d->ABank == 0x7f || (!(d->ABank & 0x40) && d->AAddress < 0x2000)); // 8 cycles per byte auto UPDATE_COUNTERS = [&]() -> bool { --d->DMACount_Or_HDMAIndirectAddress; d->AAddress += inc; p += inc; if (!addCyclesInDMA(Channel)) { CPU.InDMA = CPU.InDMAorHDMA = CPU.InWRAMDMAorHDMA = false; CPU.CurrentDMAorHDMAChannel = -1; return false; } return true; }; while (1) { if (count > rem) count = rem; rem -= count; CPU.InWRAMDMAorHDMA = inWRAM_DMA; if (!base) { // DMA SLOW PATH if (!d->TransferMode || d->TransferMode == 2 || d->TransferMode == 6) { do { Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2100 + d->BAddress); if (!UPDATE_COUNTERS()) return false; } 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); if (!UPDATE_COUNTERS()) return false; --count; // Fall through case 1: Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; --count; } } if (count == 1) { Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2100 + d->BAddress); if (!UPDATE_COUNTERS()) return false; 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); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 1; break; } // Fall through case 1: Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2100 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 2; break; } // Fall through case 2: Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 3; break; } // Fall through case 3: Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; 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); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 1; break; } // Fall through case 1: Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 2; break; } // Fall through case 2: Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2102 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 3; break; } // Fall through case 3: Work = S9xGetByte((d->ABank << 16) + p); S9xSetPPU(Work, 0x2103 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 0; break; } } while (1); } } } else { // DMA FAST PATH if (!d->TransferMode || d->TransferMode == 2 || d->TransferMode == 6) { switch (d->BAddress) { case 0x04: // OAMDATA do { Work = *(base + p); REGISTER_2104(Work); if (!UPDATE_COUNTERS()) return false; } while (--count > 0); break; case 0x18: // VMDATAL if (!PPU.VMA.FullGraphicCount) { do { Work = *(base + p); REGISTER_2118_linear(Work); if (!UPDATE_COUNTERS()) return false; } while (--count > 0); } else { do { Work = *(base + p); REGISTER_2118_tile(Work); if (!UPDATE_COUNTERS()) return false; } while (--count > 0); } break; case 0x19: // VMDATAH if (!PPU.VMA.FullGraphicCount) { do { Work = *(base + p); REGISTER_2119_linear(Work); if (!UPDATE_COUNTERS()) return false; } while (--count > 0); } else { do { Work = *(base + p); REGISTER_2119_tile(Work); if (!UPDATE_COUNTERS()) return false; } while (--count > 0); } break; case 0x22: // CGDATA do { Work = *(base + p); if (!UPDATE_COUNTERS()) return false; } while (--count > 0); break; case 0x80: // WMDATA if (!CPU.InWRAMDMAorHDMA) { do { Work = *(base + p); REGISTER_2180(Work); if (!UPDATE_COUNTERS()) return false; } while (--count > 0); } else { do { if (!UPDATE_COUNTERS()) return false; } while (--count > 0); } break; default: do { Work = *(base + p); S9xSetPPU(Work, 0x2100 + d->BAddress); if (!UPDATE_COUNTERS()) return false; } 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); if (!UPDATE_COUNTERS()) return false; --count; // Fall through case 1: OpenBus = *(base + p); REGISTER_2119_linear(OpenBus); if (!UPDATE_COUNTERS()) return false; --count; } } if (count == 1) { Work = *(base + p); REGISTER_2118_linear(Work); if (!UPDATE_COUNTERS()) return false; b = 1; } else b = 0; } else { switch (b) { default: while (count > 1) { Work = *(base + p); REGISTER_2118_tile(Work); if (!UPDATE_COUNTERS()) return false; --count; // Fall through case 1: Work = *(base + p); REGISTER_2119_tile(Work); if (!UPDATE_COUNTERS()) return false; --count; } } if (count == 1) { Work = *(base + p); REGISTER_2118_tile(Work); if (!UPDATE_COUNTERS()) return false; 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); if (!UPDATE_COUNTERS()) return false; --count; // Fall through case 1: Work = *(base + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; --count; } } if (count == 1) { Work = *(base + p); S9xSetPPU(Work, 0x2100 + d->BAddress); if (!UPDATE_COUNTERS()) return false; 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); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 1; break; } // Fall through case 1: Work = *(base + p); S9xSetPPU(Work, 0x2100 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 2; break; } // Fall through case 2: Work = *(base + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 3; break; } // Fall through case 3: Work = *(base + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; 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); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 1; break; } // Fall through case 1: Work = *(base + p); S9xSetPPU(Work, 0x2101 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 2; break; } // Fall through case 2: Work = *(base + p); S9xSetPPU(Work, 0x2102 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 3; break; } // Fall through case 3: Work = *(base + p); S9xSetPPU(Work, 0x2103 + d->BAddress); if (!UPDATE_COUNTERS()) return false; if (--count <= 0) { b = 0; break; } } while (1); } } } if (rem <= 0) break; base = S9xGetBasePointer((d->ABank << 16) + d->AAddress); count = MEMMAP_BLOCK_SIZE; inWRAM_DMA = !in_sdd1_dma && (d->ABank == 0x7e || d->ABank == 0x7f || (!(d->ABank & 0x40) && d->AAddress < 0x2000)); } } else { // PPU -> CPU // 8 cycles per byte auto UPDATE_COUNTERS = [&]() -> bool { --d->DMACount_Or_HDMAIndirectAddress; d->AAddress += inc; if (!addCyclesInDMA(Channel)) { CPU.InDMA = CPU.InDMAorHDMA = CPU.InWRAMDMAorHDMA = false; CPU.CurrentDMAorHDMAChannel = -1; return false; } return true; }; 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); if (!UPDATE_COUNTERS()) return false; --count; break; case 1: case 5: CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; --count; break; case 3: case 7: CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; --count; break; case 4: CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2102 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; CPU.InWRAMDMAorHDMA = d->AAddress < 0x2000; Work = S9xGetPPU(0x2103 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; --count; break; default: while (count) { if (!UPDATE_COUNTERS()) return false; --count; } } } 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); if (!UPDATE_COUNTERS()) return false; --count; break; case 1: case 5: Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; --count; break; case 3: case 7: Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; --count; break; case 4: Work = S9xGetPPU(0x2100 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; Work = S9xGetPPU(0x2101 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; Work = S9xGetPPU(0x2102 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; if (!--count) break; Work = S9xGetPPU(0x2103 + d->BAddress); S9xSetByte(Work, (d->ABank << 16) + d->AAddress); if (!UPDATE_COUNTERS()) return false; --count; break; default: while (count) { if (!UPDATE_COUNTERS()) return false; --count; } } } while (count); } } if (CPU.NMIPending && Timings.NMITriggerPos != 0xffff) Timings.NMITriggerPos = CPU.Cycles + Timings.NMIDMADelay; CPU.InDMA = CPU.InDMAorHDMA = CPU.InWRAMDMAorHDMA = false; CPU.CurrentDMAorHDMAChannel = -1; return true; } static inline bool HDMAReadLineCount(int d) { // CPU.InDMA is set, so S9xGetXXX() / S9xSetXXX() incur no charges. uint8_t 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].DMACount_Or_HDMAIndirectAddress = S9xGetWord((DMA[d].ABank << 16) + DMA[d].Address); ++DMA[d].Address; } ++DMA[d].Address; HDMAMemPointers[d] = nullptr; 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].DMACount_Or_HDMAIndirectAddress = S9xGetWord((DMA[d].ABank << 16) + DMA[d].Address); DMA[d].Address += 2; HDMAMemPointers[d] = S9xGetMemPointer((DMA[d].IndirectBank << 16) + DMA[d].DMACount_Or_HDMAIndirectAddress); } else HDMAMemPointers[d] = S9xGetMemPointer((DMA[d].ABank << 16) + DMA[d].Address); return true; } void S9xStartHDMA() { PPU.HDMA = Memory.FillRAM[0x420c]; PPU.HDMAEnded = 0; CPU.InHDMA = CPU.InDMAorHDMA = true; int32_t tmpch = CPU.CurrentDMAorHDMAChannel; // XXX: Not quite right... if (PPU.HDMA) ADD_CYCLES(Timings.DMACPUSync); for (uint8_t 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_t S9xDoHDMA(uint8_t byte) { SDMA *p; int d; uint8_t mask; CPU.InHDMA = CPU.InDMAorHDMA = true; CPU.HDMARanInDMA = CPU.InDMA ? byte : 0; bool temp = CPU.InWRAMDMAorHDMA; int32_t 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; uint32_t ShiftedIBank; uint16_t IAddr; if (p->HDMAIndirectAddressing) { ShiftedIBank = p->IndirectBank << 16; IAddr = p->DMACount_Or_HDMAIndirectAddress; } 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; } } if (!p->ReverseTransfer) { if ((IAddr & MEMMAP_MASK) + HDMA_ModeByteCounts[p->TransferMode] >= MEMMAP_BLOCK_SIZE) { // HDMA REALLY-SLOW PATH HDMAMemPointers[d] = nullptr; auto DOBYTE = [&](uint16_t Addr, uint16_t RegOff) { CPU.InWRAMDMAorHDMA = ShiftedIBank == 0x7e0000 || ShiftedIBank == 0x7f0000 || (!(ShiftedIBank & 0x400000) && Addr < 0x2000); S9xSetPPU(S9xGetByte(ShiftedIBank + Addr), 0x2100 + p->BAddress + RegOff); }; switch (p->TransferMode) { case 0: DOBYTE(IAddr, 0); ADD_CYCLES(SLOW_ONE_CYCLE); break; case 5: DOBYTE(IAddr, 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); ADD_CYCLES(SLOW_ONE_CYCLE); DOBYTE(IAddr + 1, 1); ADD_CYCLES(SLOW_ONE_CYCLE); break; case 2: case 6: DOBYTE(IAddr, 0); ADD_CYCLES(SLOW_ONE_CYCLE); DOBYTE(IAddr + 1, 0); ADD_CYCLES(SLOW_ONE_CYCLE); break; case 3: case 7: DOBYTE(IAddr, 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); 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); } } else { CPU.InWRAMDMAorHDMA = ShiftedIBank == 0x7e0000 || ShiftedIBank == 0x7f0000 || (!(ShiftedIBank & 0x400000) && IAddr < 0x2000); if (!HDMAMemPointers[d]) { // HDMA SLOW PATH uint32_t 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), 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), 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), 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), 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), 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); } } 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], 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], 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], 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], 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], 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; } } } } 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] = nullptr; auto DOBYTE = [&](uint16_t Addr, uint16_t RegOff) { CPU.InWRAMDMAorHDMA = ShiftedIBank == 0x7e0000 || ShiftedIBank == 0x7f0000 || (!(ShiftedIBank & 0x400000) && Addr < 0x2000); S9xSetByte(S9xGetPPU(0x2100 + p->BAddress + RegOff), ShiftedIBank + Addr); }; switch (p->TransferMode) { case 0: DOBYTE(IAddr, 0); ADD_CYCLES(SLOW_ONE_CYCLE); break; case 5: DOBYTE(IAddr, 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); ADD_CYCLES(SLOW_ONE_CYCLE); DOBYTE(IAddr + 1, 1); ADD_CYCLES(SLOW_ONE_CYCLE); break; case 2: case 6: DOBYTE(IAddr, 0); ADD_CYCLES(SLOW_ONE_CYCLE); DOBYTE(IAddr + 1, 0); ADD_CYCLES(SLOW_ONE_CYCLE); break; case 3: case 7: DOBYTE(IAddr, 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); 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); } } } } } for (mask = 1, p = &DMA[0], d = 0; mask; mask <<= 1, ++p, ++d) { if (byte & mask) { if (p->DoTransfer) { if (p->HDMAIndirectAddressing) p->DMACount_Or_HDMAIndirectAddress += 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() { for (int d = 0; d < 8; ++d) { DMA[d].ReverseTransfer = DMA[d].HDMAIndirectAddressing = DMA[d].AAddressFixed = 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 = true; } }