#include "stdafx.h" #include "Debugger.h" #include "MemoryManager.h" #include "PPU.h" #include "CodeDataLogger.h" #include "BaseMapper.h" #include "MemoryDumper.h" #include "VideoDecoder.h" #include "Disassembler.h" #include "MMC5.h" #include "DebugBreakHelper.h" MemoryDumper::MemoryDumper(shared_ptr ppu, shared_ptr memoryManager, shared_ptr mapper, shared_ptr codeDataLogger, Debugger* debugger, shared_ptr disassembler) { _debugger = debugger; _ppu = ppu; _memoryManager = memoryManager; _mapper = mapper; _codeDataLogger = codeDataLogger; _disassembler = disassembler; } void MemoryDumper::SetMemoryState(DebugMemoryType type, uint8_t *buffer, int32_t length) { switch(type) { case DebugMemoryType::ChrRom: case DebugMemoryType::PrgRom: case DebugMemoryType::CpuMemory: case DebugMemoryType::PpuMemory: break; case DebugMemoryType::InternalRam: for(int i = 0; i < 0x800 && i < length; i++) { _memoryManager->DebugWrite(i, buffer[i]); } break; case DebugMemoryType::PaletteMemory: for(int i = 0; i < 0x20 && i < length; i++) { _ppu->WritePaletteRAM(i, buffer[i]); } break; case DebugMemoryType::SpriteMemory: memcpy(_ppu->GetSpriteRam(), buffer, std::min(length, 0x100)); break; case DebugMemoryType::SecondarySpriteMemory: memcpy(_ppu->GetSecondarySpriteRam(), buffer, std::min(length, 0x20)); break; case DebugMemoryType::ChrRam: case DebugMemoryType::WorkRam: case DebugMemoryType::SaveRam: case DebugMemoryType::NametableRam: _mapper->WriteMemory(type, buffer, length); break; } } bool MemoryDumper::HasUndoHistory() { return _undoHistory.size() > 0; } void MemoryDumper::PerformUndo() { if(!_undoHistory.empty()) { DebugBreakHelper helper(_debugger); _mapper->RestorePrgChrBackup(_undoHistory.back()); _undoHistory.pop_back(); _debugger->UpdateCdlCache(); } } void MemoryDumper::AddUndoHistory(vector &originalRomData) { vector newData = _mapper->GetPrgChrCopy(); if(memcmp(originalRomData.data(), newData.data(), originalRomData.size()) != 0) { //Add a step in the undo history _undoHistory.push_back(originalRomData); if(_undoHistory.size() > 100) { _undoHistory.pop_front(); } } } uint32_t MemoryDumper::GetMemorySize(DebugMemoryType type) { switch(type) { case DebugMemoryType::CpuMemory: return 0x10000; case DebugMemoryType::PpuMemory: return 0x4000; case DebugMemoryType::PaletteMemory: return 0x20; case DebugMemoryType::SpriteMemory: return 0x100; case DebugMemoryType::SecondarySpriteMemory: return 0x20; case DebugMemoryType::InternalRam: return 0x800; case DebugMemoryType::NametableRam: case DebugMemoryType::PrgRom: case DebugMemoryType::ChrRom: case DebugMemoryType::ChrRam: case DebugMemoryType::WorkRam: case DebugMemoryType::SaveRam: return _mapper->GetMemorySize(type); } return 0; } uint32_t MemoryDumper::GetMemoryState(DebugMemoryType type, uint8_t *buffer) { switch(type) { case DebugMemoryType::CpuMemory: for(int i = 0; i <= 0xFFFF; i++) { buffer[i] = _memoryManager->DebugRead(i); } return 0x10000; case DebugMemoryType::PpuMemory: for(int i = 0; i < 0x3F00; i++) { buffer[i] = _mapper->DebugReadVRAM(i); } for(int i = 0x3F00; i <= 0x3FFF; i++) { buffer[i] = _ppu->ReadPaletteRAM(i); } return 0x4000; case DebugMemoryType::PaletteMemory: for(int i = 0; i <= 0x1F; i++) { buffer[i] = _ppu->ReadPaletteRAM(i); } return 0x20; case DebugMemoryType::SpriteMemory: memcpy(buffer, _ppu->GetSpriteRam(), 0x100); return 0x100; case DebugMemoryType::SecondarySpriteMemory: memcpy(buffer, _ppu->GetSecondarySpriteRam(), 0x20); return 0x20; case DebugMemoryType::PrgRom: case DebugMemoryType::ChrRom: case DebugMemoryType::ChrRam: case DebugMemoryType::WorkRam: case DebugMemoryType::SaveRam: case DebugMemoryType::NametableRam: return _mapper->CopyMemory(type, buffer); case DebugMemoryType::InternalRam: for(int i = 0; i < 0x800; i++) { buffer[i] = _memoryManager->DebugRead(i); } return 0x800; } return 0; } void MemoryDumper::SetMemoryValues(DebugMemoryType memoryType, uint32_t address, uint8_t* data, int32_t length) { vector originalRomData = _mapper->GetPrgChrCopy(); for(int i = 0; i < length; i++) { SetMemoryValue(memoryType, address+i, data[i], true); } if(memoryType == DebugMemoryType::CpuMemory) { //Rebuild prg rom cache as needed after editing the code with the assembler/hex editor AddressTypeInfo infoStart, infoEnd; _debugger->GetAbsoluteAddressAndType(address, &infoStart); _debugger->GetAbsoluteAddressAndType(address+length, &infoEnd); if(infoStart.Type == AddressType::PrgRom && infoEnd.Type == AddressType::PrgRom && infoEnd.Address - infoStart.Address == length) { _disassembler->RebuildPrgRomCache(infoStart.Address, length); } } else if(memoryType == DebugMemoryType::PrgRom) { _disassembler->RebuildPrgRomCache(address, length); } AddUndoHistory(originalRomData); } void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache, bool disableSideEffects) { vector originalRomData; if(!preventRebuildCache) { originalRomData = _mapper->GetPrgChrCopy(); } switch(memoryType) { case DebugMemoryType::CpuMemory: if(disableSideEffects) { AddressTypeInfo info; _debugger->GetAbsoluteAddressAndType(address, &info); if(info.Address >= 0) { switch(info.Type) { case AddressType::Register: break; //not supported case AddressType::InternalRam: SetMemoryValue(DebugMemoryType::InternalRam, info.Address, value, preventRebuildCache, true); break; case AddressType::PrgRom: SetMemoryValue(DebugMemoryType::PrgRom, info.Address, value, preventRebuildCache, true); break; case AddressType::WorkRam: SetMemoryValue(DebugMemoryType::WorkRam, info.Address, value, preventRebuildCache, true); break; case AddressType::SaveRam: SetMemoryValue(DebugMemoryType::SaveRam, info.Address, value, preventRebuildCache, true); break; } } } else { _memoryManager->DebugWrite(address, value, false); } break; case DebugMemoryType::PpuMemory: if(address < 0x3F00) { _mapper->DebugWriteVRAM(address, value, disableSideEffects); } else { _ppu->WritePaletteRAM(address, value); } break; case DebugMemoryType::PaletteMemory: _ppu->WritePaletteRAM(address, value); break; case DebugMemoryType::SpriteMemory: _ppu->GetSpriteRam()[address % 0x100] = value; break; case DebugMemoryType::SecondarySpriteMemory: _ppu->GetSecondarySpriteRam()[address % 0x20] = value; break; case DebugMemoryType::PrgRom: _mapper->SetMemoryValue(memoryType, address, value); if(!preventRebuildCache) { _disassembler->RebuildPrgRomCache(address, 1); } break; case DebugMemoryType::ChrRom: case DebugMemoryType::ChrRam: case DebugMemoryType::WorkRam: case DebugMemoryType::SaveRam: case DebugMemoryType::NametableRam: _mapper->SetMemoryValue(memoryType, address, value); break; case DebugMemoryType::InternalRam: _memoryManager->DebugWrite(address, value); break; } if(!preventRebuildCache) { AddUndoHistory(originalRomData); } } uint16_t MemoryDumper::GetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects) { return GetMemoryValue(memoryType, address, disableSideEffects) | (GetMemoryValue(memoryType, address + 1, disableSideEffects) << 8); } void MemoryDumper::SetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, uint16_t value, bool preventRebuildCache, bool disableSideEffects) { SetMemoryValue(memoryType, address, (uint8_t)value, preventRebuildCache, disableSideEffects); SetMemoryValue(memoryType, address + 1, (uint8_t)(value >> 8), preventRebuildCache, disableSideEffects); } uint8_t MemoryDumper::GetMemoryValue(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects) { switch(memoryType) { case DebugMemoryType::CpuMemory: return _memoryManager->DebugRead(address, disableSideEffects); case DebugMemoryType::PpuMemory: return _mapper->DebugReadVRAM(address, disableSideEffects); case DebugMemoryType::PaletteMemory: return _ppu->ReadPaletteRAM(address); case DebugMemoryType::SpriteMemory: return _ppu->GetSpriteRam()[address % 0x100]; case DebugMemoryType::SecondarySpriteMemory: return _ppu->GetSecondarySpriteRam()[address % 0x20]; case DebugMemoryType::PrgRom: case DebugMemoryType::ChrRom: case DebugMemoryType::ChrRam: case DebugMemoryType::WorkRam: case DebugMemoryType::SaveRam: case DebugMemoryType::NametableRam: return _mapper->GetMemoryValue(memoryType, address); case DebugMemoryType::InternalRam: return _memoryManager->DebugRead(address); } return 0; } void MemoryDumper::GetNametable(int nametableIndex, NametableDisplayMode mode, uint32_t* frameBuffer, uint8_t* tileData, uint8_t* paletteData) { shared_ptr mmc5 = std::dynamic_pointer_cast(_mapper); uint32_t *rgbPalette = _debugger->GetConsole()->GetSettings()->GetRgbPalette(); PPUDebugState state; _ppu->GetState(state); uint16_t bgAddr = state.ControlFlags.BackgroundPatternAddr; uint16_t baseAddr = 0x2000 + nametableIndex * 0x400; uint16_t baseAttributeAddr = baseAddr + 960; uint8_t grayscalePalette[4] = { 0x0F, 0x00, 0x10, 0x20 }; for(uint8_t y = 0; y < 30; y++) { for(uint8_t x = 0; x < 32; x++) { uint16_t ntIndex = (y << 5) + x; uint8_t tileIndex = _mapper->DebugReadVRAM(baseAddr + ntIndex); uint8_t attribute = _mapper->DebugReadVRAM(baseAttributeAddr + ((y & 0xFC) << 1) + (x >> 2)); tileData[y * 32 + x] = tileIndex; paletteData[y * 32 + x] = attribute; uint8_t shift = (x & 0x02) | ((y & 0x02) << 1); uint8_t paletteBaseAddr; if(mmc5 && mmc5->IsExtendedAttributes()) { paletteBaseAddr = mmc5->GetExAttributeNtPalette(ntIndex) << 2; } else { paletteBaseAddr = ((attribute >> shift) & 0x03) << 2; } uint16_t tileAddr = bgAddr + (tileIndex << 4); if(mode == NametableDisplayMode::AttributeView) { for(uint8_t i = 0; i < 8; i++) { for(uint8_t j = 0; j < 8; j++) { uint8_t color = ((j & 0x04) >> 2) + ((i & 0x04) >> 1); frameBuffer[(y << 11) + (x << 3) + (i << 8) + j] = rgbPalette[_ppu->ReadPaletteRAM(color == 0 ? 0 : (paletteBaseAddr + color))]; } } } else { for(uint8_t i = 0; i < 8; i++) { uint8_t lowByte, highByte; if(mmc5 && mmc5->IsExtendedAttributes()) { lowByte = mmc5->GetExAttributeTileData(ntIndex, tileAddr + i); highByte = mmc5->GetExAttributeTileData(ntIndex, tileAddr + i + 8); } else { lowByte = _mapper->DebugReadVRAM(tileAddr + i); highByte = _mapper->DebugReadVRAM(tileAddr + i + 8); } for(uint8_t j = 0; j < 8; j++) { uint8_t color = ((lowByte >> (7 - j)) & 0x01) | (((highByte >> (7 - j)) & 0x01) << 1); if(mode == NametableDisplayMode::Grayscale) { frameBuffer[(y << 11) + (x << 3) + (i << 8) + j] = rgbPalette[grayscalePalette[color]]; } else { frameBuffer[(y << 11) + (x << 3) + (i << 8) + j] = rgbPalette[(color == 0 ? _ppu->ReadPaletteRAM(0) : _ppu->ReadPaletteRAM(paletteBaseAddr + color)) & 0x3F]; } } } } } } } void MemoryDumper::GatherChrPaletteInfo() { shared_ptr mmc5 = std::dynamic_pointer_cast(_mapper); PPUDebugState state; _ppu->GetState(state); uint16_t bgAddr = state.ControlFlags.BackgroundPatternAddr; uint32_t palettes[8]; for(int i = 0; i < 8; i++) { palettes[i] = _ppu->ReadPaletteRAM(0) | (_ppu->ReadPaletteRAM(i * 4 + 1) << 8) | (_ppu->ReadPaletteRAM(i * 4 + 2) << 16) | (_ppu->ReadPaletteRAM(i * 4 + 3) << 24); } auto processTilePalette = [=](uint32_t tileAddr, uint8_t paletteIndex) { TileKey key; uint32_t absoluteAddr; if(!mmc5 || !mmc5->IsExtendedAttributes()) { absoluteAddr = _mapper->ToAbsoluteChrAddress(tileAddr); } else { absoluteAddr = tileAddr; } if(_mapper->HasChrRom()) { key.TileIndex = absoluteAddr / 16; key.IsChrRamTile = false; } else { _mapper->CopyChrTile(absoluteAddr, key.TileData); key.IsChrRamTile = true; } _paletteByTile[key] = palettes[paletteIndex]; }; if(state.ControlFlags.BackgroundEnabled) { //Nametables - Check all palette/tile combinations //Only process nametables when background is enabled (to avoid issues when booting with random RAM contents) for(int i = 0; i < 4; i++) { uint16_t baseAddr = 0x2000 + i * 0x400; uint16_t baseAttributeAddr = baseAddr + 960; for(uint8_t y = 0; y < 30; y++) { for(uint8_t x = 0; x < 32; x++) { uint16_t ntIndex = (y << 5) + x; uint8_t tileIndex = _mapper->DebugReadVRAM(baseAddr + ntIndex); uint16_t tileAddr; uint8_t paletteIndex; if(mmc5 && mmc5->IsExtendedAttributes()) { paletteIndex = mmc5->GetExAttributeNtPalette(ntIndex); tileAddr = mmc5->GetExAttributeAbsoluteTileAddr(ntIndex, tileIndex * 16); } else { uint8_t attribute = _mapper->DebugReadVRAM(baseAttributeAddr + ((y & 0xFC) << 1) + (x >> 2)); uint8_t shift = (x & 0x02) | ((y & 0x02) << 1); paletteIndex = ((attribute >> shift) & 0x03); tileAddr = bgAddr + (tileIndex << 4); } processTilePalette(tileAddr, paletteIndex); } } } } if(state.ControlFlags.SpritesEnabled) { //Sprites - Check all sprites palettes //Only process sprites if sprites are enabled (to avoid issues when booting with random RAM contents) uint8_t *spriteRam = _ppu->GetSpriteRam(); uint16_t spriteAddr = state.ControlFlags.SpritePatternAddr; bool largeSprites = state.ControlFlags.LargeSprites; for(uint8_t y = 0; y < 8; y++) { for(uint8_t x = 0; x < 8; x++) { uint8_t ramAddr = ((y << 3) + x) << 2; uint8_t tileIndex = spriteRam[ramAddr + 1]; uint8_t attributes = spriteRam[ramAddr + 2]; uint16_t tileAddr; if(largeSprites) { tileAddr = (tileIndex & 0x01 ? 0x1000 : 0x0000) + ((tileIndex & 0xFE) << 4); } else { tileAddr = spriteAddr + (tileIndex << 4); } uint8_t palette = (attributes & 0x03) | 0x04; processTilePalette(tileAddr, palette); if(largeSprites) { processTilePalette(tileAddr + 16, palette); } } } } } void MemoryDumper::GetChrBank(int bankIndex, uint32_t* frameBuffer, uint8_t palette, bool largeSprites, CdlHighlightType highlightType, bool useAutoPalette, bool showSingleColorTilesInGrayscale, uint32_t* paletteBuffer) { uint8_t paletteBaseAddr = (palette & 0x07) << 2; uint32_t defaultPalette; if(palette & 0x08) { //Use grayscale palette defaultPalette = 0x2010000F; } else { defaultPalette = _ppu->ReadPaletteRAM(0) | (_ppu->ReadPaletteRAM(paletteBaseAddr + 1) << 8) | (_ppu->ReadPaletteRAM(paletteBaseAddr + 2) << 16) | (_ppu->ReadPaletteRAM(paletteBaseAddr + 3) << 24); } uint32_t *rgbPalette = _debugger->GetConsole()->GetSettings()->GetRgbPalette(); uint8_t chrBuffer[0x1000]; bool chrIsDrawn[0x1000]; bool isChrRam = _mapper->GetMemorySize(DebugMemoryType::ChrRam) > 0; if(bankIndex == 0 || bankIndex == 1) { uint16_t baseAddr = bankIndex == 0 ? 0x0000 : 0x1000; for(int i = 0; i < 0x1000; i++) { chrBuffer[i] = _mapper->DebugReadVRAM(baseAddr + i); chrIsDrawn[i] = isChrRam ? true : _codeDataLogger->IsDrawn(_mapper->ToAbsoluteChrAddress(baseAddr + i)); } } else { int bank = bankIndex - 2; uint32_t baseAddr = bank * 0x1000; uint32_t chrSize = _mapper->GetMemorySize(isChrRam ? DebugMemoryType::ChrRam : DebugMemoryType::ChrRom); if(baseAddr + 0xFFF >= chrSize) { //Out of range, return to prevent crash return; } vector chrData(chrSize, 0); _mapper->CopyMemory(isChrRam ? DebugMemoryType::ChrRam : DebugMemoryType::ChrRom, chrData.data()); for(int i = 0; i < 0x1000; i++) { chrBuffer[i] = chrData[baseAddr + i]; chrIsDrawn[i] = isChrRam ? true : _codeDataLogger->IsDrawn(baseAddr + i); } } for(uint8_t y = 0; y < 16; y++) { for(uint8_t x = 0; x < 16; x++) { uint8_t tileIndex = y * 16 + x; uint32_t paletteData = defaultPalette; if(useAutoPalette) { TileKey key; uint32_t absoluteTileIndex = bankIndex <= 1 ? _mapper->ToAbsoluteChrAddress(bankIndex * 0x1000 + tileIndex * 16) / 16 : ((bankIndex - 2) * 256 + tileIndex); if(_mapper->HasChrRom()) { key.TileIndex = absoluteTileIndex; key.IsChrRamTile = false; } else { _mapper->CopyChrTile(absoluteTileIndex * 16, key.TileData); key.IsChrRamTile = true; } auto result = _paletteByTile.find(key); if(result != _paletteByTile.end()) { paletteData = result->second; } } auto outputTile = [=](uint32_t palette) { paletteBuffer[tileIndex] = palette; bool usesSingleColor = true; int64_t previousColor = -1; uint16_t tileAddr = tileIndex << 4; for(uint8_t i = 0; i < 8; i++) { uint8_t lowByte = chrBuffer[tileAddr + i]; uint8_t highByte = chrBuffer[tileAddr + i + 8]; bool isDrawn = chrIsDrawn[tileAddr + i]; for(uint8_t j = 0; j < 8; j++) { uint8_t color = ((lowByte >> (7 - j)) & 0x01) | (((highByte >> (7 - j)) & 0x01) << 1); uint32_t position; if(largeSprites) { int tmpX = x / 2 + ((y & 0x01) ? 8 : 0); int tmpY = (y & 0xFE) + ((x & 0x01) ? 1 : 0); position = (tmpY << 10) + (tmpX << 3) + (i << 7) + j; } else { position = (y << 10) + (x << 3) + (i << 7) + j; } uint32_t pixelColor = rgbPalette[(palette >> (8 * color)) & 0x3F]; frameBuffer[position] = pixelColor; if(previousColor >= 0 && previousColor != pixelColor) { usesSingleColor = false; } previousColor = pixelColor; if(highlightType != CdlHighlightType::None && isDrawn == (highlightType != CdlHighlightType::HighlightUsed)) { frameBuffer[position] &= 0x4FFFFFFF; } } } return !usesSingleColor; }; if(!outputTile(paletteData) && showSingleColorTilesInGrayscale) { //Tile is a solid color, redraw it in grayscale outputTile(0x2010000F); } } } } void MemoryDumper::GetSprites(uint32_t* frameBuffer, int16_t sourcePage) { memset(frameBuffer, 0, 64*128*sizeof(uint32_t)); uint8_t src[256]; if(sourcePage < 0) { memcpy(src, _ppu->GetSpriteRam(), 256); } else { for(int i = (sourcePage << 8); i < (sourcePage+1) << 8; i++) { src[i & 0xFF] = _memoryManager->DebugRead(i); } } uint32_t *rgbPalette = _debugger->GetConsole()->GetSettings()->GetRgbPalette(); PPUDebugState state; _ppu->GetState(state); uint16_t spriteAddr = state.ControlFlags.SpritePatternAddr; bool largeSprites = state.ControlFlags.LargeSprites; for(uint8_t y = 0; y < 8; y++) { for(uint8_t x = 0; x < 8; x++) { uint8_t ramAddr = ((y << 3) + x) << 2; uint8_t tileIndex = src[ramAddr + 1]; uint8_t attributes = src[ramAddr + 2]; bool verticalMirror = (attributes & 0x80) == 0x80; bool horizontalMirror = (attributes & 0x40) == 0x40; uint16_t tileAddr; if(largeSprites) { tileAddr = (tileIndex & 0x01 ? 0x1000 : 0x0000) + ((tileIndex & 0xFE) << 4); } else { tileAddr = spriteAddr + (tileIndex << 4); } uint8_t palette = 0x10 + ((attributes & 0x03) << 2); for(uint8_t i = 0, iMax = largeSprites ? 16 : 8; i < iMax; i++) { if(i == 8) { //Move to next tile for 2nd tile of 8x16 sprites tileAddr += 8; } uint8_t lowByte = _mapper->DebugReadVRAM(tileAddr + i); uint8_t highByte = _mapper->DebugReadVRAM(tileAddr + i + 8); for(uint8_t j = 0; j < 8; j++) { uint8_t color; if(horizontalMirror) { color = ((lowByte >> j) & 0x01) | (((highByte >> j) & 0x01) << 1); } else { color = ((lowByte >> (7 - j)) & 0x01) | (((highByte >> (7 - j)) & 0x01) << 1); } uint16_t destAddr; if(verticalMirror) { destAddr = (y << 10) + (x << 3) + (((largeSprites ? 15 : 7) - i) << 6) + j; } else { destAddr = (y << 10) + (x << 3) + (i << 6) + j; } if(color != 0) { frameBuffer[destAddr] = rgbPalette[_ppu->ReadPaletteRAM(palette + color) & 0x3F]; } } } } } } void MemoryDumper::GetPalette(uint32_t* frameBuffer) { uint32_t *rgbPalette = _debugger->GetConsole()->GetSettings()->GetRgbPalette(); for(uint8_t i = 0; i < 32; i++) { frameBuffer[i] = rgbPalette[_ppu->ReadPaletteRAM(i) & 0x3F]; } }