Debugger: Added tile editor

This commit is contained in:
Sour 2022-06-19 15:05:12 -04:00
parent 1547e70b2f
commit 35a54505b4
36 changed files with 903 additions and 141 deletions

View file

@ -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,

View file

@ -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) {

View file

@ -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);

View file

@ -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;

View file

@ -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;
};

View file

@ -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;

View file

@ -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;
};

View file

@ -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;

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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)); }

View file

@ -9,6 +9,14 @@
<Application.Resources>
<l:EnumConverter x:Key="Enum" />
<u:FontNameConverter x:Key="FontNameConverter" />
<VisualBrush x:Key="ViewerBgBrush" TileMode="Tile" Stretch="None" AlignmentX="Left" AlignmentY="Top" SourceRect="0,0,5,5" DestinationRect="0,0,5,5">
<VisualBrush.Visual>
<Panel Background="#202020">
<Line StartPoint="0, 0" EndPoint="5, 5" Stroke="Gray" StrokeThickness="1" Opacity="0.5" />
<Line StartPoint="5, 0" EndPoint="0, 5" Stroke="LightGray" StrokeThickness="1" Opacity="0.5" />
</Panel>
</VisualBrush.Visual>
</VisualBrush>
</Application.Resources>
<Application.DataTemplates>

View file

@ -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();

View file

@ -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

View file

@ -0,0 +1,11 @@
using Mesen.Interop;
using ReactiveUI.Fody.Helpers;
namespace Mesen.Config
{
public class TileEditorConfig : BaseWindowConfig<TileEditorConfig>
{
[Reactive] public int ImageScale { get; set; } = 8;
[Reactive] public TileBackground Background { get; set; } = TileBackground.Transparent;
}
}

View file

@ -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();

View file

@ -30,7 +30,7 @@
MinWidth="{Binding FirstColumnWidth, ElementName=root}"
IsVisible="{Binding Name.Length}"
Margin="0 3 10 2"
/>
/>
<ContentControl Content="{Binding Value}">
<ContentControl.DataTemplates>
<DataTemplate DataType="x:String">

View file

@ -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) {

View file

@ -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

View file

@ -9,7 +9,7 @@
x:Name="root"
x:Class="Mesen.Debugger.Controls.ScrollPictureViewer"
>
<Border BorderBrush="Gray" BorderThickness="1" Background="#202020">
<Border BorderBrush="Gray" BorderThickness="1">
<ScrollViewer
Name="scrollViewer"
VerticalScrollBarVisibility="Auto"
@ -17,26 +17,28 @@
AllowAutoHide="False"
Offset="{CompiledBinding ScrollOffset, ElementName=root, Mode=TwoWay}"
>
<dc:PictureViewer
x:Name="picViewer"
PointerPressed="Viewer_PointerPressed"
PointerMoved="Viewer_PointerMoved"
Source="{CompiledBinding Source, ElementName=root}"
Zoom="{CompiledBinding Zoom, ElementName=root}"
AllowSelection="{CompiledBinding AllowSelection, ElementName=root}"
ShowMousePosition="{CompiledBinding ShowMousePosition, ElementName=root}"
GridSizeX="{CompiledBinding GridSizeX, ElementName=root}"
GridSizeY="{CompiledBinding GridSizeY, ElementName=root}"
ShowGrid="{CompiledBinding ShowGrid, ElementName=root}"
AltGridSizeX="{CompiledBinding AltGridSizeX, ElementName=root}"
AltGridSizeY="{CompiledBinding AltGridSizeY, ElementName=root}"
ShowAltGrid="{CompiledBinding ShowAltGrid, ElementName=root}"
SelectionRect="{CompiledBinding SelectionRect, ElementName=root}"
OverlayRect="{CompiledBinding OverlayRect, ElementName=root}"
MouseOverRect="{CompiledBinding MouseOverRect, ElementName=root}"
HighlightRects="{CompiledBinding HighlightRects, ElementName=root}"
GridHighlight="{CompiledBinding GridHighlight, ElementName=root}"
<Border Background="{StaticResource ViewerBgBrush}" HorizontalAlignment="Left" VerticalAlignment="Top">
<dc:PictureViewer
x:Name="picViewer"
PointerPressed="Viewer_PointerPressed"
PointerMoved="Viewer_PointerMoved"
Source="{CompiledBinding Source, ElementName=root}"
Zoom="{CompiledBinding Zoom, ElementName=root}"
AllowSelection="{CompiledBinding AllowSelection, ElementName=root}"
ShowMousePosition="{CompiledBinding ShowMousePosition, ElementName=root}"
GridSizeX="{CompiledBinding GridSizeX, ElementName=root}"
GridSizeY="{CompiledBinding GridSizeY, ElementName=root}"
ShowGrid="{CompiledBinding ShowGrid, ElementName=root}"
AltGridSizeX="{CompiledBinding AltGridSizeX, ElementName=root}"
AltGridSizeY="{CompiledBinding AltGridSizeY, ElementName=root}"
ShowAltGrid="{CompiledBinding ShowAltGrid, ElementName=root}"
SelectionRect="{CompiledBinding SelectionRect, ElementName=root}"
OverlayRect="{CompiledBinding OverlayRect, ElementName=root}"
MouseOverRect="{CompiledBinding MouseOverRect, ElementName=root}"
HighlightRects="{CompiledBinding HighlightRects, ElementName=root}"
GridHighlight="{CompiledBinding GridHighlight, ElementName=root}"
/>
</Border>
</ScrollViewer>
</Border>
</UserControl>

View file

@ -25,6 +25,7 @@ namespace Mesen.Debugger.Controls
public static readonly StyledProperty<int> AltGridSizeYProperty = AvaloniaProperty.Register<ScrollPictureViewer, int>(nameof(AltGridSizeY), 8);
public static readonly StyledProperty<bool> ShowAltGridProperty = AvaloniaProperty.Register<ScrollPictureViewer, bool>(nameof(ShowAltGrid), false);
public static readonly StyledProperty<bool> AllowSelectionProperty = AvaloniaProperty.Register<ScrollPictureViewer, bool>(nameof(AllowSelection), true);
public static readonly StyledProperty<bool> AllowClickDragProperty = AvaloniaProperty.Register<ScrollPictureViewer, bool>(nameof(AllowClickDrag), true);
public static readonly StyledProperty<GridRowColumn?> GridHighlightProperty = AvaloniaProperty.Register<ScrollPictureViewer, GridRowColumn?>(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) {

View file

@ -665,5 +665,12 @@ namespace Mesen.Debugger.Utilities
FindNext,
[IconFile("PreviousArrow")]
FindPrev,
[IconFile("Edit")]
EditTile,
[IconFile("Edit")]
EditTiles,
[IconFile("Edit")]
EditSprite,
}
}

View file

@ -16,7 +16,7 @@ namespace Mesen.Debugger.Utilities
private static object _windowNotifLock = new();
private static bool _loadingGame = false;
public static T OpenDebugWindow<T>(Func<T> createWindow) where T : Window
public static T CreateDebugWindow<T>(Func<T> 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<T>(Func<T> createWindow) where T : Window
{
T wnd = CreateDebugWindow<T>(createWindow);
wnd.Show();
return wnd;
}

View file

@ -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[] {

View file

@ -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<UInt32>();
[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);

View file

@ -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<object> {
GetEditTileAction(wnd),
GetViewInMemoryViewerAction(),
GetViewInTileViewerAction(),
new ContextMenuSeparator(),
@ -121,19 +127,17 @@ namespace Mesen.Debugger.ViewModels
});
DebugShortcutManager.CreateContextMenu(_spriteGrid, new List<object> {
GetEditTileAction(wnd),
GetViewInMemoryViewerAction(),
GetViewInTileViewerAction()
});
DebugShortcutManager.CreateContextMenu(listView, new List<object> {
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() {

View file

@ -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<UInt32>();
[Reactive] public UInt32[] RawPalette { get; set; } = Array.Empty<UInt32>();
[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<object> FileMenuActions { get; private set; } = new();
public List<object> ViewMenuActions { get; private set; } = new();
private CpuType _cpuType;
private List<AddressInfo> _tileAddresses;
private int _columnCount = 1;
private int _rowCount = 1;
private TileFormat _tileFormat;
private byte[] _sourceData = Array.Empty<byte>();
private UInt32[] _tileBuffer = Array.Empty<UInt32>();
[Obsolete("For designer only")]
public TileEditorViewModel() : this(new() { new() }, 1, TileFormat.Bpp4, 0) { }
public TileEditorViewModel(List<AddressInfo> 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<object>() {
new ContextMenuAction() {
ActionType = ActionType.ExportToPng,
OnClick = () => picViewer.ExportToPng()
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.Exit,
OnClick = () => wnd.Close()
}
});
ViewMenuActions = AddDisposables(new List<object>() {
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
};
}
}

View file

@ -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<object> {
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<AddressInfo> 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();

View file

@ -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<byte>();
private byte[] _vram = Array.Empty<byte>();
private UInt32[] _rgbPalette = Array.Empty<UInt32>();
private UInt32[] _rawPalette = Array.Empty<UInt32>();
@ -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<AddressInfo> 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;

View file

@ -0,0 +1,80 @@
<Window
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:m="clr-namespace:Mesen"
xmlns:vm="using:Mesen.ViewModels"
xmlns:sys="using:System"
xmlns:v="using:Mesen.Views"
xmlns:c="using:Mesen.Controls"
xmlns:i="using:Mesen.Interop"
xmlns:l="using:Mesen.Localization"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dvm="using:Mesen.Debugger.ViewModels"
xmlns:dc="using:Mesen.Debugger.Controls"
xmlns:dv="using:Mesen.Debugger.Views"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="600"
x:Class="Mesen.Debugger.Windows.TileEditorWindow"
x:DataType="dvm:TileEditorViewModel"
Width="600" Height="600"
Title="{l:Translate wndTitle}"
Icon="/Assets/Edit.png"
>
<Design.DataContext>
<dvm:TileEditorViewModel />
</Design.DataContext>
<DockPanel>
<Panel DockPanel.Dock="Top">
<Menu DockPanel.Dock="Top">
<MenuItem Classes="ActionMenu" Header="{l:Translate mnuFile}" Items="{CompiledBinding FileMenuActions}" />
<MenuItem Classes="ActionMenu" Header="{l:Translate mnuView}" Items="{CompiledBinding ViewMenuActions}" />
</Menu>
</Panel>
<ScrollViewer Margin="3 0" DockPanel.Dock="Right">
<StackPanel>
<Border BorderBrush="Gray" BorderThickness="1" HorizontalAlignment="Left" VerticalAlignment="Top">
<dc:PaletteSelector
ColumnCount="{CompiledBinding PaletteColumnCount}"
BlockSize="14"
SelectionMode="SingleColor"
PaletteColors="{CompiledBinding PaletteColors}"
RawPalette="{CompiledBinding RawPalette}"
RawFormat="{CompiledBinding RawFormat}"
SelectedPalette="{CompiledBinding SelectedColor}"
/>
</Border>
<DockPanel Margin="3 5">
<TextBlock Text="{l:Translate lblBackground}" VerticalAlignment="Center" DockPanel.Dock="Left" />
<c:EnumComboBox SelectedItem="{CompiledBinding Config.Background}" />
</DockPanel>
<StackPanel Margin="3 0 3 5">
<TextBlock Text="{l:Translate lblHint1}" />
<TextBlock Text="{l:Translate lblHint2}" />
<TextBlock Text="{l:Translate lblHint3}" />
</StackPanel>
<c:GroupBox Header="{l:Translate lblPreview}">
<Border BorderBrush="Gray" Background="{StaticResource ViewerBgBrush}" BorderThickness="1" HorizontalAlignment="Left">
<dc:PictureViewer
Zoom="4"
Source="{CompiledBinding ViewerBitmap}"
AllowSelection="False"
ShowMousePosition="False"
/>
</Border>
</c:GroupBox>
</StackPanel>
</ScrollViewer>
<dc:ScrollPictureViewer
x:Name="picViewer"
AllowClickDrag="False"
AllowSelection="False"
Zoom="{CompiledBinding Config.ImageScale}"
Source="{CompiledBinding ViewerBitmap}"
GridSizeX="1"
GridSizeY="1"
/>
</DockPanel>
</Window>

View file

@ -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<ScrollPictureViewer>("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<AddressInfo>() { tileAddr }, 1, tileFormat, selectedPalette, parent);
}
public static void OpenAtTile(List<AddressInfo> 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<TileEditorWindow>(() => new TileEditorWindow(model));
wnd.ShowCentered((Control)parent);
}
public void ProcessNotification(NotificationEventArgs e)
{
if(e.NotificationType == ConsoleNotificationType.GameLoaded) {
Dispatcher.UIThread.Post(() => {
Close();
});
}
}
}
}

View file

@ -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}"
/>
<Rectangle
Fill="{StaticResource DisabledOverlayBrush}"

View file

@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Mesen.Debugger;
using Mesen.Utilities;
@ -279,7 +280,8 @@ namespace Mesen.Interop
return sprites;
}
[DllImport(DllPath)] public static extern DebugPaletteInfo GetPaletteInfo(CpuType cpuType);
[DllImport(DllPath)] public static extern DebugPaletteInfo GetPaletteInfo(CpuType cpuType, GetPaletteInfoOptions options = new());
[DllImport(DllPath)] public static extern void SetTilePixel(AddressInfo tileAddress, TileFormat format, int x, int y, int color);
[DllImport(DllPath)] public static extern void SetViewerUpdateTiming(Int32 viewerId, Int32 scanline, Int32 cycle, CpuType cpuType);
@ -781,11 +783,12 @@ namespace Mesen.Interop
public enum TileBackground
{
Default = 0,
PaletteColor = 1,
Black = 2,
White = 3,
Magenta = 4
Default,
Transparent,
PaletteColor,
Black,
White,
Magenta,
}
public enum NullableBoolean
@ -874,6 +877,11 @@ namespace Mesen.Interop
public Int32 SelectedSprite;
}
public struct GetPaletteInfoOptions
{
public TileFormat Format;
}
public struct DebugSpritePreviewInfo
{
public UInt32 Width;
@ -919,6 +927,9 @@ namespace Mesen.Interop
[MarshalAs(UnmanagedType.I1)] public bool UseExtendedVram;
public NullableBoolean UseSecondTable;
public UInt32 TileCount;
public fixed UInt32 TileAddresses[8*8];
public fixed UInt32 SpritePreview[64*64];
}
@ -976,6 +987,31 @@ namespace Mesen.Interop
PceSpriteBpp4
}
public static class TileFormatExtensions
{
public static PixelSize GetTileSize(this TileFormat format)
{
return format switch {
TileFormat.PceSpriteBpp4 => 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,

View file

@ -795,7 +795,20 @@
<Control ID="lblAttributeHighlightMode">Attributes:</Control>
<Control ID="chkShowScrollOverlay">Show scroll overlay</Control>
</Form>
<Form ID="TileEditorWindow">
<Control ID="wndTitle">Tile Editor</Control>
<Control ID="mnuFile">_File</Control>
<Control ID="mnuView">_View</Control>
<Control ID="lblBackground">Background:</Control>
<Control ID="lblPreview">Preview</Control>
<Control ID="lblHint1">Select color by clicking above.</Control>
<Control ID="lblHint2">Left-click: Draw selected color</Control>
<Control ID="lblHint3">Right-click: Make pixel transparent</Control>
</Form>
<Form ID="TileViewerWindow">
<Control ID="wndTitle">Tile Viewer</Control>
<Control ID="mnuFile">_File</Control>
@ -2003,6 +2016,7 @@
<Value ID="Black">Black</Value>
<Value ID="White">White</Value>
<Value ID="Magenta">Magenta</Value>
<Value ID="Transparent">Transparent</Value>
</Enum>
<Enum ID="TilemapDisplayMode">
<Value ID="Default">Tilemap</Value>
@ -2270,9 +2284,12 @@
<Value ID="TilemapViewer_EditAttributeBreakpoint">Tilemap Viewer - Edit Breakpoint (Attribute)</Value>
<Value ID="TilemapViewer_ViewInMemoryViewer">Tilemap Viewer - View in Memory Viewer</Value>
<Value ID="TilemapViewer_ViewInTileViewer">Tilemap Viewer - View in Tile Viewer</Value>
<Value ID="TilemapViewer_EditTile">Tilemap Viewer - Edit Tile</Value>
<Value ID="TileViewer_ViewInMemoryViewer">Tile Viewer - View in Memory Viewer</Value>
<Value ID="TileViewer_EditTile">Tile Viewer - Edit Tile</Value>
<Value ID="SpriteViewer_ViewInMemoryViewer">Sprite Viewer - View in Memory Viewer</Value>
<Value ID="SpriteViewer_ViewInTileViewer">Sprite Viewer - View in Tile Viewer</Value>
<Value ID="SpriteViewer_EditSprite">Sprite Viewer - Edit Sprite</Value>
<Value ID="MemoryViewer_Freeze">Freeze</Value>
<Value ID="MemoryViewer_Unfreeze">Unfreeze</Value>
<Value ID="MemoryViewer_AddToWatch">Add to Watch</Value>
@ -2524,6 +2541,10 @@
<Value ID="Find">Find...</Value>
<Value ID="FindNext">Find next</Value>
<Value ID="FindPrev">Find previous</Value>
<Value ID="EditTile">Edit tile</Value>
<Value ID="EditTiles">Edit tiles</Value>
<Value ID="EditSprite">Edit sprite</Value>
</Enum>
</Enums>
</Resources>

View file

@ -357,6 +357,9 @@
<Compile Update="Debugger\Windows\PaletteViewerWindow.axaml.cs">
<DependentUpon>PaletteViewerWindow.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Windows\TileEditorWindow.axaml.cs">
<DependentUpon>TileEditorWindow.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Windows\TraceLoggerWindow.axaml.cs">
<DependentUpon>TraceLoggerWindow.axaml</DependentUpon>
</Compile>