Skip to content

Commit

Permalink
Clean up HelloWorld sample
Browse files Browse the repository at this point in the history
  • Loading branch information
YoshiRulz committed Feb 1, 2025
1 parent de1a7b1 commit 756c79f
Showing 1 changed file with 66 additions and 72 deletions.
138 changes: 66 additions & 72 deletions ExternalToolProjects/HelloWorld/CustomMainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,91 +15,93 @@ namespace HelloWorld
public partial class CustomMainForm : ToolFormBase, IExternalToolForm
{
/// <remarks>
/// <see cref="RequiredServiceAttribute">RequiredServices</see> are populated by EmuHawk at runtime.
/// These remain supported, but you should only use them when there is no API that does what you want.
/// <see cref="ApiContainer"/> can be used as a shorthand for accessing the various APIs, more like the Lua syntax.
/// </remarks>
[RequiredService]
private IEmulator? _emu { get; set; }
public ApiContainer? _apiContainer { get; set; }

[RequiredService]
private IMemoryDomains? _memoryDomains { get; set; }
private ApiContainer APIs
=> _apiContainer!;

/// <remarks>
/// <see cref="RequiredApiAttribute">RequiredApis</see> are populated by EmuHawk at runtime.
/// You can have props for any subset of the available APIs, or use an <see cref="ApiContainer"/> to get them all at once.
/// <see cref="RequiredServiceAttribute">RequiredServices</see> are populated by EmuHawk at runtime.
/// These remain supported, but you should only use them when there is no API that does what you want.
/// </remarks>
[RequiredApi]
private IEmulationApi? _emuApi { get; set; }
[RequiredService]
public IMemoryDomains? _memoryDomains { get; set; }

private IMemoryDomains MemoryDomains
=> _memoryDomains!;

/// <remarks>
/// <see cref="ApiContainer"/> can be used as a shorthand for accessing the various APIs, more like the Lua syntax.
/// An example of a hack. Hacks should be your last resort because they're prone to break with new releases.
/// </remarks>
public ApiContainer? _apiContainer { get; set; }
private Config GlobalConfig
=> ((EmulationApi) APIs.Emulation).ForbiddenConfigReference;

private ApiContainer APIs => _apiContainer!;
private string SavestatePath
=> Path.Combine(GlobalConfig.PathEntries.SaveStateAbsolutePath(APIs.Emulation.GetSystemId()), "Test");

/// <remarks>
/// An example of a hack. Hacks should be your last resort because they're prone to break with new releases.
/// Another hack because there's no API for controlling the RAM Watch tool.
/// </remarks>
private Config GlobalConfig => (_emuApi as EmulationApi ?? throw new Exception("required API wasn't fulfilled")).ForbiddenConfigReference;

private WatchList? _watches;

private WatchList Watches
{
get
=> _watches ??= new(MemoryDomains, APIs.Emulation.GetSystemId()) // technically this should be `GlobalEmulator.SystemId` instead of `GlobalGame.SystemId` (which this is) but it's close enough
{
WatchList CreateWatches()
{
var w = new WatchList(_memoryDomains, _emu?.SystemId ?? string.Empty);
w.AddRange(new[] {
Watch.GenerateWatch(_memoryDomains?.MainMemory, 0x40, WatchSize.Byte, WatchDisplayType.Hex, true),
Watch.GenerateWatch(_memoryDomains?.MainMemory, 0x50, WatchSize.Word, WatchDisplayType.Unsigned, true),
Watch.GenerateWatch(_memoryDomains?.MainMemory, 0x60, WatchSize.DWord, WatchDisplayType.Hex, true)
});
return w;
}
_watches ??= CreateWatches();
return _watches;
}
}
Watch.GenerateWatch(MemoryDomains.MainMemory, 0x40, WatchSize.Byte, WatchDisplayType.Hex, bigEndian: true),
Watch.GenerateWatch(MemoryDomains.MainMemory, 0x50, WatchSize.Word, WatchDisplayType.Unsigned, bigEndian: true),
Watch.GenerateWatch(MemoryDomains.MainMemory, 0x60, WatchSize.DWord, WatchDisplayType.Hex, bigEndian: true),
};

protected override string WindowTitleStatic => "HelloWorld";
private readonly Label[] WatchReadouts;

protected override string WindowTitleStatic
=> "HelloWorld";

public CustomMainForm()
{
InitializeComponent();
label_GameHash.Click += label_GameHash_Click;
Closing += (sender, args) => APIs.EmuClient.SetClientExtraPadding(0, 0, 0, 0);
WatchReadouts = [ label_Watch1, label_Watch2, label_Watch3 ];
label_GameHash.Click += (_, _) => Clipboard.SetText(APIs.Emulation.GetGameInfo()!.Hash);
Closing += (_, _) =>
{
APIs.EmuClient.SetClientExtraPadding(0, 0, 0, 0);
APIs.EmuClient.BeforeQuickSave -= HandleQuickSave;
APIs.EmuClient.BeforeQuickLoad -= HandleQuickLoad;
};
Load += (_, _) =>
{
APIs.EmuClient.BeforeQuickSave += (_, e) =>
{
if (e.Slot != 0) return; // only take effect on slot 0
var basePath = Path.Combine(GlobalConfig.PathEntries.SaveStateAbsolutePath(APIs.Emulation.GetSystemId()), "Test");
if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);
APIs.EmuClient.SaveState(Path.Combine(basePath, e.Name));
e.Handled = true;
};
APIs.EmuClient.BeforeQuickLoad += (_, e) =>
{
if (e.Slot != 0) return; // only take effect on slot 0
var basePath = Path.Combine(GlobalConfig.PathEntries.SaveStateAbsolutePath(APIs.Emulation.GetSystemId()), "Test");
APIs.EmuClient.LoadState(Path.Combine(basePath, e.Name));
e.Handled = true;
};
APIs.EmuClient.BeforeQuickSave += HandleQuickSave;
APIs.EmuClient.BeforeQuickLoad += HandleQuickLoad;
};
}

public void HandleQuickSave(object sender, BeforeQuickSaveEventArgs e)
{
if (e.Slot is not 0) return; // only take effect on slot 0
var basePath = SavestatePath;
if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);
APIs.EmuClient.SaveState(Path.Combine(basePath, e.Name));
e.Handled = true;
}

public void HandleQuickLoad(object sender, BeforeQuickLoadEventArgs e)
{
if (e.Slot is not 0) return; // only take effect on slot 0
APIs.EmuClient.LoadState(Path.Combine(SavestatePath, e.Name));
e.Handled = true;
}

/// <remarks>This is called once when the form is opened, and every time a new movie session starts.</remarks>
public override void Restart()
{
APIs.EmuClient.SetClientExtraPadding(50, 50);
APIs.EmuClient.SetClientExtraPadding(left: 50, top: 50);
var gi = APIs.Emulation.GetGameInfo();
if (!string.IsNullOrEmpty(gi?.Name))
if (gi?.Name is { Length: not 0 } gameName)
{
Watches.RefreshDomains(_memoryDomains, GlobalConfig.RamWatchDefinePrevious);
label_Game.Text = $"You're playing {gi!.Name}";
Watches.RefreshDomains(MemoryDomains, GlobalConfig.RamWatchDefinePrevious);
label_Game.Text = $"You're playing {gameName}";
label_GameHash.Text = $"Hash: {gi.Hash}";
}
else
Expand All @@ -111,33 +113,25 @@ public override void Restart()

public override void UpdateValues(ToolFormUpdateType type)
{
if (!(type == ToolFormUpdateType.PreFrame || type == ToolFormUpdateType.FastPreFrame)
|| string.IsNullOrEmpty(APIs.Emulation.GetGameInfo()?.Name)
|| Watches.Count < 3)
if (type is not (ToolFormUpdateType.PreFrame or ToolFormUpdateType.FastPreFrame)
|| string.IsNullOrEmpty(APIs.Emulation.GetGameInfo()?.Name)
|| Watches.Count < 3)
{
return;
}
Watches.UpdateValues(GlobalConfig.RamWatchDefinePrevious);
label_Watch1.Text = $"First watch ({Watches[0].AddressString}) current value: {Watches[0].ValueString}";
label_Watch2.Text = $"Second watch ({Watches[1].AddressString}) current value: {Watches[1].ValueString}";
label_Watch3.Text = $"Third watch ({Watches[2].AddressString}) current value: {Watches[2].ValueString}";
for (var i = 0; i < 3; i++)
{
WatchReadouts[i].Text = $"Watch 0x{Watches[i].AddressString} current value: {Watches[i].ValueString}";
}
}

private void button1_Click(object sender, EventArgs e) => APIs.EmuClient.DoFrameAdvance();

private void label_GameHash_Click(object sender, EventArgs e) => Clipboard.SetText(APIs.Emulation.GetGameInfo()!.Hash);
private void button1_Click(object sender, EventArgs e)
=> APIs.EmuClient.DoFrameAdvance();

private void loadstate_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(savestateName.Text)) return;
APIs.EmuClient.LoadState(savestateName.Text);
#if false
static void Test(BinaryReader r)
{
var b = new System.Drawing.Bitmap(r.BaseStream);
}
BinaryStateLoader.LoadAndDetect($"{savestateName.Text}.State").GetLump(BinaryStateLump.Framebuffer, false, Test);
#endif
if (!string.IsNullOrWhiteSpace(savestateName.Text)) APIs.EmuClient.LoadState(savestateName.Text);
}

private void saveState_Click(object sender, EventArgs e)
Expand Down

0 comments on commit 756c79f

Please sign in to comment.