From 35a54505b425e7a554bfa6e6fcb24af8e0718f9d Mon Sep 17 00:00:00 2001 From: Sour Date: Sun, 19 Jun 2022 15:05:12 -0400 Subject: [PATCH] Debugger: Added tile editor --- Core/Debugger/DebugTypes.h | 16 +- Core/Debugger/PpuTools.cpp | 91 +++++++++ Core/Debugger/PpuTools.h | 9 +- Core/Gameboy/Debugger/GbPpuTools.cpp | 15 +- Core/Gameboy/Debugger/GbPpuTools.h | 2 +- Core/NES/Debugger/NesPpuTools.cpp | 8 +- Core/NES/Debugger/NesPpuTools.h | 2 +- Core/PCE/Debugger/PceVdcTools.cpp | 13 +- Core/PCE/Debugger/PceVdcTools.h | 2 +- Core/SNES/Debugger/SnesPpuTools.cpp | 37 +++- Core/SNES/Debugger/SnesPpuTools.h | 2 +- InteropDLL/DebugApiWrapper.cpp | 3 +- NewUI/App.axaml | 8 + NewUI/Config/Debugger/DebugConfig.cs | 1 + .../Debugger/DebuggerShortcutsConfig.cs | 16 +- NewUI/Config/Debugger/TileEditorConfig.cs | 11 ++ NewUI/Config/Debugger/TileViewerConfig.cs | 1 - NewUI/Debugger/Controls/DynamicTooltip.axaml | 2 +- NewUI/Debugger/Controls/PaletteSelector.cs | 3 + NewUI/Debugger/Controls/PictureViewer.cs | 36 +++- .../Controls/ScrollPictureViewer.axaml | 42 ++-- .../Controls/ScrollPictureViewer.axaml.cs | 10 +- NewUI/Debugger/Utilities/ContextMenuAction.cs | 7 + .../Debugger/Utilities/DebugWindowManager.cs | 8 +- .../DebuggerConfigWindowViewModel.cs | 5 +- .../Debugger/ViewModels/SpritePreviewModel.cs | 14 ++ .../ViewModels/SpriteViewerViewModel.cs | 35 +++- .../ViewModels/TileEditorViewModel.cs | 179 ++++++++++++++++++ .../ViewModels/TileViewerViewModel.cs | 152 +++++++++------ .../ViewModels/TilemapViewerViewModel.cs | 70 ++++++- NewUI/Debugger/Windows/TileEditorWindow.axaml | 80 ++++++++ .../Windows/TileEditorWindow.axaml.cs | 90 +++++++++ NewUI/Debugger/Windows/TileViewerWindow.axaml | 2 +- NewUI/Interop/DebugApi.cs | 48 ++++- NewUI/Localization/resources.en.xml | 21 ++ NewUI/NewUI.csproj | 3 + 36 files changed, 903 insertions(+), 141 deletions(-) create mode 100644 NewUI/Config/Debugger/TileEditorConfig.cs create mode 100644 NewUI/Debugger/ViewModels/TileEditorViewModel.cs create mode 100644 NewUI/Debugger/Windows/TileEditorWindow.axaml create mode 100644 NewUI/Debugger/Windows/TileEditorWindow.axaml.cs diff --git a/Core/Debugger/DebugTypes.h b/Core/Debugger/DebugTypes.h index 58c6be5a..54c341b9 100644 --- a/Core/Debugger/DebugTypes.h +++ b/Core/Debugger/DebugTypes.h @@ -213,11 +213,12 @@ enum class TileLayout enum class TileBackground { - Default = 0, - PaletteColor = 1, - Black = 2, - White = 3, - Magenta = 4 + Default, + Transparent, + PaletteColor, + Black, + White, + Magenta, }; enum class TileFilter @@ -246,6 +247,11 @@ struct GetSpritePreviewOptions int32_t SelectedSprite; }; +struct GetPaletteInfoOptions +{ + TileFormat Format; +}; + enum class StackFrameFlags { None = 0, diff --git a/Core/Debugger/PpuTools.cpp b/Core/Debugger/PpuTools.cpp index 4965f3ec..3b94a08c 100644 --- a/Core/Debugger/PpuTools.cpp +++ b/Core/Debugger/PpuTools.cpp @@ -164,6 +164,7 @@ void PpuTools::GetTileView(GetTileViewOptions options, uint8_t *source, uint32_t case TileBackground::Black: bgColor = 0xFF000000; break; case TileBackground::White: bgColor = 0xFFFFFFFF; break; case TileBackground::Magenta: bgColor = 0xFFFF00FF; break; + case TileBackground::Transparent: bgColor = 0; break; } uint32_t outputSize = tileCount * tileWidth * tileHeight; @@ -244,6 +245,96 @@ void PpuTools::RemoveViewer(uint32_t viewerId) _updateTimings.erase(viewerId); } +void PpuTools::SetTilePixel(AddressInfo tileAddress, TileFormat format, int32_t x, int32_t y, int32_t color) +{ + ConsoleMemoryInfo memInfo = _emu->GetMemory(tileAddress.Type); + if(!memInfo.Memory || memInfo.Size == 0) { + return; + } + + int rowOffset; + switch(format) { + default: rowOffset = 2; break; + case TileFormat::Mode7: + case TileFormat::Mode7DirectColor: + case TileFormat::Mode7ExtBg: + rowOffset = 16; + break; + + case TileFormat::NesBpp2: rowOffset = 1; break; + case TileFormat::PceSpriteBpp4: rowOffset = 2; break; + } + + uint8_t* ram = (uint8_t*)memInfo.Memory; + int rowStart = tileAddress.Address + (y * rowOffset); + int ramMask = (memInfo.Size - 1); + + uint8_t shift = (7 - x); + + auto setBit = [&](uint32_t addr, uint8_t bitNumber, uint8_t bitValue) { + ram[addr & ramMask] &= ~(1 << bitNumber); + ram[addr & ramMask] |= (bitValue & 0x01) << bitNumber; + }; + + switch(format) { + case TileFormat::PceSpriteBpp4: + { + shift = 15 - x; + if(shift >= 8) { + shift -= 8; + rowStart++; + } + + setBit(rowStart, shift, color & 0x01); + setBit(rowStart + 32, shift, (color & 0x02) >> 1); + setBit(rowStart + 64, shift, (color & 0x04) >> 2); + setBit(rowStart + 96, shift, (color & 0x08) >> 3); + break; + } + + case TileFormat::Bpp2: + setBit(rowStart, shift, color & 0x01); + setBit(rowStart + 1, shift, (color & 0x02) >> 1); + break; + + case TileFormat::NesBpp2: + setBit(rowStart, shift, color & 0x01); + setBit(rowStart + 8, shift, (color & 0x02) >> 1); + break; + + case TileFormat::Bpp4: + setBit(rowStart, shift, color & 0x01); + setBit(rowStart + 1, shift, (color & 0x02) >> 1); + setBit(rowStart + 16, shift, (color & 0x04) >> 2); + setBit(rowStart + 17, shift, (color & 0x08) >> 3); + break; + + case TileFormat::Bpp8: + case TileFormat::DirectColor: + setBit(rowStart, shift, color & 0x01); + setBit(rowStart + 1, shift, (color & 0x02) >> 1); + setBit(rowStart + 16, shift, (color & 0x04) >> 2); + setBit(rowStart + 17, shift, (color & 0x08) >> 3); + setBit(rowStart + 32, shift, (color & 0x10) >> 4); + setBit(rowStart + 33, shift, (color & 0x20) >> 5); + setBit(rowStart + 48, shift, (color & 0x40) >> 6); + setBit(rowStart + 49, shift, (color & 0x80) >> 7); + break; + + case TileFormat::Mode7: + case TileFormat::Mode7DirectColor: + ram[(rowStart + x * 2 + 1) & ramMask] = color; + break; + + case TileFormat::Mode7ExtBg: + ram[(rowStart + x * 2 + 1) & ramMask] = color; + break; + + default: + throw std::runtime_error("unsupported format"); + } +} + void PpuTools::UpdateViewers(uint16_t scanline, uint16_t cycle) { for(auto updateTiming : _updateTimings) { diff --git a/Core/Debugger/PpuTools.h b/Core/Debugger/PpuTools.h index e7f37d06..f2f181ff 100644 --- a/Core/Debugger/PpuTools.h +++ b/Core/Debugger/PpuTools.h @@ -57,6 +57,8 @@ struct DebugSpriteInfo bool UseExtendedVram; NullableBoolean UseSecondTable; + uint32_t TileCount; + uint32_t TileAddresses[8 * 8]; uint32_t SpritePreview[64 * 64]; public: @@ -65,6 +67,7 @@ public: TileIndex = -1; TileAddress = -1; PaletteAddress = -1; + Format = {}; SpriteIndex = -1; X = -1; Y = -1; @@ -78,7 +81,9 @@ public: HorizontalMirror = false; VerticalMirror = false; Visible = false; + UseExtendedVram = false; UseSecondTable = NullableBoolean::Undefined; + TileCount = 0; } }; @@ -180,7 +185,7 @@ protected: public: PpuTools(Debugger* debugger, Emulator *emu); - virtual DebugPaletteInfo GetPaletteInfo() = 0; + virtual DebugPaletteInfo GetPaletteInfo(GetPaletteInfoOptions options) = 0; void GetTileView(GetTileViewOptions options, uint8_t *source, uint32_t srcSize, const uint32_t* palette, uint32_t *outBuffer); @@ -192,6 +197,8 @@ public: virtual void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t* outBuffer) = 0; virtual void GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[]) = 0; + void SetTilePixel(AddressInfo tileAddress, TileFormat format, int32_t x, int32_t y, int32_t color); + virtual void SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle); void RemoveViewer(uint32_t viewerId); diff --git a/Core/Gameboy/Debugger/GbPpuTools.cpp b/Core/Gameboy/Debugger/GbPpuTools.cpp index 621c5d78..295adaff 100644 --- a/Core/Gameboy/Debugger/GbPpuTools.cpp +++ b/Core/Gameboy/Debugger/GbPpuTools.cpp @@ -107,7 +107,7 @@ FrameInfo GbPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state DebugTilemapTileInfo GbPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) { - DebugTilemapTileInfo result; + DebugTilemapTileInfo result = {}; FrameInfo size = GetTilemapSize(options, baseState); @@ -192,11 +192,18 @@ void GbPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint16_t i, GetSpritePre uint8_t tileIndex = (uint8_t)sprite.TileIndex; uint16_t tileBank = useSecondTable ? 0x2000 : 0x0000; + uint16_t tileStart; if(state.LargeSprites) { - tileIndex &= 0xFE; + tileStart = (tileIndex & 0xFE) * 16; + sprite.TileAddresses[0] = tileStart; + sprite.TileAddresses[1] = tileStart + 16; + sprite.TileCount = 2; + } else { + tileStart = tileIndex * 16; + sprite.TileAddresses[0] = tileStart; + sprite.TileCount = 1; } - uint16_t tileStart = tileIndex * 16; tileStart |= tileBank; sprite.TileAddress = tileStart; @@ -231,7 +238,7 @@ void GbPpuTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseS } } -DebugPaletteInfo GbPpuTools::GetPaletteInfo() +DebugPaletteInfo GbPpuTools::GetPaletteInfo(GetPaletteInfoOptions options) { DebugPaletteInfo info = {}; GbPpuState state; diff --git a/Core/Gameboy/Debugger/GbPpuTools.h b/Core/Gameboy/Debugger/GbPpuTools.h index 1f4a1373..9fef5a6b 100644 --- a/Core/Gameboy/Debugger/GbPpuTools.h +++ b/Core/Gameboy/Debugger/GbPpuTools.h @@ -21,5 +21,5 @@ public: DebugSpritePreviewInfo GetSpritePreviewInfo(GetSpritePreviewOptions options, BaseState& state) override; void GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[]) override; - DebugPaletteInfo GetPaletteInfo() override; + DebugPaletteInfo GetPaletteInfo(GetPaletteInfoOptions options) override; }; \ No newline at end of file diff --git a/Core/NES/Debugger/NesPpuTools.cpp b/Core/NES/Debugger/NesPpuTools.cpp index 1b475be0..df55d7f9 100644 --- a/Core/NES/Debugger/NesPpuTools.cpp +++ b/Core/NES/Debugger/NesPpuTools.cpp @@ -227,7 +227,7 @@ FrameInfo NesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& stat DebugTilemapTileInfo NesPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) { - DebugTilemapTileInfo result; + DebugTilemapTileInfo result = {}; FrameInfo size = GetTilemapSize(options, baseState); if(x >= size.Width || y >= size.Height) { @@ -308,8 +308,12 @@ void NesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t i, GetSpritePr } else { tileStart = 0x0000 | (sprite.TileIndex * 16); } + sprite.TileAddresses[0] = tileStart; + sprite.TileAddresses[1] = tileStart + 16; + sprite.TileCount = 2; } else { tileStart = (sprite.TileIndex * 16) | sprAddr; + sprite.TileCount = 1; } sprite.TileAddress = tileStart; @@ -354,7 +358,7 @@ DebugSpritePreviewInfo NesPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions return info; } -DebugPaletteInfo NesPpuTools::GetPaletteInfo() +DebugPaletteInfo NesPpuTools::GetPaletteInfo(GetPaletteInfoOptions options) { DebugPaletteInfo info = {}; info.RawFormat = RawPaletteFormat::Indexed; diff --git a/Core/NES/Debugger/NesPpuTools.h b/Core/NES/Debugger/NesPpuTools.h index 0c53ba10..0cfd3a61 100644 --- a/Core/NES/Debugger/NesPpuTools.h +++ b/Core/NES/Debugger/NesPpuTools.h @@ -25,5 +25,5 @@ public: void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t *outBuffer) override; void GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[]) override; - DebugPaletteInfo GetPaletteInfo() override; + DebugPaletteInfo GetPaletteInfo(GetPaletteInfoOptions options) override; }; \ No newline at end of file diff --git a/Core/PCE/Debugger/PceVdcTools.cpp b/Core/PCE/Debugger/PceVdcTools.cpp index afec23ea..4fb7a1d0 100644 --- a/Core/PCE/Debugger/PceVdcTools.cpp +++ b/Core/PCE/Debugger/PceVdcTools.cpp @@ -31,7 +31,7 @@ FrameInfo PceVdcTools::GetTilemapSize(GetTilemapOptions options, BaseState& base DebugTilemapTileInfo PceVdcTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) { - DebugTilemapTileInfo result; + DebugTilemapTileInfo result = {}; FrameInfo size = GetTilemapSize(options, baseState); if(x >= size.Width || y >= size.Height) { @@ -196,8 +196,15 @@ void PceVdcTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint16_t spriteIndex, G } else if(height == 64) { tileIndex &= ~0x06; } - + sprite.TileAddress = tileIndex * 64 * 2; + sprite.TileCount = 0; + for(int i = 0, rowCount = height / 16; i < rowCount; i++) { + for(int j = 0, columnCount = width / 16; j < columnCount; j++) { + sprite.TileAddresses[sprite.TileCount] = sprite.TileAddress + (i * columnCount + j) * 128; + sprite.TileCount++; + } + } uint8_t yOffset; int rowOffset; @@ -267,7 +274,7 @@ DebugSpritePreviewInfo PceVdcTools::GetSpritePreviewInfo(GetSpritePreviewOptions return info; } -DebugPaletteInfo PceVdcTools::GetPaletteInfo() +DebugPaletteInfo PceVdcTools::GetPaletteInfo(GetPaletteInfoOptions options) { DebugPaletteInfo info = {}; info.RawFormat = RawPaletteFormat::Rgb333; diff --git a/Core/PCE/Debugger/PceVdcTools.h b/Core/PCE/Debugger/PceVdcTools.h index fd43a310..5a86474d 100644 --- a/Core/PCE/Debugger/PceVdcTools.h +++ b/Core/PCE/Debugger/PceVdcTools.h @@ -27,5 +27,5 @@ public: void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t *outBuffer) override; void GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[]) override; - DebugPaletteInfo GetPaletteInfo() override; + DebugPaletteInfo GetPaletteInfo(GetPaletteInfoOptions options) override; }; \ No newline at end of file diff --git a/Core/SNES/Debugger/SnesPpuTools.cpp b/Core/SNES/Debugger/SnesPpuTools.cpp index 33b09965..8bed9932 100644 --- a/Core/SNES/Debugger/SnesPpuTools.cpp +++ b/Core/SNES/Debugger/SnesPpuTools.cpp @@ -253,6 +253,17 @@ void SnesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint16_t spriteIndex, int tileRow = (sprite.TileIndex & 0xF0) >> 4; int tileColumn = sprite.TileIndex & 0x0F; + + sprite.TileCount = 0; + for(int i = 0, rowCount = height / 8; i < rowCount; i++) { + int row = (i + tileRow) & 0x0F; + for(int j = 0, columnCount = width / 8; j < columnCount; j++) { + int col = (j + tileColumn) & 0x0F; + sprite.TileAddresses[sprite.TileCount] = ((state.OamBaseAddress + (row * 16 + col) * 16 + (useSecondTable ? state.OamAddressOffset : 0)) & 0x7FFF) << 1; + sprite.TileCount++; + } + } + uint8_t yOffset; int rowOffset; @@ -341,7 +352,7 @@ FrameInfo SnesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& bas DebugTilemapTileInfo SnesPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) { - DebugTilemapTileInfo result; + DebugTilemapTileInfo result = {}; FrameInfo size = GetTilemapSize(options, baseState); if(x >= size.Width || y >= size.Height) { @@ -417,7 +428,7 @@ DebugSpritePreviewInfo SnesPpuTools::GetSpritePreviewInfo(GetSpritePreviewOption return info; } -DebugPaletteInfo SnesPpuTools::GetPaletteInfo() +DebugPaletteInfo SnesPpuTools::GetPaletteInfo(GetPaletteInfoOptions options) { DebugPaletteInfo info = {}; info.RawFormat = RawPaletteFormat::Rgb555; @@ -426,11 +437,23 @@ DebugPaletteInfo SnesPpuTools::GetPaletteInfo() info.SpriteColorCount = 16 * 8; info.ColorCount = info.BgColorCount + info.SpriteColorCount; - uint8_t* cgram= _debugger->GetMemoryDumper()->GetMemoryBuffer(MemoryType::SnesCgRam); - for(int i = 0; i < 256; i++) { - info.RawPalette[i] = cgram[i*2] | (cgram[i*2+1] << 8); - info.RgbPalette[i] = SnesDefaultVideoFilter::ToArgb(info.RawPalette[i]); - } + switch(options.Format) { + case TileFormat::DirectColor: + case TileFormat::Mode7DirectColor: + for(int i = 0; i < 256; i++) { + info.RawPalette[i] = ((i & 0x07) << 2) | ((i & 0x38) << 4) | ((i & 0xC0) << 7); + info.RgbPalette[i] = SnesDefaultVideoFilter::ToArgb(info.RawPalette[i]); + } + break; + default: + uint8_t mask = options.Format == TileFormat::Mode7ExtBg ? 0x7F : 0xFF; + uint8_t* cgram = _debugger->GetMemoryDumper()->GetMemoryBuffer(MemoryType::SnesCgRam); + for(int i = 0; i < 256; i++) { + info.RawPalette[i] = cgram[(i & mask) * 2] | (cgram[(i & mask) * 2 + 1] << 8); + info.RgbPalette[i] = SnesDefaultVideoFilter::ToArgb(info.RawPalette[i]); + } + break; + } return info; } diff --git a/Core/SNES/Debugger/SnesPpuTools.h b/Core/SNES/Debugger/SnesPpuTools.h index 09333927..1e72b670 100644 --- a/Core/SNES/Debugger/SnesPpuTools.h +++ b/Core/SNES/Debugger/SnesPpuTools.h @@ -22,5 +22,5 @@ public: void GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[]) override; DebugSpritePreviewInfo GetSpritePreviewInfo(GetSpritePreviewOptions options, BaseState& state) override; - DebugPaletteInfo GetPaletteInfo() override; + DebugPaletteInfo GetPaletteInfo(GetPaletteInfoOptions options) override; }; \ No newline at end of file diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp index f30385de..b1fe9dee 100644 --- a/InteropDLL/DebugApiWrapper.cpp +++ b/InteropDLL/DebugApiWrapper.cpp @@ -152,7 +152,8 @@ extern "C" DllExport void __stdcall GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t* buffer) { WithToolVoid(GetPpuTools(cpuType), GetSpritePreview(options, state, vram, oamRam, palette, buffer)); } DllExport void __stdcall GetSpriteList(CpuType cpuType, GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo sprites[]) { WithToolVoid(GetPpuTools(cpuType), GetSpriteList(options, state, vram, oamRam, palette, sprites)); } - DllExport DebugPaletteInfo __stdcall GetPaletteInfo(CpuType cpuType) { return WithTool(DebugPaletteInfo, GetPpuTools(cpuType), GetPaletteInfo()); } + DllExport DebugPaletteInfo __stdcall GetPaletteInfo(CpuType cpuType, GetPaletteInfoOptions options) { return WithTool(DebugPaletteInfo, GetPpuTools(cpuType), GetPaletteInfo(options)); } + DllExport void __stdcall SetTilePixel(AddressInfo tileAddress, TileFormat format, int32_t x, int32_t y, int32_t color) { WithToolVoid(GetPpuTools(DebugUtilities::ToCpuType(tileAddress.Type)), SetTilePixel(tileAddress, format, x, y, color)); } DllExport void __stdcall SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle, CpuType cpuType) { WithToolVoid(GetPpuTools(cpuType), SetViewerUpdateTiming(viewerId, scanline, cycle)); } diff --git a/NewUI/App.axaml b/NewUI/App.axaml index a1366e18..b08c71d1 100644 --- a/NewUI/App.axaml +++ b/NewUI/App.axaml @@ -9,6 +9,14 @@ + + + + + + + + diff --git a/NewUI/Config/Debugger/DebugConfig.cs b/NewUI/Config/Debugger/DebugConfig.cs index b3468e76..5618c0e4 100644 --- a/NewUI/Config/Debugger/DebugConfig.cs +++ b/NewUI/Config/Debugger/DebugConfig.cs @@ -20,6 +20,7 @@ namespace Mesen.Config public TilemapViewerConfig TilemapViewer { get; set; } = new TilemapViewerConfig(); public TileViewerConfig TileViewer { get; set; } = new TileViewerConfig(); public PaletteViewerConfig PaletteViewer { get; set; } = new PaletteViewerConfig(); + public TileEditorConfig TileEditor { get; set; } = new TileEditorConfig(); public RegisterViewerConfig RegisterViewer { get; set; } = new RegisterViewerConfig(); public SpriteViewerConfig SpriteViewer { get; set; } = new SpriteViewerConfig(); public IntegrationConfig Integration { get; set; } = new IntegrationConfig(); diff --git a/NewUI/Config/Debugger/DebuggerShortcutsConfig.cs b/NewUI/Config/Debugger/DebuggerShortcutsConfig.cs index baf758d4..f110f8bb 100644 --- a/NewUI/Config/Debugger/DebuggerShortcutsConfig.cs +++ b/NewUI/Config/Debugger/DebuggerShortcutsConfig.cs @@ -202,14 +202,17 @@ namespace Mesen.Config Add(new() { Shortcut = DebuggerShortcut.ResetWorkspace, KeyBinding = new() }); Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_ViewInMemoryViewer, KeyBinding = new(Key.F1) }); - Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_ViewInTileViewer, KeyBinding = new(Key.F2) }); - Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_EditTilemapBreakpoint, KeyBinding = new(Key.F3) }); - Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_EditAttributeBreakpoint, KeyBinding = new(Key.F4) }); + Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_EditTile, KeyBinding = new(Key.F2) }); + Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_ViewInTileViewer, KeyBinding = new(Key.F3) }); + Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_EditTilemapBreakpoint, KeyBinding = new(Key.F4) }); + Add(new() { Shortcut = DebuggerShortcut.TilemapViewer_EditAttributeBreakpoint, KeyBinding = new() }); Add(new() { Shortcut = DebuggerShortcut.SpriteViewer_ViewInMemoryViewer, KeyBinding = new(Key.F1) }); - Add(new() { Shortcut = DebuggerShortcut.SpriteViewer_ViewInTileViewer, KeyBinding = new(Key.F2) }); - + Add(new() { Shortcut = DebuggerShortcut.SpriteViewer_ViewInTileViewer, KeyBinding = new(Key.F3) }); + Add(new() { Shortcut = DebuggerShortcut.SpriteViewer_EditSprite, KeyBinding = new(Key.F2) }); + Add(new() { Shortcut = DebuggerShortcut.TileViewer_ViewInMemoryViewer, KeyBinding = new(Key.F1) }); + Add(new() { Shortcut = DebuggerShortcut.TileViewer_EditTile, KeyBinding = new(Key.F2) }); //Memory Tools //Add(new() { Shortcut = eDebuggerShortcut.MemoryViewer_Freeze, KeyBinding = new(KeyModifiers.Control, Key.Q) }); @@ -358,9 +361,12 @@ namespace Mesen.Config TilemapViewer_EditAttributeBreakpoint, TilemapViewer_ViewInTileViewer, TilemapViewer_ViewInMemoryViewer, + TilemapViewer_EditTile, SpriteViewer_ViewInMemoryViewer, SpriteViewer_ViewInTileViewer, + SpriteViewer_EditSprite, TileViewer_ViewInMemoryViewer, + TileViewer_EditTile, } public class DebuggerShortcutInfo : ViewModelBase diff --git a/NewUI/Config/Debugger/TileEditorConfig.cs b/NewUI/Config/Debugger/TileEditorConfig.cs new file mode 100644 index 00000000..98994437 --- /dev/null +++ b/NewUI/Config/Debugger/TileEditorConfig.cs @@ -0,0 +1,11 @@ +using Mesen.Interop; +using ReactiveUI.Fody.Helpers; + +namespace Mesen.Config +{ + public class TileEditorConfig : BaseWindowConfig + { + [Reactive] public int ImageScale { get; set; } = 8; + [Reactive] public TileBackground Background { get; set; } = TileBackground.Transparent; + } +} diff --git a/NewUI/Config/Debugger/TileViewerConfig.cs b/NewUI/Config/Debugger/TileViewerConfig.cs index bef09d11..7da83fcc 100644 --- a/NewUI/Config/Debugger/TileViewerConfig.cs +++ b/NewUI/Config/Debugger/TileViewerConfig.cs @@ -18,7 +18,6 @@ namespace Mesen.Config [Reactive] public int RowCount { get; set; } = 64; [Reactive] public int ColumnCount { get; set; } = 32; [Reactive] public int StartAddress { get; set; } = 0; - [Reactive] public int SelectedPalette { get; set; } = 0; [Reactive] public bool UseGrayscalePalette { get; set; } = false; [Reactive] public RefreshTimingConfig RefreshTiming { get; set; } = new(); diff --git a/NewUI/Debugger/Controls/DynamicTooltip.axaml b/NewUI/Debugger/Controls/DynamicTooltip.axaml index 16742b0c..c56c6206 100644 --- a/NewUI/Debugger/Controls/DynamicTooltip.axaml +++ b/NewUI/Debugger/Controls/DynamicTooltip.axaml @@ -30,7 +30,7 @@ MinWidth="{Binding FirstColumnWidth, ElementName=root}" IsVisible="{Binding Name.Length}" Margin="0 3 10 2" - /> + /> diff --git a/NewUI/Debugger/Controls/PaletteSelector.cs b/NewUI/Debugger/Controls/PaletteSelector.cs index 1144b74e..0be8f832 100644 --- a/NewUI/Debugger/Controls/PaletteSelector.cs +++ b/NewUI/Debugger/Controls/PaletteSelector.cs @@ -324,6 +324,9 @@ namespace Mesen.Debugger.Controls RaiseEvent(new ColorClickEventArgs() { ColorIndex = paletteIndex, Color = Color.FromUInt32(PaletteColors[paletteIndex]) }); + if(SelectionMode == PaletteSelectionMode.None) { + return; + } if(SelectionMode == PaletteSelectionMode.SingleColor) { paletteIndex /= 1; } else if(SelectionMode == PaletteSelectionMode.FourColors) { diff --git a/NewUI/Debugger/Controls/PictureViewer.cs b/NewUI/Debugger/Controls/PictureViewer.cs index c4af87a7..55c8a438 100644 --- a/NewUI/Debugger/Controls/PictureViewer.cs +++ b/NewUI/Debugger/Controls/PictureViewer.cs @@ -214,12 +214,12 @@ namespace Mesen.Debugger.Controls public void ZoomIn() { - Zoom = Math.Min(20, Math.Max(1, Zoom + 1)); + Zoom = Math.Min(40, Math.Max(1, Zoom + 1)); } public void ZoomOut() { - Zoom = Math.Min(20, Math.Max(1, Zoom - 1)); + Zoom = Math.Min(40, Math.Max(1, Zoom - 1)); } public async void ExportToPng() @@ -247,12 +247,24 @@ namespace Mesen.Debugger.Controls protected override void OnPointerMoved(PointerEventArgs e) { base.OnPointerMoved(e); + PixelPoint? p = GetGridPointFromMousePoint(e.GetCurrentPoint(this).Position); + if(p == null) { + e.Handled = true; + MouseOverRect = null; + return; + } + if(ShowMousePosition) { - PixelPoint? p = GetGridPointFromMousePoint(e.GetCurrentPoint(this).Position); - if(p != null) { - MouseOverRect = GetTileRect(p.Value); - } else { - MouseOverRect = null; + MouseOverRect = GetTileRect(p.Value); + } + + PointerPointProperties props = e.GetCurrentPoint(this).Properties; + if(props.IsLeftButtonPressed || props.IsRightButtonPressed) { + PositionClickedEventArgs args = new(p.Value, props, PositionClickedEvent); + RaiseEvent(args); + + if(!args.Handled && AllowSelection) { + SelectionRect = GetTileRect(p.Value); } } } @@ -271,7 +283,7 @@ namespace Mesen.Debugger.Controls return; } - PositionClickedEventArgs args = new() { RoutedEvent = PositionClickedEvent, Position = p.Value }; + PositionClickedEventArgs args = new(p.Value, e.GetCurrentPoint(this).Properties, PositionClickedEvent); RaiseEvent(args); if(!args.Handled && AllowSelection) { @@ -441,6 +453,14 @@ namespace Mesen.Debugger.Controls public class PositionClickedEventArgs : RoutedEventArgs { public PixelPoint Position; + public PointerPointProperties Properties; + + public PositionClickedEventArgs(PixelPoint position, PointerPointProperties properties, RoutedEvent evt) + { + Position = position; + Properties = properties; + RoutedEvent = evt; + } } public class GridRowColumn diff --git a/NewUI/Debugger/Controls/ScrollPictureViewer.axaml b/NewUI/Debugger/Controls/ScrollPictureViewer.axaml index 3def6677..bed8116b 100644 --- a/NewUI/Debugger/Controls/ScrollPictureViewer.axaml +++ b/NewUI/Debugger/Controls/ScrollPictureViewer.axaml @@ -9,7 +9,7 @@ x:Name="root" x:Class="Mesen.Debugger.Controls.ScrollPictureViewer" > - + - + + \ No newline at end of file diff --git a/NewUI/Debugger/Controls/ScrollPictureViewer.axaml.cs b/NewUI/Debugger/Controls/ScrollPictureViewer.axaml.cs index 6c4cdfab..cb375abf 100644 --- a/NewUI/Debugger/Controls/ScrollPictureViewer.axaml.cs +++ b/NewUI/Debugger/Controls/ScrollPictureViewer.axaml.cs @@ -25,6 +25,7 @@ namespace Mesen.Debugger.Controls public static readonly StyledProperty AltGridSizeYProperty = AvaloniaProperty.Register(nameof(AltGridSizeY), 8); public static readonly StyledProperty ShowAltGridProperty = AvaloniaProperty.Register(nameof(ShowAltGrid), false); public static readonly StyledProperty AllowSelectionProperty = AvaloniaProperty.Register(nameof(AllowSelection), true); + public static readonly StyledProperty AllowClickDragProperty = AvaloniaProperty.Register(nameof(AllowClickDrag), true); public static readonly StyledProperty GridHighlightProperty = AvaloniaProperty.Register(nameof(GridHighlight), null); @@ -102,6 +103,12 @@ namespace Mesen.Debugger.Controls set { SetValue(ShowAltGridProperty, value); } } + public bool AllowClickDrag + { + get { return GetValue(AllowClickDragProperty); } + set { SetValue(AllowClickDragProperty, value); } + } + public Rect SelectionRect { get { return GetValue(SelectionRectProperty); } @@ -141,6 +148,7 @@ namespace Mesen.Debugger.Controls public ScrollPictureViewer() { InitializeComponent(); + Background = new SolidColorBrush(0xFF202020); } private void InitializeComponent() @@ -157,7 +165,7 @@ namespace Mesen.Debugger.Controls private void Viewer_PointerMoved(object? sender, PointerEventArgs e) { - if(e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { + if(AllowClickDrag && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { Vector offset = ScrollOffset; offset -= e.GetPosition(this) - _lastPosition; if(offset.X < 0) { diff --git a/NewUI/Debugger/Utilities/ContextMenuAction.cs b/NewUI/Debugger/Utilities/ContextMenuAction.cs index d0c193b1..d094484c 100644 --- a/NewUI/Debugger/Utilities/ContextMenuAction.cs +++ b/NewUI/Debugger/Utilities/ContextMenuAction.cs @@ -665,5 +665,12 @@ namespace Mesen.Debugger.Utilities FindNext, [IconFile("PreviousArrow")] FindPrev, + + [IconFile("Edit")] + EditTile, + [IconFile("Edit")] + EditTiles, + [IconFile("Edit")] + EditSprite, } } diff --git a/NewUI/Debugger/Utilities/DebugWindowManager.cs b/NewUI/Debugger/Utilities/DebugWindowManager.cs index b30c5bd6..7a29a9ca 100644 --- a/NewUI/Debugger/Utilities/DebugWindowManager.cs +++ b/NewUI/Debugger/Utilities/DebugWindowManager.cs @@ -16,7 +16,7 @@ namespace Mesen.Debugger.Utilities private static object _windowNotifLock = new(); private static bool _loadingGame = false; - public static T OpenDebugWindow(Func createWindow) where T : Window + public static T CreateDebugWindow(Func createWindow) where T : Window { if(Interlocked.Increment(ref _debugWindowCounter) == 1) { //Opened a debug window and nothing else was opened, load the saved workspace @@ -35,6 +35,12 @@ namespace Mesen.Debugger.Utilities } }; _openedWindows.TryAdd(wnd, true); + return wnd; + } + + public static T OpenDebugWindow(Func createWindow) where T : Window + { + T wnd = CreateDebugWindow(createWindow); wnd.Show(); return wnd; } diff --git a/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs b/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs index 18c832a6..2ee9b6b8 100644 --- a/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs +++ b/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs @@ -109,9 +109,12 @@ namespace Mesen.Debugger.ViewModels DebuggerShortcut.TilemapViewer_ViewInTileViewer, DebuggerShortcut.TilemapViewer_EditTilemapBreakpoint, DebuggerShortcut.TilemapViewer_EditAttributeBreakpoint, + DebuggerShortcut.TilemapViewer_EditTile, DebuggerShortcut.TileViewer_ViewInMemoryViewer, + DebuggerShortcut.TileViewer_EditTile, DebuggerShortcut.SpriteViewer_ViewInMemoryViewer, - DebuggerShortcut.SpriteViewer_ViewInTileViewer + DebuggerShortcut.SpriteViewer_ViewInTileViewer, + DebuggerShortcut.SpriteViewer_EditSprite }); DebuggerShortcuts = CreateShortcutList(new DebuggerShortcut[] { diff --git a/NewUI/Debugger/ViewModels/SpritePreviewModel.cs b/NewUI/Debugger/ViewModels/SpritePreviewModel.cs index e3e4d873..26aec5f0 100644 --- a/NewUI/Debugger/ViewModels/SpritePreviewModel.cs +++ b/NewUI/Debugger/ViewModels/SpritePreviewModel.cs @@ -32,6 +32,10 @@ namespace Mesen.Debugger.ViewModels [Reactive] public bool HorizontalMirror { get; set; } [Reactive] public bool VerticalMirror { get; set; } [Reactive] public bool UseExtendedVram { get; set; } + + public UInt32 TileCount { get; set; } + public UInt32[] TileAddresses { get; set; } = Array.Empty(); + [Reactive] public NullableBoolean UseSecondTable { get; set; } [Reactive] public DynamicBitmap? SpritePreview { get; set; } @@ -60,6 +64,16 @@ namespace Mesen.Debugger.ViewModels UseSecondTable = sprite.UseSecondTable; UseExtendedVram = sprite.UseExtendedVram; + TileCount = sprite.TileCount; + fixed(UInt32* p = sprite.TileAddresses) { + if(TileAddresses == null || TileAddresses.Length < TileCount) { + TileAddresses = new UInt32[TileCount]; + } + for(int i = 0; i < sprite.TileCount; i++) { + TileAddresses[i] = p[i]; + } + } + fixed(UInt32* p = sprite.SpritePreview) { if(SpritePreview == null || SpritePreview.PixelSize.Width != sprite.Width || SpritePreview.PixelSize.Height != sprite.Height) { SpritePreview = new DynamicBitmap(new PixelSize(Width, Height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul); diff --git a/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs b/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs index c6f2edb2..1e12b349 100644 --- a/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs +++ b/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Mesen.Debugger.ViewModels { @@ -110,7 +111,12 @@ namespace Mesen.Debugger.ViewModels }, }); + if(Design.IsDesignMode || wnd == null) { + return; + } + DebugShortcutManager.CreateContextMenu(picViewer, new List { + GetEditTileAction(wnd), GetViewInMemoryViewerAction(), GetViewInTileViewerAction(), new ContextMenuSeparator(), @@ -121,19 +127,17 @@ namespace Mesen.Debugger.ViewModels }); DebugShortcutManager.CreateContextMenu(_spriteGrid, new List { + GetEditTileAction(wnd), GetViewInMemoryViewerAction(), GetViewInTileViewerAction() }); DebugShortcutManager.CreateContextMenu(listView, new List { + GetEditTileAction(wnd), GetViewInMemoryViewerAction(), GetViewInTileViewerAction() }); - if(Design.IsDesignMode || wnd == null) { - return; - } - AddDisposable(this.WhenAnyValue(x => x.SelectedSprite).Subscribe(x => { UpdateSelectionPreview(); if(x != null) { @@ -153,6 +157,29 @@ namespace Mesen.Debugger.ViewModels DebugShortcutManager.RegisterActions(wnd, ViewMenuActions); } + private ContextMenuAction GetEditTileAction(Window wnd) + { + return new ContextMenuAction() { + ActionType = ActionType.EditSprite, + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.SpriteViewer_EditSprite), + IsEnabled = () => GetSelectedSprite() != null, + OnClick = () => { + SpritePreviewModel? sprite = GetSelectedSprite(); + if(sprite?.TileAddress >= 0 && _palette != null) { + PixelSize size = sprite.Format.GetTileSize(); + DebugPaletteInfo pal = _palette.Get(); + int paletteOffset = (int)(pal.BgColorCount / pal.ColorsPerPalette); + TileEditorWindow.OpenAtTile( + sprite.TileAddresses.Select(x => new AddressInfo() { Address = (int)x, Type = CpuType.GetVramMemoryType(sprite.UseExtendedVram) }).ToList(), + sprite.Width / size.Width, + sprite.Format, + sprite.Palette + paletteOffset, + wnd); + } + } + }; + } + private ContextMenuAction GetViewInMemoryViewerAction() { return new ContextMenuAction() { diff --git a/NewUI/Debugger/ViewModels/TileEditorViewModel.cs b/NewUI/Debugger/ViewModels/TileEditorViewModel.cs new file mode 100644 index 00000000..9db291cd --- /dev/null +++ b/NewUI/Debugger/ViewModels/TileEditorViewModel.cs @@ -0,0 +1,179 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Platform; +using Avalonia.Threading; +using Mesen.Config; +using Mesen.Debugger.Controls; +using Mesen.Debugger.Utilities; +using Mesen.Debugger.Windows; +using Mesen.Interop; +using Mesen.Utilities; +using Mesen.ViewModels; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; + +namespace Mesen.Debugger.ViewModels; + +public class TileEditorViewModel : DisposableViewModel +{ + [Reactive] public DynamicBitmap ViewerBitmap { get; private set; } + + [Reactive] public UInt32[] PaletteColors { get; set; } = Array.Empty(); + [Reactive] public UInt32[] RawPalette { get; set; } = Array.Empty(); + [Reactive] public RawPaletteFormat RawFormat { get; set; } + [Reactive] public int PaletteColumnCount { get; private set; } = 16; + [Reactive] public int SelectedColor { get; set; } = 0; + + public TileEditorConfig Config { get; } + + public List FileMenuActions { get; private set; } = new(); + public List ViewMenuActions { get; private set; } = new(); + + private CpuType _cpuType; + private List _tileAddresses; + private int _columnCount = 1; + private int _rowCount = 1; + private TileFormat _tileFormat; + private byte[] _sourceData = Array.Empty(); + private UInt32[] _tileBuffer = Array.Empty(); + + [Obsolete("For designer only")] + public TileEditorViewModel() : this(new() { new() }, 1, TileFormat.Bpp4, 0) { } + + public TileEditorViewModel(List tileAddresses, int columnCount, TileFormat format, int initialPalette) + { + Config = ConfigManager.Config.Debug.TileEditor; + _tileAddresses = tileAddresses; + _columnCount = columnCount; + _rowCount = tileAddresses.Count / columnCount; + _cpuType = tileAddresses[0].Type.ToCpuType(); + _tileFormat = format; + SelectedColor = initialPalette * GetColorsPerPalette(_tileFormat); + + PixelSize size = format.GetTileSize(); + _tileBuffer = new UInt32[size.Width * size.Height]; + ViewerBitmap = new DynamicBitmap(new PixelSize(size.Width * _columnCount, size.Height * _rowCount), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul); + + if(Design.IsDesignMode) { + return; + } + + AddDisposable(this.WhenAnyValue(x => x.Config.Background).Subscribe(x => RefreshViewer())); + AddDisposable(this.WhenAnyValue(x => x.SelectedColor).Subscribe(x => RefreshViewer())); + AddDisposable(this.WhenAnyValue(x => x.Config.ImageScale).Subscribe(x => { + if(Config.ImageScale < 4) { + Config.ImageScale = 4; + } + })); + + RefreshViewer(); + } + + public void InitActions(PictureViewer picViewer, Window wnd) + { + FileMenuActions = AddDisposables(new List() { + new ContextMenuAction() { + ActionType = ActionType.ExportToPng, + OnClick = () => picViewer.ExportToPng() + }, + new ContextMenuSeparator(), + new ContextMenuAction() { + ActionType = ActionType.Exit, + OnClick = () => wnd.Close() + } + }); + + ViewMenuActions = AddDisposables(new List() { + new ContextMenuAction() { + ActionType = ActionType.ZoomIn, + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ZoomIn), + OnClick = () => picViewer.ZoomIn() + }, + new ContextMenuAction() { + ActionType = ActionType.ZoomOut, + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ZoomOut), + OnClick = () => picViewer.ZoomOut() + }, + }); + + DebugShortcutManager.RegisterActions(wnd, FileMenuActions); + DebugShortcutManager.RegisterActions(wnd, ViewMenuActions); + } + + public void UpdatePixel(PixelPoint position, bool clearPixel) + { + int pixelColor = clearPixel ? 0 : SelectedColor % GetColorsPerPalette(_tileFormat); + PixelSize tileSize = _tileFormat.GetTileSize(); + + int column = position.X / tileSize.Width; + int row = position.Y / tileSize.Height; + int tileX = position.X % tileSize.Width; + int tileY = position.Y % tileSize.Height; + DebugApi.SetTilePixel(_tileAddresses[column+row*_columnCount], _tileFormat, tileX, tileY, pixelColor); + RefreshViewer(); + } + + private unsafe void RefreshViewer() + { + Dispatcher.UIThread.Post((Action)(() => { + DebugPaletteInfo palette = DebugApi.GetPaletteInfo(_cpuType, new GetPaletteInfoOptions() { Format = _tileFormat }); + PaletteColors = palette.GetRgbPalette(); + RawPalette = palette.GetRawPalette(); + RawFormat = palette.RawFormat; + PaletteColumnCount = PaletteColors.Length > 16 ? 16 : 4; + + _sourceData = DebugApi.GetMemoryState(_tileAddresses[0].Type); + PixelSize tileSize = _tileFormat.GetTileSize(); + + using(var framebuffer = ViewerBitmap.Lock()) { + for(int y = 0; y < _rowCount; y++) { + for(int x = 0; x < _columnCount; x++) { + fixed(UInt32* ptr = _tileBuffer) { + DebugApi.GetTileView(_cpuType, GetOptions(x, y), _sourceData, _sourceData.Length, PaletteColors, (IntPtr)ptr); + UInt32* viewer = (UInt32*)framebuffer.FrameBuffer.Address; + int rowPitch = ViewerBitmap.PixelSize.Width; + int baseOffset = x * tileSize.Width + y * tileSize.Height * rowPitch; + for(int j = 0; j < tileSize.Height; j++) { + for(int i = 0; i < tileSize.Width; i++) { + viewer[baseOffset + j * rowPitch + i] = ptr[j * tileSize.Width + i]; + } + } + } + } + } + } + })); + } + + private int GetColorsPerPalette(TileFormat format) + { + return format.GetBitsPerPixel() switch { + 2 => 4, //4-color palettes + 4 => 16, //16-color palettes + _ => 256 + }; + } + + private GetTileViewOptions GetOptions(int column, int row) + { + int tileIndex = row * _columnCount + column; + + return new GetTileViewOptions() { + MemType = _tileAddresses[tileIndex].Type, + Format = _tileFormat, + Width = _tileFormat.GetTileSize().Width / 8, + Height = _tileFormat.GetTileSize().Height / 8, + Palette = SelectedColor / GetColorsPerPalette(_tileFormat), + Layout = TileLayout.Normal, + Filter = TileFilter.None, + StartAddress = _tileAddresses[tileIndex].Address, + Background = Config.Background, + UseGrayscalePalette = false + }; + } +} diff --git a/NewUI/Debugger/ViewModels/TileViewerViewModel.cs b/NewUI/Debugger/ViewModels/TileViewerViewModel.cs index 9a02ac34..5c2effcf 100644 --- a/NewUI/Debugger/ViewModels/TileViewerViewModel.cs +++ b/NewUI/Debugger/ViewModels/TileViewerViewModel.cs @@ -40,6 +40,7 @@ namespace Mesen.Debugger.ViewModels [Reactive] public RawPaletteFormat RawFormat { get; set; } [Reactive] public PaletteSelectionMode PaletteSelectionMode { get; private set; } [Reactive] public int PaletteColumnCount { get; private set; } = 16; + [Reactive] public int SelectedPalette { get; set; } = 0; [Reactive] public int AddressIncrement { get; private set; } [Reactive] public int MaximumAddress { get; private set; } = int.MaxValue; @@ -115,6 +116,43 @@ namespace Mesen.Debugger.ViewModels }); DebugShortcutManager.CreateContextMenu(picViewer, new List { + new ContextMenuAction() { + ActionType = ActionType.EditTile, + HintText = () => $"{GridSizeX}px x {GridSizeY}px", + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.TileViewer_EditTile), + IsEnabled = () => GetSelectedTileAddress() >= 0, + OnClick = () => EditTileGrid(1, 1, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.EditTiles, + SubActions = new() { + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"1x2 ({GridSizeX}px x {GridSizeY*2}px)", + IsEnabled = () => GetSelectedTileAddress() >= 0, + OnClick = () => EditTileGrid(1, 2, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"2x1 ({GridSizeX*2}px x {GridSizeY}px)", + IsEnabled = () => GetSelectedTileAddress() >= 0, + OnClick = () => EditTileGrid(2, 1, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"2x2 ({GridSizeX*2}px x {GridSizeY*2}px)", + IsEnabled = () => GetSelectedTileAddress() >= 0, + OnClick = () => EditTileGrid(2, 2, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"4x4 ({GridSizeX*4}px x {GridSizeY*4}px)", + IsEnabled = () => GetSelectedTileAddress() >= 0, + OnClick = () => EditTileGrid(4, 4, wnd) + } + } + }, + new ContextMenuSeparator(), new ContextMenuAction() { ActionType = ActionType.ViewInMemoryViewer, Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.TileViewer_ViewInMemoryViewer), @@ -139,19 +177,21 @@ namespace Mesen.Debugger.ViewModels InitForCpuType(); AddDisposable(this.WhenAnyValue(x => x.Config.Format).Subscribe(x => { - PaletteSelectionMode = GetBitsPerPixel(x) switch { + PaletteSelectionMode = x.GetBitsPerPixel() switch { 2 => PaletteSelectionMode.FourColors, 4 => PaletteSelectionMode.SixteenColors, _ => PaletteSelectionMode.None }; - PixelSize tileSize = GetTileSize(x); + PixelSize tileSize = x.GetTileSize(); if(GridSizeX != tileSize.Width || GridSizeY != tileSize.Height) { GridSizeX = tileSize.Width; GridSizeY = tileSize.Height; SelectionRect = Rect.Empty; PreviewPanel = null; } + + RefreshPalette(); })); AddDisposable(this.WhenAnyValue(x => x.Config.Layout).Subscribe(x => { @@ -160,7 +200,7 @@ namespace Mesen.Debugger.ViewModels AddDisposable(this.WhenAnyValue(x => x.Config.ColumnCount, x => x.Config.RowCount, x => x.Config.Format).Subscribe(x => { ApplyColumnRowCountRestrictions(); - AddressIncrement = Config.ColumnCount * Config.RowCount * 8 * 8 * GetBitsPerPixel(Config.Format) / 8; + AddressIncrement = Config.ColumnCount * Config.RowCount * 8 * 8 * Config.Format.GetBitsPerPixel() / 8; })); AddDisposable(this.WhenAnyValue(x => x.Config.Source).Subscribe(memType => { @@ -172,6 +212,7 @@ namespace Mesen.Debugger.ViewModels RefreshData(); })); + AddDisposable(this.WhenAnyValue(x => x.SelectedPalette).Subscribe(x => RefreshTab())); AddDisposable(this.WhenAnyValue(x => x.SelectionRect).Subscribe(x => UpdatePreviewPanel())); AddDisposable(ReactiveHelper.RegisterRecursiveObserver(Config, Config_PropertyChanged)); @@ -365,14 +406,15 @@ namespace Mesen.Debugger.ViewModels int[,] layerBpp = new int[8, 4] { { 2, 2, 2, 2 }, { 4, 4, 2, 0 }, { 4, 4, 0, 0 }, { 8, 4, 0, 0 }, { 8, 2, 0, 0 }, { 4, 2, 0, 0 }, { 4, 0, 0, 0 }, { 8, 0, 0, 0 } }; SnesPpuState ppu = (SnesPpuState)state; Config.Source = MemoryType.SnesVideoRam; - Config.StartAddress = ppu.Layers[layer].ChrAddress * 2; Config.ColumnCount = 16; Config.RowCount = 16; Config.Layout = TileLayout.Normal; if(ppu.BgMode == 7) { - Config.Format = ppu.DirectColorMode ? TileFormat.Mode7DirectColor : TileFormat.Mode7; - Config.SelectedPalette = 0; + Config.Format = ppu.ExtBgEnabled ? TileFormat.Mode7ExtBg : (ppu.DirectColorMode ? TileFormat.Mode7DirectColor : TileFormat.Mode7); + Config.StartAddress = 0; + SelectedPalette = 0; } else { + Config.StartAddress = ppu.Layers[layer].ChrAddress * 2; Config.Format = layerBpp[ppu.BgMode, layer] switch { 2 => TileFormat.Bpp2, 4 => TileFormat.Bpp4, @@ -380,8 +422,8 @@ namespace Mesen.Debugger.ViewModels _ => TileFormat.Bpp2 }; - if(layerBpp[ppu.BgMode, layer] == 8 || Config.SelectedPalette >= (layerBpp[ppu.BgMode, layer] == 2 ? 32 : 8)) { - Config.SelectedPalette = 0; + if(layerBpp[ppu.BgMode, layer] == 8 || SelectedPalette >= (layerBpp[ppu.BgMode, layer] == 2 ? 32 : 8)) { + SelectedPalette = 0; } } break; @@ -395,8 +437,8 @@ namespace Mesen.Debugger.ViewModels Config.RowCount = 16; Config.Layout = TileLayout.Normal; Config.Format = TileFormat.NesBpp2; - if(Config.SelectedPalette >= 4) { - Config.SelectedPalette = 0; + if(SelectedPalette >= 4) { + SelectedPalette = 0; } break; } @@ -410,8 +452,8 @@ namespace Mesen.Debugger.ViewModels Config.Layout = TileLayout.Normal; Config.Format = TileFormat.Bpp2; Config.Background = ppu.CgbEnabled ? TileBackground.PaletteColor : TileBackground.Default; - if(!ppu.CgbEnabled || Config.SelectedPalette > 8) { - Config.SelectedPalette = 0; + if(!ppu.CgbEnabled || SelectedPalette > 8) { + SelectedPalette = 0; } break; } @@ -424,8 +466,8 @@ namespace Mesen.Debugger.ViewModels Config.Layout = TileLayout.Normal; Config.Format = TileFormat.Bpp4; Config.Background = TileBackground.Default; - if(Config.SelectedPalette >= 16) { - Config.SelectedPalette = 0; + if(SelectedPalette >= 16) { + SelectedPalette = 0; } break; } @@ -447,8 +489,8 @@ namespace Mesen.Debugger.ViewModels Config.ColumnCount = 16; Config.RowCount = 16; Config.StartAddress = (ppu.OamBaseAddress + (layer == 1 ? ppu.OamAddressOffset : 0)) * 2; - if(Config.SelectedPalette < 8 || Config.SelectedPalette >= 16) { - Config.SelectedPalette = 8; + if(SelectedPalette < 8 || SelectedPalette >= 16) { + SelectedPalette = 8; } break; } @@ -467,8 +509,8 @@ namespace Mesen.Debugger.ViewModels Config.Layout = TileLayout.Normal; } Config.Source = MemoryType.NesPpuMemory; - if(Config.SelectedPalette < 4 || Config.SelectedPalette >= 8) { - Config.SelectedPalette = 4; + if(SelectedPalette < 4 || SelectedPalette >= 8) { + SelectedPalette = 4; } Config.Format = TileFormat.NesBpp2; break; @@ -483,10 +525,10 @@ namespace Mesen.Debugger.ViewModels Config.Layout = TileLayout.Normal; Config.Background = TileBackground.Black; Config.Format = TileFormat.Bpp2; - if(ppu.CgbEnabled && Config.SelectedPalette < 8) { - Config.SelectedPalette = 8; - } else if(!ppu.CgbEnabled && Config.SelectedPalette == 0) { - Config.SelectedPalette = 1; + if(ppu.CgbEnabled && SelectedPalette < 8) { + SelectedPalette = 8; + } else if(!ppu.CgbEnabled && SelectedPalette == 0) { + SelectedPalette = 1; } break; } @@ -499,8 +541,8 @@ namespace Mesen.Debugger.ViewModels Config.Layout = TileLayout.Normal; Config.Format = TileFormat.PceSpriteBpp4; Config.Background = TileBackground.Default; - if(Config.SelectedPalette < 16) { - Config.SelectedPalette = 16; + if(SelectedPalette < 16) { + SelectedPalette = 16; } break; } @@ -511,11 +553,11 @@ namespace Mesen.Debugger.ViewModels { Config.Source = type; Config.Format = format; - Config.SelectedPalette = paletteIndex; + SelectedPalette = paletteIndex; Config.StartAddress = address / AddressIncrement * AddressIncrement; Config.Layout = layout; - int bitsPerPixel = GetBitsPerPixel(Config.Format); - PixelSize tileSize = GetTileSize(Config.Format); + int bitsPerPixel = Config.Format.GetBitsPerPixel(); + PixelSize tileSize = Config.Format.GetTileSize(); int bytesPerTile = tileSize.Width * tileSize.Height * bitsPerPixel / 8; int gap = address - Config.StartAddress; @@ -606,16 +648,20 @@ namespace Mesen.Debugger.ViewModels public void RefreshData() { _ppuState = DebugApi.GetPpuState(CpuType); + + RefreshPalette(); + _sourceData = DebugApi.GetMemoryState(Config.Source); - DebugPaletteInfo palette = DebugApi.GetPaletteInfo(CpuType); + RefreshTab(); + } + + private void RefreshPalette() + { + DebugPaletteInfo palette = DebugApi.GetPaletteInfo(CpuType, new GetPaletteInfoOptions() { Format = Config.Format }); PaletteColors = palette.GetRgbPalette(); RawPalette = palette.GetRawPalette(); RawFormat = palette.RawFormat; PaletteColumnCount = PaletteColors.Length > 16 ? 16 : 4; - - _sourceData = DebugApi.GetMemoryState(Config.Source); - - RefreshTab(); } private void RefreshTab() @@ -633,8 +679,8 @@ namespace Mesen.Debugger.ViewModels private int GetTileAddress(PixelPoint pixelPosition) { - int bitsPerPixel = GetBitsPerPixel(Config.Format); - PixelSize tileSize = GetTileSize(Config.Format); + int bitsPerPixel = Config.Format.GetBitsPerPixel(); + PixelSize tileSize = Config.Format.GetTileSize(); int bytesPerTile = tileSize.Width * tileSize.Height * bitsPerPixel / 8; PixelPoint pos = FromLayoutCoordinates(Config.Layout, new PixelPoint(pixelPosition.X / tileSize.Width, pixelPosition.Y / tileSize.Height)); int offset = (pos.Y * Config.ColumnCount * 8 / tileSize.Width + pos.X) * bytesPerTile; @@ -662,7 +708,7 @@ namespace Mesen.Debugger.ViewModels entries.StartUpdate(); - PixelSize tileSize = GetTileSize(Config.Format); + PixelSize tileSize = Config.Format.GetTileSize(); PixelRect cropRect = new PixelRect(p.X / tileSize.Width * tileSize.Width, p.Y / tileSize.Height * tileSize.Height, tileSize.Width, tileSize.Height); entries.AddPicture("Tile", ViewerBitmap, 6, cropRect); @@ -678,6 +724,18 @@ namespace Mesen.Debugger.ViewModels } } + private void EditTileGrid(int columnCount, int rowCount, Window wnd) + { + PixelPoint p = ViewerMousePos ?? PixelPoint.FromPoint(SelectionRect.TopLeft, 1); + List addresses = new(); + for(int row = 0; row < rowCount; row++) { + for(int col = 0; col < columnCount; col++) { + addresses.Add(new AddressInfo() { Address = GetTileAddress(new PixelPoint(p.X + col*GridSizeX, p.Y + row*GridSizeY)), Type = Config.Source }); + } + } + TileEditorWindow.OpenAtTile(addresses, columnCount, Config.Format, SelectedPalette, wnd); + } + private GetTileViewOptions GetOptions() { return new GetTileViewOptions() { @@ -685,7 +743,7 @@ namespace Mesen.Debugger.ViewModels Format = Config.Format, Width = Config.ColumnCount, Height = Config.RowCount, - Palette = Config.SelectedPalette, + Palette = SelectedPalette, Layout = Config.Layout, Filter = ShowFilterDropdown ? Config.Filter : TileFilter.None, StartAddress = Config.StartAddress, @@ -704,28 +762,6 @@ namespace Mesen.Debugger.ViewModels } } - private int GetBitsPerPixel(TileFormat format) - { - return format switch { - TileFormat.Bpp2 => 2, - TileFormat.Bpp4 => 4, - TileFormat.DirectColor => 8, - TileFormat.Mode7 => 16, - TileFormat.Mode7DirectColor => 16, - TileFormat.NesBpp2 => 2, - TileFormat.PceSpriteBpp4 => 4, - _ => 8, - }; - } - - private PixelSize GetTileSize(TileFormat format) - { - return format switch { - TileFormat.PceSpriteBpp4 => new PixelSize(16, 16), - _ => new PixelSize(8,8), - }; - } - public void OnGameLoaded() { InitForCpuType(); diff --git a/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs b/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs index ec9b6731..bd461a64 100644 --- a/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs +++ b/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs @@ -51,7 +51,7 @@ namespace Mesen.Debugger.ViewModels private DebugTilemapInfo _tilemapInfo; private PictureViewer _picViewer; private BaseState? _ppuState; - private byte[]? _prevVram; + private byte[] _prevVram = Array.Empty(); private byte[] _vram = Array.Empty(); private UInt32[] _rgbPalette = Array.Empty(); private UInt32[] _rawPalette = Array.Empty(); @@ -144,6 +144,37 @@ namespace Mesen.Debugger.ViewModels } }, new ContextMenuSeparator(), + new ContextMenuAction() { + ActionType = ActionType.EditTile, + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.TilemapViewer_EditTile), + OnClick = () => EditTileGrid(1, 1, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.EditTiles, + SubActions = new() { + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"1x2 ({GridSizeX}px x {GridSizeY*2}px)", + OnClick = () => EditTileGrid(1, 2, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"2x1 ({GridSizeX*2}px x {GridSizeY}px)", + OnClick = () => EditTileGrid(2, 1, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"2x2 ({GridSizeX*2}px x {GridSizeY*2}px)", + OnClick = () => EditTileGrid(2, 2, wnd) + }, + new ContextMenuAction() { + ActionType = ActionType.Custom, + CustomText = $"4x4 ({GridSizeX*4}px x {GridSizeY*4}px)", + OnClick = () => EditTileGrid(4, 4, wnd) + } + } + }, + new ContextMenuSeparator(), new ContextMenuAction() { ActionType = ActionType.EditTilemapBreakpoint, HintText = () => { @@ -277,7 +308,7 @@ namespace Mesen.Debugger.ViewModels private DebugTilemapTileInfo? GetSelectedTileInfo() { - if(_ppuState == null || _prevVram == null) { + if(_ppuState == null || _vram == null) { return null; } else { PixelPoint p; @@ -289,7 +320,7 @@ namespace Mesen.Debugger.ViewModels } p = PixelPoint.FromPoint(SelectionRect.TopLeft, 1); } - return DebugApi.GetTilemapTileInfo((uint)p.X, (uint)p.Y, CpuType, GetOptions(SelectedTab), _prevVram, _ppuState); + return DebugApi.GetTilemapTileInfo((uint)p.X, (uint)p.Y, CpuType, GetOptions(SelectedTab), _vram, _ppuState); } } @@ -323,7 +354,7 @@ namespace Mesen.Debugger.ViewModels { return new GetTilemapOptions() { Layer = (byte)tab.Layer, - CompareVram = prevVram, + CompareVram = prevVram?.Length > 0 ? prevVram : null, AccessCounters = accessCounters, TileHighlightMode = Config.TileHighlightMode, AttributeHighlightMode = Config.AttributeHighlightMode, @@ -360,7 +391,7 @@ namespace Mesen.Debugger.ViewModels } BaseState ppuState = _ppuState; - byte[]? prevVram = _prevVram; + byte[] prevVram = _prevVram; byte[] vram = _vram; uint[] palette = _rgbPalette; AddressCounters[] accessCounters = _accessCounters; @@ -428,11 +459,11 @@ namespace Mesen.Debugger.ViewModels public DynamicTooltip? GetPreviewPanel(PixelPoint p, DynamicTooltip? tooltipToUpdate) { - if(_ppuState == null || _prevVram == null) { + if(_ppuState == null) { return null; } - DebugTilemapTileInfo? result = DebugApi.GetTilemapTileInfo((uint)p.X, (uint)p.Y, CpuType, GetOptions(SelectedTab), _prevVram, _ppuState); + DebugTilemapTileInfo? result = DebugApi.GetTilemapTileInfo((uint)p.X, (uint)p.Y, CpuType, GetOptions(SelectedTab), _vram, _ppuState); if(result == null) { return null; } @@ -492,6 +523,31 @@ namespace Mesen.Debugger.ViewModels } } + private void EditTileGrid(int columnCount, int rowCount, Window wnd) + { + if(_ppuState != null) { + PixelPoint p = ViewerMousePos ?? PixelPoint.FromPoint(SelectionRect.TopLeft, 1); + List addresses = new(); + MemoryType memType = GetVramMemoryType(); + int palette = -1; + for(int row = 0; row < rowCount; row++) { + for(int col = 0; col < columnCount; col++) { + DebugTilemapTileInfo? tile = DebugApi.GetTilemapTileInfo((uint)(p.X + GridSizeX*col), (uint)(p.Y + GridSizeY*row), CpuType, GetOptions(SelectedTab), _vram, _ppuState); + if(tile == null) { + return; + } + + if(palette == -1) { + palette = tile.Value.PaletteIndex; + } + addresses.Add(new AddressInfo() { Address = tile.Value.TileAddress, Type = memType }); + } + } + palette = Math.Max(0, palette); + TileEditorWindow.OpenAtTile(addresses, columnCount, _tilemapInfo.Format, palette, wnd); + } + } + public void OnGameLoaded() { _inGameLoaded = true; diff --git a/NewUI/Debugger/Windows/TileEditorWindow.axaml b/NewUI/Debugger/Windows/TileEditorWindow.axaml new file mode 100644 index 00000000..b5c312c7 --- /dev/null +++ b/NewUI/Debugger/Windows/TileEditorWindow.axaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NewUI/Debugger/Windows/TileEditorWindow.axaml.cs b/NewUI/Debugger/Windows/TileEditorWindow.axaml.cs new file mode 100644 index 00000000..38da32c3 --- /dev/null +++ b/NewUI/Debugger/Windows/TileEditorWindow.axaml.cs @@ -0,0 +1,90 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using System; +using Mesen.Debugger.Controls; +using Mesen.Debugger.ViewModels; +using Mesen.Interop; +using System.ComponentModel; +using Avalonia.Interactivity; +using Mesen.Debugger.Utilities; +using Mesen.Utilities; +using System.Collections.Generic; +using Avalonia.Threading; +using System.Linq; +using Mesen.Config; + +namespace Mesen.Debugger.Windows +{ + public class TileEditorWindow : Window, INotificationHandler + { + private TileEditorViewModel _model; + + [Obsolete("For designer only")] + public TileEditorWindow() : this(new()) { } + + public TileEditorWindow(TileEditorViewModel model) + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + + PictureViewer picViewer = this.FindControl("picViewer").InnerViewer; + picViewer.PositionClicked += PicViewer_PositionClicked; + _model = model; + _model.InitActions(picViewer, this); + DataContext = _model; + + _model.Config.LoadWindowSettings(this); + } + + protected override void OnClosing(CancelEventArgs e) + { + base.OnClosing(e); + _model.Config.SaveWindowSettings(this); + } + + private void PicViewer_PositionClicked(object? sender, PositionClickedEventArgs e) + { + _model.UpdatePixel(e.Position, e.Properties.IsRightButtonPressed); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public static void OpenAtTile(AddressInfo tileAddr, TileFormat tileFormat, int selectedPalette, Window parent) + { + OpenAtTile(new List() { tileAddr }, 1, tileFormat, selectedPalette, parent); + } + + public static void OpenAtTile(List tileAddresses, int columnCount, TileFormat tileFormat, int selectedPalette, Window parent) + { + for(int i = 0; i < tileAddresses.Count; i++) { + AddressInfo addr = tileAddresses[i]; + if(addr.Type.IsRelativeMemory()) { + tileAddresses[i] = DebugApi.GetAbsoluteAddress(addr); + } + } + + if(tileAddresses.Any(x => x.Address < 0)) { + return; + } + + TileEditorViewModel model = new(tileAddresses, columnCount, tileFormat, selectedPalette); + TileEditorWindow wnd = DebugWindowManager.CreateDebugWindow(() => new TileEditorWindow(model)); + wnd.ShowCentered((Control)parent); + } + + public void ProcessNotification(NotificationEventArgs e) + { + if(e.NotificationType == ConsoleNotificationType.GameLoaded) { + Dispatcher.UIThread.Post(() => { + Close(); + }); + } + } + } +} diff --git a/NewUI/Debugger/Windows/TileViewerWindow.axaml b/NewUI/Debugger/Windows/TileViewerWindow.axaml index 2b4c1355..3f251317 100644 --- a/NewUI/Debugger/Windows/TileViewerWindow.axaml +++ b/NewUI/Debugger/Windows/TileViewerWindow.axaml @@ -134,10 +134,10 @@ IsEnabled="{CompiledBinding !Config.UseGrayscalePalette}" BlockSize="12" SelectionMode="{CompiledBinding PaletteSelectionMode}" - SelectedPalette="{CompiledBinding Config.SelectedPalette}" PaletteColors="{CompiledBinding PaletteColors}" RawPalette="{CompiledBinding RawPalette}" RawFormat="{CompiledBinding RawFormat}" + SelectedPalette="{CompiledBinding SelectedPalette}" /> new PixelSize(16, 16), + _ => new PixelSize(8, 8), + }; + } + + public static int GetBitsPerPixel(this TileFormat format) + { + return format switch { + TileFormat.Bpp2 => 2, + TileFormat.Bpp4 => 4, + TileFormat.DirectColor => 8, + TileFormat.Mode7 => 16, + TileFormat.Mode7DirectColor => 16, + TileFormat.NesBpp2 => 2, + TileFormat.PceSpriteBpp4 => 4, + _ => 8, + }; + } + } + public enum TileLayout { Normal, diff --git a/NewUI/Localization/resources.en.xml b/NewUI/Localization/resources.en.xml index 96b52f3a..b83af05c 100644 --- a/NewUI/Localization/resources.en.xml +++ b/NewUI/Localization/resources.en.xml @@ -795,7 +795,20 @@ Attributes: Show scroll overlay + +
+ Tile Editor + _File + _View + Background: + Preview + + Select color by clicking above. + Left-click: Draw selected color + Right-click: Make pixel transparent +
+
Tile Viewer _File @@ -2003,6 +2016,7 @@ Black White Magenta + Transparent Tilemap @@ -2270,9 +2284,12 @@ Tilemap Viewer - Edit Breakpoint (Attribute) Tilemap Viewer - View in Memory Viewer Tilemap Viewer - View in Tile Viewer + Tilemap Viewer - Edit Tile Tile Viewer - View in Memory Viewer + Tile Viewer - Edit Tile Sprite Viewer - View in Memory Viewer Sprite Viewer - View in Tile Viewer + Sprite Viewer - Edit Sprite Freeze Unfreeze Add to Watch @@ -2524,6 +2541,10 @@ Find... Find next Find previous + + Edit tile + Edit tiles + Edit sprite \ No newline at end of file diff --git a/NewUI/NewUI.csproj b/NewUI/NewUI.csproj index 5a829ccc..52a2980b 100644 --- a/NewUI/NewUI.csproj +++ b/NewUI/NewUI.csproj @@ -357,6 +357,9 @@ PaletteViewerWindow.axaml + + TileEditorWindow.axaml + TraceLoggerWindow.axaml