mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Caused by what looks like slight differences between how AOT treats an empty array (vs JIT builds) when it marshals the array (AOT sends a null pointer, JIT sends a pointer to an empty array) This was causing issues in both builds (AOT crashed immediately because of the null pointer, JIT read from out-of-bounds memory)
1171 lines
37 KiB
C#
1171 lines
37 KiB
C#
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Media;
|
|
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.ComponentModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Reactive;
|
|
using System.Reactive.Linq;
|
|
|
|
namespace Mesen.Debugger.ViewModels
|
|
{
|
|
public class TileViewerViewModel : DisposableViewModel, ICpuTypeModel, IMouseOverViewerModel
|
|
{
|
|
public CpuType CpuType { get; set; }
|
|
|
|
public TileViewerConfig Config { get; }
|
|
public RefreshTimingViewModel RefreshTiming { get; }
|
|
|
|
[Reactive] public DynamicBitmap ViewerBitmap { get; private set; }
|
|
|
|
[Reactive] public DynamicTooltip? PreviewPanel { get; private set; }
|
|
|
|
[Reactive] public DynamicTooltip? ViewerTooltip { get; set; }
|
|
[Reactive] public PixelPoint? ViewerMousePos { get; 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 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;
|
|
|
|
[Reactive] public int GridSizeX { get; set; } = 8;
|
|
[Reactive] public int GridSizeY { get; set; } = 8;
|
|
|
|
[Reactive] public Rect SelectionRect { get; set; }
|
|
|
|
[Reactive] public List<PictureViewerLine>? PageDelimiters { get; set; }
|
|
|
|
[Reactive] public Enum[] AvailableMemoryTypes { get; set; } = Array.Empty<Enum>();
|
|
[Reactive] public Enum[] AvailableFormats { get; set; } = Array.Empty<Enum>();
|
|
[Reactive] public bool ShowFormatDropdown { get; set; }
|
|
[Reactive] public bool ShowFilterDropdown { get; set; }
|
|
|
|
[Reactive] public List<List<ConfigPreset>> ConfigPresetRows { get; set; } = new() { new(), new(), new() };
|
|
[Reactive] public List<ConfigPreset> ConfigPresets { get; set; } = new List<ConfigPreset>();
|
|
|
|
public List<object> FileMenuActions { get; } = new();
|
|
public List<object> ViewMenuActions { get; } = new();
|
|
|
|
public int ColumnCount => Math.Clamp(Config.ColumnCount, 4, 256);
|
|
public int RowCount => Math.Clamp(Config.RowCount, 4, 256);
|
|
|
|
private BaseState? _ppuState;
|
|
private object _updateLock = new();
|
|
private byte[] _coreSourceData = Array.Empty<byte>();
|
|
private byte[] _sourceData = Array.Empty<byte>();
|
|
private bool _refreshPending;
|
|
private bool _inGameLoaded;
|
|
|
|
[Obsolete("For designer only")]
|
|
public TileViewerViewModel() : this(CpuType.Snes, new PictureViewer(), null) { }
|
|
|
|
public TileViewerViewModel(CpuType cpuType, PictureViewer picViewer, Window? wnd)
|
|
{
|
|
Config = ConfigManager.Config.Debug.TileViewer.Clone();
|
|
CpuType = cpuType;
|
|
RefreshTiming = new RefreshTimingViewModel(Config.RefreshTiming, cpuType);
|
|
|
|
InitBitmap();
|
|
|
|
if(Design.IsDesignMode || wnd == null) {
|
|
return;
|
|
}
|
|
|
|
FileMenuActions = AddDisposables(new List<object>() {
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.ExportToPng,
|
|
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.SaveAsPng),
|
|
OnClick = () => picViewer.ExportToPng()
|
|
},
|
|
new ContextMenuSeparator(),
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.Exit,
|
|
OnClick = () => wnd?.Close()
|
|
}
|
|
});
|
|
|
|
ViewMenuActions = AddDisposables(new List<object>() {
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.Refresh,
|
|
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.Refresh),
|
|
OnClick = () => RefreshData()
|
|
},
|
|
new ContextMenuSeparator(),
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.EnableAutoRefresh,
|
|
IsSelected = () => Config.RefreshTiming.AutoRefresh,
|
|
OnClick = () => Config.RefreshTiming.AutoRefresh = !Config.RefreshTiming.AutoRefresh
|
|
},
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.RefreshOnBreakPause,
|
|
IsSelected = () => Config.RefreshTiming.RefreshOnBreakPause,
|
|
OnClick = () => Config.RefreshTiming.RefreshOnBreakPause = !Config.RefreshTiming.RefreshOnBreakPause
|
|
},
|
|
new ContextMenuSeparator(),
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.ShowSettingsPanel,
|
|
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ToggleSettingsPanel),
|
|
IsSelected = () => Config.ShowSettingsPanel,
|
|
OnClick = () => Config.ShowSettingsPanel = !Config.ShowSettingsPanel
|
|
},
|
|
new ContextMenuSeparator(),
|
|
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()
|
|
},
|
|
});
|
|
|
|
AddDisposables(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() {
|
|
GetEditTileAction(1, 2, wnd),
|
|
GetEditTileAction(2, 1, wnd),
|
|
GetEditTileAction(2, 2, wnd),
|
|
GetEditTileAction(4, 4, wnd),
|
|
GetEditTileAction(8, 8, wnd)
|
|
}
|
|
},
|
|
new ContextMenuSeparator(),
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.ViewInMemoryViewer,
|
|
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.TileViewer_ViewInMemoryViewer),
|
|
IsEnabled = () => GetSelectedTileAddress() >= 0,
|
|
OnClick = () => {
|
|
int address = GetSelectedTileAddress();
|
|
if(address >= 0) {
|
|
MemoryToolsWindow.ShowInMemoryTools(Config.Source, address);
|
|
}
|
|
}
|
|
},
|
|
new ContextMenuSeparator() { IsVisible = () => CpuType == CpuType.Nes },
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.CopyToHdPackFormat,
|
|
IsVisible = () => CpuType == CpuType.Nes,
|
|
IsEnabled = () => GetSelectedTileAddress() >= 0 && HdPackCopyHelper.IsActionAllowed(Config.Source),
|
|
OnClick = () => {
|
|
int address = GetSelectedTileAddress();
|
|
if(address >= 0) {
|
|
HdPackCopyHelper.CopyToHdPackFormat(address, Config.Source, RawPalette, SelectedPalette, false);
|
|
}
|
|
}
|
|
}
|
|
}));
|
|
|
|
DebugShortcutManager.RegisterActions(wnd, FileMenuActions);
|
|
DebugShortcutManager.RegisterActions(wnd, ViewMenuActions);
|
|
|
|
InitForCpuType();
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.Config.Format, x => x.RawPalette).Subscribe(x => {
|
|
PaletteSelectionMode selMode = PaletteSelectionMode;
|
|
selMode = x.Item1.GetBitsPerPixel() switch {
|
|
1 => PaletteSelectionMode.TwoColors,
|
|
2 => PaletteSelectionMode.FourColors,
|
|
4 => PaletteSelectionMode.SixteenColors,
|
|
8 => RawPalette.Length >= 512 ? PaletteSelectionMode._256Colors : PaletteSelectionMode.None,
|
|
_ => PaletteSelectionMode.None
|
|
};
|
|
|
|
if(selMode != PaletteSelectionMode) {
|
|
PaletteSelectionMode = selMode;
|
|
|
|
PixelSize tileSize = x.Item1.GetTileSize();
|
|
if(GridSizeX != tileSize.Width || GridSizeY != tileSize.Height) {
|
|
GridSizeX = tileSize.Width;
|
|
GridSizeY = tileSize.Height;
|
|
SelectionRect = default;
|
|
PreviewPanel = null;
|
|
}
|
|
|
|
RefreshPalette();
|
|
}
|
|
}));
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.Config.Layout).Subscribe(x => {
|
|
ApplyColumnRowCountRestrictions();
|
|
}));
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.Config.StartAddress).Subscribe(x => {
|
|
RefreshData();
|
|
}));
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.Config.ColumnCount, x => x.Config.RowCount, x => x.Config.Format).Subscribe(x => {
|
|
//Enforce min/max values for column/row counts
|
|
Config.ColumnCount = ColumnCount;
|
|
Config.RowCount = RowCount;
|
|
|
|
ApplyColumnRowCountRestrictions();
|
|
AddressIncrement = ColumnCount * RowCount * 8 * 8 * Config.Format.GetBitsPerPixel() / 8;
|
|
|
|
RefreshData();
|
|
}));
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.Config.Source).Subscribe(memType => {
|
|
MaximumAddress = Math.Max(0, DebugApi.GetMemorySize(memType) - 1);
|
|
if(Config.StartAddress > MaximumAddress) {
|
|
Config.StartAddress = 0;
|
|
}
|
|
ShowFilterDropdown = memType.SupportsCdl();
|
|
RefreshData();
|
|
}));
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.SelectedPalette).Subscribe(x => RefreshTab()));
|
|
AddDisposable(this.WhenAnyValue(x => x.SelectionRect).Subscribe(x => UpdatePreviewPanel()));
|
|
|
|
LoadSelectedPreset(false);
|
|
|
|
AddDisposable(this.WhenAnyValue(
|
|
x => x.Config.Source, x => x.Config.StartAddress, x => x.Config.ColumnCount,
|
|
x => x.Config.RowCount, x => x.Config.Format
|
|
).Skip(1).Subscribe(x => ClearPresetSelection()));
|
|
|
|
AddDisposable(ReactiveHelper.RegisterRecursiveObserver(Config, Config_PropertyChanged));
|
|
}
|
|
|
|
private void ApplyColumnRowCountRestrictions()
|
|
{
|
|
if(Config.Layout != TileLayout.Normal || Config.Format == TileFormat.PceSpriteBpp4) {
|
|
//Force multiple of 2 when in 8x16 or 16x16 display modes
|
|
Config.ColumnCount &= ~0x01;
|
|
Config.RowCount &= ~0x01;
|
|
}
|
|
}
|
|
|
|
private void InitForCpuType()
|
|
{
|
|
string selectedPreset = Config.SelectedPreset;
|
|
|
|
AvailableFormats = CpuType switch {
|
|
CpuType.Snes => new Enum[] { TileFormat.Bpp2, TileFormat.Bpp4, TileFormat.Bpp8, TileFormat.DirectColor, TileFormat.Mode7, TileFormat.Mode7ExtBg, TileFormat.Mode7DirectColor },
|
|
CpuType.Nes => new Enum[] { TileFormat.NesBpp2 },
|
|
CpuType.Gameboy => new Enum[] { TileFormat.Bpp2 },
|
|
CpuType.Pce => new Enum[] { TileFormat.Bpp4, TileFormat.PceSpriteBpp4, TileFormat.PceSpriteBpp2Sp01, TileFormat.PceSpriteBpp2Sp23, TileFormat.PceBackgroundBpp2Cg0, TileFormat.PceBackgroundBpp2Cg1 },
|
|
CpuType.Sms => new Enum[] { TileFormat.SmsBpp4, TileFormat.SmsSgBpp1 },
|
|
CpuType.Gba => new Enum[] { TileFormat.GbaBpp4, TileFormat.GbaBpp8 },
|
|
CpuType.Ws => new Enum[] { TileFormat.Bpp2, TileFormat.SmsBpp4, TileFormat.WsBpp4Packed },
|
|
_ => throw new Exception("Unsupported CPU type")
|
|
};
|
|
|
|
ConfigPresets = GetConfigPresets();
|
|
ConfigPresetRows = new(ConfigPresetRows); //Force UI update
|
|
|
|
if(!AvailableFormats.Contains(Config.Format)) {
|
|
Config.Format = (TileFormat)AvailableFormats[0];
|
|
}
|
|
ShowFormatDropdown = AvailableFormats.Length > 1;
|
|
|
|
AvailableMemoryTypes = Enum.GetValues<MemoryType>().Where(t => t.SupportsTileViewer() && DebugApi.GetMemorySize(t) > 0).Cast<Enum>().ToArray();
|
|
if(!AvailableMemoryTypes.Contains(Config.Source)) {
|
|
//Switched to another console, or game doesn't support the same memory type, etc.
|
|
ResetToDefaultView();
|
|
}
|
|
ShowFilterDropdown = Config.Source.SupportsCdl();
|
|
MaximumAddress = Math.Max(0, DebugApi.GetMemorySize(Config.Source) - 1);
|
|
|
|
Dispatcher.UIThread.Post(() => {
|
|
Config.SelectedPreset = selectedPreset;
|
|
LoadSelectedPreset(true);
|
|
});
|
|
}
|
|
|
|
private void ResetToDefaultView()
|
|
{
|
|
//Reset to the default view for each console (show all of VRAM)
|
|
Config.Source = CpuType.GetVramMemoryType();
|
|
Config.StartAddress = 0;
|
|
switch(CpuType) {
|
|
case CpuType.Snes:
|
|
case CpuType.Nes:
|
|
case CpuType.Gameboy:
|
|
ApplyPpuPreset();
|
|
break;
|
|
|
|
case CpuType.Pce:
|
|
ApplyBgPreset(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void SelectTile(MemoryType type, int address, TileFormat format, TileLayout layout, int paletteIndex)
|
|
{
|
|
Config.Source = type;
|
|
Config.Format = format;
|
|
SelectedPalette = paletteIndex;
|
|
Config.StartAddress = address / AddressIncrement * AddressIncrement;
|
|
Config.Layout = layout;
|
|
PixelSize tileSize = Config.Format.GetTileSize();
|
|
int bytesPerTile = Config.Format.GetBytesPerTile();
|
|
|
|
int gap = address - Config.StartAddress;
|
|
int tileNumber = gap / bytesPerTile;
|
|
int tilesPerRow = ColumnCount * 8 / tileSize.Width;
|
|
|
|
PixelPoint pos = new PixelPoint(tileNumber % tilesPerRow, tileNumber / tilesPerRow);
|
|
pos = ToLayoutCoordinates(Config.Layout, pos);
|
|
SelectionRect = new Rect(pos.X * tileSize.Width, pos.Y * tileSize.Height, tileSize.Width, tileSize.Height);
|
|
}
|
|
|
|
private PixelPoint ToLayoutCoordinates(TileLayout layout, PixelPoint pos)
|
|
{
|
|
int column = pos.X;
|
|
int row = pos.Y;
|
|
|
|
switch(layout) {
|
|
case TileLayout.SingleLine8x16: {
|
|
int displayColumn = column / 2 + ((row & 0x01) != 0 ? ColumnCount / 2 : 0);
|
|
int displayRow = (row & ~0x01) + ((column & 0x01) != 0 ? 1 : 0);
|
|
return new PixelPoint(displayColumn, displayRow);
|
|
}
|
|
|
|
case TileLayout.SingleLine16x16: {
|
|
int displayColumn = (column / 2) + (column & 0x01) + ((row & 0x01) != 0 ? ColumnCount / 2 : 0) + ((column & 0x02) != 0 ? -1 : 0);
|
|
int displayRow = (row & ~0x01) + ((column & 0x02) != 0 ? 1 : 0);
|
|
return new PixelPoint(displayColumn, displayRow);
|
|
}
|
|
|
|
case TileLayout.Normal:
|
|
return pos;
|
|
|
|
default:
|
|
throw new NotImplementedException("TileLayout not supported");
|
|
}
|
|
}
|
|
|
|
private PixelPoint FromLayoutCoordinates(TileLayout layout, PixelPoint pos)
|
|
{
|
|
int column = pos.X;
|
|
int row = pos.Y;
|
|
|
|
switch(layout) {
|
|
case TileLayout.SingleLine8x16: {
|
|
//A0 B0 C0 D0 -> A0 A1 B0 B1
|
|
//A1 B1 C1 D1 C0 C1 D0 D1
|
|
int displayColumn = (column * 2) % ColumnCount + (row & 0x01);
|
|
int displayRow = (row & ~0x01) + ((column >= ColumnCount / 2) ? 1 : 0);
|
|
return new PixelPoint(displayColumn, displayRow);
|
|
}
|
|
|
|
case TileLayout.SingleLine16x16: {
|
|
//A0 A1 B0 B1 C0 C1 D0 D1 -> A0 A1 A2 A3 B0 B1 B2 B3
|
|
//A2 A3 B2 B3 C2 C3 D2 D3 C0 C1 C2 C3 D0 D1 D2 D3
|
|
//E0 E1 F0 F1 G0 G1 H0 H1 -> E0 E1 E2 E3 F0 F1 F2 F3
|
|
//E2 E3 F2 F3 G2 G3 H2 H3 G0 G1 G2 G3 H0 H1 H2 H3
|
|
int displayColumn = ((column & ~0x01) * 2 + ((row & 0x01) != 0 ? 2 : 0) + (column & 0x01)) % ColumnCount;
|
|
int displayRow = (row & ~0x01) + ((column >= ColumnCount / 2) ? 1 : 0);
|
|
return new PixelPoint(displayColumn, displayRow);
|
|
}
|
|
|
|
case TileLayout.Normal:
|
|
return pos;
|
|
|
|
default:
|
|
throw new NotImplementedException("TileLayout not supported");
|
|
}
|
|
}
|
|
|
|
private void UpdatePreviewPanel()
|
|
{
|
|
if(SelectionRect == default) {
|
|
PreviewPanel = null;
|
|
} else {
|
|
PreviewPanel = GetPreviewPanel(PixelPoint.FromPoint(SelectionRect.TopLeft, 1), PreviewPanel);
|
|
}
|
|
|
|
if(ViewerTooltip != null && ViewerMousePos != null) {
|
|
GetPreviewPanel(ViewerMousePos.Value, ViewerTooltip);
|
|
}
|
|
}
|
|
|
|
private void Config_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if(!_inGameLoaded) {
|
|
RefreshTab();
|
|
}
|
|
}
|
|
|
|
public void RefreshData()
|
|
{
|
|
_ppuState = DebugApi.GetPpuState(CpuType);
|
|
|
|
RefreshPalette();
|
|
|
|
int bytesPerTile = Config.Format.GetBytesPerTile();
|
|
int tileCount = Config.RowCount * Config.ColumnCount;
|
|
int totalSize = bytesPerTile * tileCount;
|
|
|
|
lock(_updateLock) {
|
|
DebugApi.GetMemoryValues(Config.Source, (uint)Config.StartAddress, (uint)(Config.StartAddress + totalSize - 1), ref _coreSourceData);
|
|
}
|
|
|
|
RefreshTab();
|
|
}
|
|
|
|
private void RefreshPalette()
|
|
{
|
|
DebugPaletteInfo palette = DebugApi.GetPaletteInfo(CpuType, new GetPaletteInfoOptions() { Format = Config.Format });
|
|
var paletteColors = palette.GetRgbPalette();
|
|
var rawPalette = palette.GetRawPalette();
|
|
var rawFormat = palette.RawFormat;
|
|
var paletteColumnCount = paletteColors.Length > 16 ? 16 : 4;
|
|
|
|
Dispatcher.UIThread.Post(() => {
|
|
PaletteColors = paletteColors;
|
|
RawPalette = rawPalette;
|
|
RawFormat = rawFormat;
|
|
PaletteColumnCount = paletteColumnCount;
|
|
});
|
|
}
|
|
|
|
private void RefreshTab()
|
|
{
|
|
if(_refreshPending) {
|
|
return;
|
|
}
|
|
|
|
_refreshPending = true;
|
|
Dispatcher.UIThread.Post(() => {
|
|
InternalRefreshTab();
|
|
_refreshPending = false;
|
|
});
|
|
}
|
|
|
|
private void InternalRefreshTab()
|
|
{
|
|
if(Disposed || PaletteColors.Length == 0) {
|
|
return;
|
|
}
|
|
|
|
InitBitmap();
|
|
|
|
lock(_updateLock) {
|
|
Array.Resize(ref _sourceData, _coreSourceData.Length);
|
|
Array.Copy(_coreSourceData, _sourceData, _coreSourceData.Length);
|
|
}
|
|
|
|
using(var framebuffer = ViewerBitmap.Lock()) {
|
|
DebugApi.GetTileView(CpuType, GetOptions(), _sourceData, _sourceData.Length, PaletteColors, framebuffer.FrameBuffer.Address);
|
|
}
|
|
|
|
if(IsNesChrModeEnabled) {
|
|
DrawNesChrPageDelimiters();
|
|
} else {
|
|
PageDelimiters = null;
|
|
}
|
|
|
|
UpdatePreviewPanel();
|
|
LoadSelectedPreset(true);
|
|
}
|
|
|
|
private int GetTileAddress(PixelPoint pixelPosition)
|
|
{
|
|
PixelSize tileSize = Config.Format.GetTileSize();
|
|
int bytesPerTile = Config.Format.GetBytesPerTile();
|
|
PixelPoint pos = FromLayoutCoordinates(Config.Layout, new PixelPoint(pixelPosition.X / tileSize.Width, pixelPosition.Y / tileSize.Height));
|
|
int offset = (pos.Y * ColumnCount * 8 / tileSize.Width + pos.X) * bytesPerTile;
|
|
return (Config.StartAddress + offset) % (MaximumAddress + 1);
|
|
}
|
|
|
|
private int GetSelectedTileAddress()
|
|
{
|
|
PixelPoint p;
|
|
if(ViewerMousePos.HasValue) {
|
|
p = ViewerMousePos.Value;
|
|
} else {
|
|
if(SelectionRect == default) {
|
|
return -1;
|
|
}
|
|
p = PixelPoint.FromPoint(SelectionRect.TopLeft, 1);
|
|
}
|
|
|
|
return GetTileAddress(p);
|
|
}
|
|
|
|
public DynamicTooltip? GetPreviewPanel(PixelPoint p, DynamicTooltip? tooltipToUpdate)
|
|
{
|
|
TooltipEntries entries = tooltipToUpdate?.Items ?? new();
|
|
|
|
entries.StartUpdate();
|
|
|
|
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);
|
|
|
|
int address = GetTileAddress(cropRect.TopLeft);
|
|
if(Config.Source.IsRelativeMemory()) {
|
|
entries.AddEntry("Tile address (" + Config.Source.GetShortName() + ")", FormatAddress(address, Config.Source));
|
|
|
|
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = address, Type = Config.Source });
|
|
if(absAddress.Address >= 0) {
|
|
entries.AddEntry("Tile address (" + absAddress.Type.GetShortName() + ")", FormatAddress(absAddress.Address, absAddress.Type));
|
|
}
|
|
} else {
|
|
entries.AddEntry("Tile address", FormatAddress(address, Config.Source));
|
|
}
|
|
|
|
if(ShowNesTileIndex) {
|
|
entries.AddEntry("Tile index", "$" + ((address >> 4) & 0xFF).ToString("X2"));
|
|
}
|
|
|
|
entries.EndUpdate();
|
|
|
|
if(tooltipToUpdate != null) {
|
|
return tooltipToUpdate;
|
|
} else {
|
|
return new DynamicTooltip() { Items = entries };
|
|
}
|
|
}
|
|
|
|
private string FormatAddress(int address, MemoryType memType)
|
|
{
|
|
if(memType.IsWordAddressing()) {
|
|
return $"${address / 2:X4}.w";
|
|
} else {
|
|
return $"${address:X4}";
|
|
}
|
|
}
|
|
|
|
private ContextMenuAction GetEditTileAction(int columnCount, int rowCount, Window wnd)
|
|
{
|
|
return new ContextMenuAction() {
|
|
ActionType = ActionType.Custom,
|
|
CustomText = $"{columnCount}x{rowCount} ({GridSizeX * columnCount}px x {GridSizeY * rowCount}px)",
|
|
IsEnabled = () => GetSelectedTileAddress() >= 0,
|
|
OnClick = () => EditTileGrid(columnCount, rowCount, wnd)
|
|
};
|
|
}
|
|
|
|
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,
|
|
CpuType,
|
|
RefreshTiming.Config.RefreshScanline,
|
|
RefreshTiming.Config.RefreshCycle
|
|
);
|
|
}
|
|
|
|
private void DrawNesChrPageDelimiters()
|
|
{
|
|
double pageHeight = ((double)256 / ColumnCount) * 8;
|
|
double y = pageHeight;
|
|
List<PictureViewerLine> delimiters = new List<PictureViewerLine>();
|
|
int yMax = RowCount * 8;
|
|
|
|
while(y < yMax) {
|
|
//Hide delimiter if the selected tile is right above or below it
|
|
if(Math.Abs(SelectionRect.Top - y) >= 5 && Math.Abs(SelectionRect.Bottom - y) >= 5) {
|
|
Point start = new Point(0, y);
|
|
Point end = new Point(ColumnCount * 8 - 1, y);
|
|
delimiters.Add(new PictureViewerLine() { Start = start, End = end, Color = Colors.Black });
|
|
delimiters.Add(new PictureViewerLine() { Start = start, End = end, Color = Colors.White, DashStyle = new DashStyle(DashStyle.Dash.Dashes, 0) });
|
|
}
|
|
y += pageHeight;
|
|
}
|
|
PageDelimiters = delimiters;
|
|
}
|
|
|
|
private bool ShowNesTileIndex
|
|
{
|
|
get { return Config.Source.IsPpuMemory() && Config.Source.ToCpuType() == CpuType.Nes && (Config.StartAddress & 0xFFF) == 0; }
|
|
}
|
|
|
|
private bool IsNesChrModeEnabled
|
|
{
|
|
get
|
|
{
|
|
if(ShowNesTileIndex) {
|
|
double rowsPerPage = (double)256 / ColumnCount;
|
|
return rowsPerPage == Math.Floor(rowsPerPage);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private GetTileViewOptions GetOptions()
|
|
{
|
|
return new GetTileViewOptions() {
|
|
MemType = Config.Source,
|
|
Format = Config.Format,
|
|
Width = ColumnCount,
|
|
Height = RowCount,
|
|
Palette = SelectedPalette,
|
|
Layout = Config.Layout,
|
|
Filter = ShowFilterDropdown ? Config.Filter : TileFilter.None,
|
|
StartAddress = Config.StartAddress,
|
|
Background = Config.Background,
|
|
UseGrayscalePalette = Config.UseGrayscalePalette
|
|
};
|
|
}
|
|
|
|
[MemberNotNull(nameof(ViewerBitmap))]
|
|
private void InitBitmap()
|
|
{
|
|
int width = ColumnCount * 8;
|
|
int height = RowCount * 8;
|
|
if(ViewerBitmap == null || ViewerBitmap.PixelSize.Width != width || ViewerBitmap.PixelSize.Height != height) {
|
|
ViewerBitmap = new DynamicBitmap(new PixelSize(width, height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul);
|
|
}
|
|
}
|
|
|
|
public void OnGameLoaded()
|
|
{
|
|
Dispatcher.UIThread.Post(() => {
|
|
_inGameLoaded = true;
|
|
InitForCpuType();
|
|
RefreshData();
|
|
_inGameLoaded = false;
|
|
});
|
|
}
|
|
|
|
private void ApplyPresetValues(PresetValues preset, bool keepUserConfig)
|
|
{
|
|
Config.Source = preset.Source ?? Config.Source;
|
|
Config.Format = preset.Format ?? Config.Format;
|
|
Config.Filter = preset.Filter ?? Config.Filter;
|
|
Config.RowCount = preset.RowCount ?? Config.RowCount;
|
|
Config.ColumnCount = preset.ColumnCount ?? Config.ColumnCount;
|
|
Config.StartAddress = preset.StartAddress ?? Config.StartAddress;
|
|
if(!keepUserConfig) {
|
|
SelectedPalette = preset.SelectedPalette ?? SelectedPalette;
|
|
Config.Background = preset.Background ?? Config.Background;
|
|
Config.Layout = preset.Layout ?? Config.Layout;
|
|
}
|
|
}
|
|
|
|
private void LoadSelectedPreset(bool keepUserConfig)
|
|
{
|
|
if(Config.SelectedPreset != "") {
|
|
ConfigPreset? preset = ConfigPresets.Find(x => x.Name == Config.SelectedPreset);
|
|
if(preset != null) {
|
|
PresetValues? values = preset.GetPresetValues();
|
|
if(values != null) {
|
|
ApplyPresetValues(values, keepUserConfig);
|
|
Config.SelectedPreset = preset.Name;
|
|
preset.Selected = true;
|
|
}
|
|
} else {
|
|
Config.SelectedPreset = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ClearPresetSelection()
|
|
{
|
|
if(Config.SelectedPreset != "") {
|
|
ConfigPreset? preset = ConfigPresets.Find(x => x.Name == Config.SelectedPreset);
|
|
if(preset != null) {
|
|
preset.Selected = false;
|
|
}
|
|
Config.SelectedPreset = "";
|
|
}
|
|
}
|
|
|
|
private ConfigPreset CreatePreset(int row, string name, Func<PresetValues?> getPresetValues)
|
|
{
|
|
ConfigPreset preset = new ConfigPreset(name, getPresetValues, () => {
|
|
PresetValues? preset = getPresetValues();
|
|
if(preset == null) {
|
|
return;
|
|
}
|
|
|
|
ApplyPresetValues(preset, false);
|
|
|
|
if(Config.SelectedPreset != name) {
|
|
ClearPresetSelection();
|
|
ConfigPreset? cfgPeset = ConfigPresets.Find(x => x.Name == name);
|
|
if(cfgPeset != null) {
|
|
Config.SelectedPreset = name;
|
|
cfgPeset.Selected = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
ConfigPresetRows[row].Add(preset);
|
|
return preset;
|
|
}
|
|
|
|
private List<ConfigPreset> GetConfigPresets()
|
|
{
|
|
ConfigPresetRows = new() { new(), new(), new() };
|
|
|
|
switch(CpuType) {
|
|
case CpuType.Snes:
|
|
return new() {
|
|
CreatePreset(0, "PPU", () => ApplyPpuPreset()),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset()),
|
|
CreatePreset(1, "BG1", () => ApplyBgPreset(0)),
|
|
CreatePreset(1, "BG2", () => ApplyBgPreset(1)),
|
|
CreatePreset(1, "BG3", () => ApplyBgPreset(2)),
|
|
CreatePreset(1, "BG4", () => ApplyBgPreset(3)),
|
|
CreatePreset(2, "OAM1", () => ApplySpritePreset(0)),
|
|
CreatePreset(2, "OAM2", () => ApplySpritePreset(1)),
|
|
};
|
|
|
|
case CpuType.Nes:
|
|
return new() {
|
|
CreatePreset(0, "PPU", () => ApplyPpuPreset()),
|
|
CreatePreset(0, "CHR", () => ApplyChrPreset()),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset()),
|
|
CreatePreset(1, "BG", () => ApplyBgPreset(0)),
|
|
CreatePreset(1, "OAM", () => ApplySpritePreset(0)),
|
|
};
|
|
|
|
case CpuType.Gameboy:
|
|
if(DebugApi.GetPpuState<GbPpuState>(CpuType.Gameboy).CgbEnabled) {
|
|
return new() {
|
|
CreatePreset(0, "PPU", () => ApplyPpuPreset()),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset()),
|
|
CreatePreset(1, "BG1", () => ApplyBgPreset(0)),
|
|
CreatePreset(1, "BG2", () => ApplyBgPreset(1)),
|
|
CreatePreset(2, "OAM1", () => ApplySpritePreset(0)),
|
|
CreatePreset(2, "OAM2", () => ApplySpritePreset(1)),
|
|
};
|
|
} else {
|
|
return new() {
|
|
CreatePreset(0, "PPU", () => ApplyPpuPreset()),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset()),
|
|
CreatePreset(1, "BG", () => ApplyBgPreset(0)),
|
|
CreatePreset(1, "OAM", () => ApplySpritePreset(0)),
|
|
};
|
|
}
|
|
|
|
case CpuType.Pce:
|
|
if(DebugApi.GetConsoleState<PceState>(ConsoleType.PcEngine).IsSuperGrafx) {
|
|
return new() {
|
|
CreatePreset(0, "BG1", () => ApplyBgPreset(0)),
|
|
CreatePreset(0, "SPR1", () => ApplySpritePreset(0)),
|
|
CreatePreset(1, "BG2", () => ApplyBgPreset(1)),
|
|
CreatePreset(1, "SPR2", () => ApplySpritePreset(1)),
|
|
CreatePreset(2, "ROM", () => ApplyPrgPreset()),
|
|
};
|
|
} else {
|
|
return new() {
|
|
CreatePreset(0, "BG", () => ApplyBgPreset(0)),
|
|
CreatePreset(0, "Sprites", () => ApplySpritePreset(0)),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset()),
|
|
};
|
|
}
|
|
|
|
case CpuType.Sms:
|
|
return new() {
|
|
CreatePreset(0, "VDP", () => ApplyPpuPreset()),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset())
|
|
};
|
|
|
|
case CpuType.Gba:
|
|
return new() {
|
|
CreatePreset(0, "VRAM", () => ApplyPpuPreset()),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset()),
|
|
CreatePreset(1, "BG1", () => ApplyBgPreset(0)),
|
|
CreatePreset(1, "BG2", () => ApplyBgPreset(1)),
|
|
CreatePreset(1, "BG3", () => ApplyBgPreset(2)),
|
|
CreatePreset(1, "BG4", () => ApplyBgPreset(3)),
|
|
CreatePreset(2, "Sprites", () => ApplySpritePreset(0)),
|
|
};
|
|
|
|
case CpuType.Ws:
|
|
return new() {
|
|
CreatePreset(0, "PPU", () => ApplyPpuPreset()),
|
|
CreatePreset(0, "ROM", () => ApplyPrgPreset()),
|
|
CreatePreset(1, "Bank 0", () => ApplyBgPreset(0)),
|
|
CreatePreset(1, "Bank 1", () => ApplyBgPreset(1)),
|
|
};
|
|
|
|
default:
|
|
throw new Exception("Unsupported CPU type");
|
|
}
|
|
}
|
|
|
|
private PresetValues? ApplyPrgPreset()
|
|
{
|
|
BaseState? state = _ppuState;
|
|
if(state == null) {
|
|
return null;
|
|
}
|
|
|
|
PresetValues preset = new();
|
|
preset.Source = CpuType.GetPrgRomMemoryType();
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 32;
|
|
preset.Layout = TileLayout.Normal;
|
|
|
|
if(CpuType == CpuType.Ws) {
|
|
WsPpuState ppu = (WsPpuState)state;
|
|
preset.Format = ppu.Mode.ToTileFormat();
|
|
} else {
|
|
preset.Format = (TileFormat)AvailableFormats[0];
|
|
}
|
|
|
|
return preset;
|
|
}
|
|
|
|
private PresetValues? ApplyChrPreset()
|
|
{
|
|
PresetValues preset = new();
|
|
preset.Source = DebugApi.GetMemorySize(MemoryType.NesChrRam) > 0 ? MemoryType.NesChrRam : MemoryType.NesChrRom;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 32;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.NesBpp2;
|
|
return preset;
|
|
}
|
|
|
|
private PresetValues? ApplyPpuPreset()
|
|
{
|
|
BaseState? state = _ppuState;
|
|
if(state == null) {
|
|
return null;
|
|
}
|
|
|
|
PresetValues preset = new();
|
|
|
|
switch(CpuType) {
|
|
case CpuType.Snes: {
|
|
preset.Source = MemoryType.SnesVideoRam;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 128;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.Bpp4;
|
|
break;
|
|
}
|
|
|
|
case CpuType.Nes: {
|
|
preset.Source = MemoryType.NesPpuMemory;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 32;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.NesBpp2;
|
|
break;
|
|
}
|
|
|
|
case CpuType.Gameboy: {
|
|
preset.Source = MemoryType.GbVideoRam;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 32;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.Bpp2;
|
|
break;
|
|
}
|
|
|
|
case CpuType.Sms: {
|
|
SmsVdpState vdp = (SmsVdpState)state;
|
|
preset.Source = MemoryType.SmsVideoRam;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = vdp.UseMode4 ? 16 : 32;
|
|
preset.RowCount = vdp.UseMode4 ? 32 : 64;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = vdp.UseMode4 ? TileFormat.SmsBpp4 : TileFormat.SmsSgBpp1;
|
|
if(!vdp.UseMode4) {
|
|
preset.SelectedPalette = 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Gba: {
|
|
preset.Source = MemoryType.GbaVideoRam;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 128;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.GbaBpp4;
|
|
break;
|
|
}
|
|
|
|
case CpuType.Ws: {
|
|
WsPpuState ppu = (WsPpuState)state;
|
|
preset.Source = MemoryType.WsWorkRam;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 128;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = ppu.Mode.ToTileFormat();
|
|
break;
|
|
}
|
|
}
|
|
return preset;
|
|
}
|
|
|
|
private PresetValues? ApplyBgPreset(int layer)
|
|
{
|
|
BaseState? state = _ppuState;
|
|
if(state == null) {
|
|
return null;
|
|
}
|
|
|
|
PresetValues preset = new();
|
|
|
|
switch(CpuType) {
|
|
case CpuType.Snes: {
|
|
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;
|
|
preset.Source = MemoryType.SnesVideoRam;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 64;
|
|
preset.Layout = TileLayout.Normal;
|
|
if(ppu.BgMode == 7) {
|
|
preset.Format = ppu.ExtBgEnabled ? TileFormat.Mode7ExtBg : (ppu.DirectColorMode ? TileFormat.Mode7DirectColor : TileFormat.Mode7);
|
|
preset.StartAddress = 0;
|
|
preset.SelectedPalette = 0;
|
|
} else {
|
|
preset.StartAddress = ppu.Layers[layer].ChrAddress * 2;
|
|
preset.Format = layerBpp[ppu.BgMode, layer] switch {
|
|
2 => TileFormat.Bpp2,
|
|
4 => TileFormat.Bpp4,
|
|
8 => ppu.DirectColorMode ? TileFormat.DirectColor : TileFormat.Bpp8,
|
|
_ => TileFormat.Bpp2
|
|
};
|
|
|
|
if(layerBpp[ppu.BgMode, layer] == 8 || SelectedPalette >= (layerBpp[ppu.BgMode, layer] == 2 ? 32 : 8)) {
|
|
preset.SelectedPalette = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Nes: {
|
|
NesPpuState ppu = (NesPpuState)state;
|
|
preset.Source = MemoryType.NesPpuMemory;
|
|
preset.StartAddress = ppu.Control.BackgroundPatternAddr;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 16;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.NesBpp2;
|
|
if(SelectedPalette >= 4) {
|
|
preset.SelectedPalette = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Gameboy: {
|
|
GbPpuState ppu = (GbPpuState)state;
|
|
preset.Source = MemoryType.GbVideoRam;
|
|
preset.StartAddress = (layer == 0 ? 0 : 0x2000) | (ppu.BgTileSelect ? 0 : 0x800);
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 16;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.Bpp2;
|
|
preset.Background = ppu.CgbEnabled ? TileBackground.PaletteColor : TileBackground.Default;
|
|
if(!ppu.CgbEnabled || SelectedPalette > 8) {
|
|
preset.SelectedPalette = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Pce: {
|
|
preset.Source = layer == 0 ? MemoryType.PceVideoRam : MemoryType.PceVideoRamVdc2;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 32;
|
|
preset.RowCount = 64;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.Bpp4;
|
|
preset.Background = TileBackground.Default;
|
|
if(SelectedPalette >= 16) {
|
|
preset.SelectedPalette = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Gba: {
|
|
GbaPpuState ppu = (GbaPpuState)state;
|
|
preset.Source = MemoryType.GbaVideoRam;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 64;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.StartAddress = ppu.BgLayers[layer].TilesetAddr;
|
|
preset.Format = ppu.BgLayers[layer].Bpp8Mode ? TileFormat.GbaBpp8 : TileFormat.GbaBpp4;
|
|
if(preset.Format == TileFormat.GbaBpp8 || preset.Format == TileFormat.GbaBpp4 && preset.SelectedPalette > 16) {
|
|
preset.SelectedPalette = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Ws: {
|
|
WsPpuState ppu = (WsPpuState)state;
|
|
int bank0Addr = ppu.Mode >= WsVideoMode.Color4bpp ? 0x4000 : 0x2000;
|
|
int bank1Addr = ppu.Mode >= WsVideoMode.Color4bpp ? 0x8000 : (ppu.Mode == WsVideoMode.Monochrome ? 0x2000 : 0x4000);
|
|
|
|
preset.Source = MemoryType.WsWorkRam;
|
|
preset.StartAddress = layer == 0 ? bank0Addr : bank1Addr;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 128;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = ppu.Mode.ToTileFormat();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return preset;
|
|
}
|
|
|
|
private PresetValues? ApplySpritePreset(int layer)
|
|
{
|
|
BaseState? state = _ppuState;
|
|
if(state == null) {
|
|
return null;
|
|
}
|
|
|
|
PresetValues preset = new();
|
|
|
|
switch(CpuType) {
|
|
case CpuType.Snes: {
|
|
SnesPpuState ppu = (SnesPpuState)state;
|
|
preset.Source = MemoryType.SnesVideoRam;
|
|
preset.Format = TileFormat.Bpp4;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 16;
|
|
preset.StartAddress = (ppu.OamBaseAddress + (layer == 1 ? ppu.OamAddressOffset : 0)) * 2;
|
|
if(SelectedPalette < 8 || SelectedPalette >= 16) {
|
|
preset.SelectedPalette = 8;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Nes: {
|
|
NesPpuState ppu = (NesPpuState)state;
|
|
if(ppu.Control.LargeSprites) {
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 32;
|
|
preset.Layout = TileLayout.SingleLine8x16;
|
|
} else {
|
|
preset.StartAddress = ppu.Control.SpritePatternAddr;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 16;
|
|
preset.Layout = TileLayout.Normal;
|
|
}
|
|
preset.Source = MemoryType.NesPpuMemory;
|
|
if(SelectedPalette < 4 || SelectedPalette >= 8) {
|
|
preset.SelectedPalette = 4;
|
|
}
|
|
preset.Format = TileFormat.NesBpp2;
|
|
break;
|
|
}
|
|
|
|
case CpuType.Gameboy: {
|
|
GbPpuState ppu = (GbPpuState)state;
|
|
preset.Source = MemoryType.GbVideoRam;
|
|
preset.StartAddress = layer == 0 ? 0 : 0x2000;
|
|
preset.ColumnCount = 16;
|
|
preset.RowCount = 16;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Background = TileBackground.Black;
|
|
preset.Format = TileFormat.Bpp2;
|
|
if(ppu.CgbEnabled && SelectedPalette < 8) {
|
|
preset.SelectedPalette = 8;
|
|
} else if(!ppu.CgbEnabled && SelectedPalette == 0) {
|
|
preset.SelectedPalette = 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Pce: {
|
|
preset.Source = layer == 0 ? MemoryType.PceVideoRam : MemoryType.PceVideoRamVdc2;
|
|
preset.StartAddress = 0;
|
|
preset.ColumnCount = 32;
|
|
preset.RowCount = 64;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.PceSpriteBpp4;
|
|
preset.Background = TileBackground.Default;
|
|
if(SelectedPalette < 16) {
|
|
preset.SelectedPalette = 16;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CpuType.Gba: {
|
|
preset.Source = MemoryType.GbaVideoRam;
|
|
preset.StartAddress = 0x10000;
|
|
preset.ColumnCount = 32;
|
|
preset.RowCount = 32;
|
|
preset.Layout = TileLayout.Normal;
|
|
preset.Format = TileFormat.GbaBpp4;
|
|
preset.Background = TileBackground.Default;
|
|
if(SelectedPalette < 16) {
|
|
preset.SelectedPalette = 16;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return preset;
|
|
}
|
|
}
|
|
|
|
public class ConfigPreset : ViewModelBase
|
|
{
|
|
public string Name { get; }
|
|
public Func<PresetValues?> GetPresetValues { get; }
|
|
public ReactiveCommand<Unit, Unit> ClickCommand { get; }
|
|
public Action ApplyPreset { get; }
|
|
|
|
[Reactive] public bool Selected { get; set; }
|
|
|
|
public ConfigPreset(string name, Func<PresetValues?> getPresetValues, Action applyPreset)
|
|
{
|
|
Name = name;
|
|
GetPresetValues = getPresetValues;
|
|
ApplyPreset = applyPreset;
|
|
ClickCommand = ReactiveCommand.Create(ApplyPreset);
|
|
}
|
|
}
|
|
|
|
public class PresetValues
|
|
{
|
|
public MemoryType? Source { get; set; }
|
|
public TileFormat? Format { get; set; }
|
|
public TileLayout? Layout { get; set; }
|
|
public TileFilter? Filter { get; set; }
|
|
public TileBackground? Background { get; set; }
|
|
public int? RowCount { get; set; }
|
|
public int? ColumnCount { get; set; }
|
|
public int? StartAddress { get; set; }
|
|
public int? SelectedPalette { get; set; }
|
|
}
|
|
}
|