fix possible deadlocks with RAIntegration due to dumb threading bs, cleanup some of this code

also make sure to reboot core when starting up RetroAchievements with RAIntegration active (RAIntegration doesn't do this itself it seems)
This commit is contained in:
CasualPokePlayer 2023-01-25 22:28:26 -08:00
parent 95001d0baa
commit f8a5adecb5
10 changed files with 220 additions and 266 deletions

View File

@ -19,7 +19,7 @@ namespace BizHawk.Client.EmuHawk
{
_resolver = new("RA_Integration-x64.dll", hasLimitedLifetime: true);
RA = BizInvoker.GetInvoker<RAInterface>(_resolver, CallingConventionAdapters.Native);
_version = new(Marshal.PtrToStringAnsi(RA.IntegrationVersion()));
_version = new(Marshal.PtrToStringAnsi(RA.IntegrationVersion())!);
Console.WriteLine($"Loaded RetroAchievements v{_version}");
}
@ -58,58 +58,42 @@ namespace BizHawk.Client.EmuHawk
if (_version < minVer)
{
if (mainForm.ShowMessageBox2(
owner: null,
text: "An update is required to use RetroAchievements. Do you want to download the update now?",
caption: "Update",
icon: EMsgBoxIcon.Question,
useOKCancel: false))
{
DetachDll();
var ret = DownloadDll((string)info["LatestVersionUrlX64"]);
AttachDll();
return ret;
}
else
{
return false;
}
if (!mainForm.ShowMessageBox2(
owner: null,
text:
"An update is required to use RetroAchievements. Do you want to download the update now?",
caption: "Update",
icon: EMsgBoxIcon.Question,
useOKCancel: false)) return false;
DetachDll();
var ret = DownloadDll((string)info["LatestVersionUrlX64"]);
AttachDll();
return ret;
}
else if (_version < lastestVer)
{
if (mainForm.ShowMessageBox2(
owner: null,
text: "An optional update is available for RetroAchievements. Do you want to download the update now?",
caption: "Update",
icon: EMsgBoxIcon.Question,
useOKCancel: false))
{
DetachDll();
DownloadDll((string)info["LatestVersionUrlX64"]);
AttachDll();
return true; // even if this fails, should be OK to use the old dll
}
else
{
// don't have to update in this case
return true;
}
}
else
{
return true;
}
}
else
{
mainForm.ShowMessageBox(
owner: null,
text: "Failed to fetch update information, cannot start RetroAchievements.",
caption: "Error",
icon: EMsgBoxIcon.Error);
return false;
if (_version >= lastestVer) return true;
if (!mainForm.ShowMessageBox2(
owner: null,
text:
"An optional update is available for RetroAchievements. Do you want to download the update now?",
caption: "Update",
icon: EMsgBoxIcon.Question,
useOKCancel: false)) return true;
DetachDll();
DownloadDll((string)info["LatestVersionUrlX64"]);
AttachDll();
return true; // even if this fails, should be OK to use the old dll
}
mainForm.ShowMessageBox(
owner: null,
text: "Failed to fetch update information, cannot start RetroAchievements.",
caption: "Error",
icon: EMsgBoxIcon.Error);
return false;
}
catch (Exception ex)
{

View File

@ -43,12 +43,11 @@ namespace BizHawk.Client.EmuHawk
private readonly RAInterface.MenuItem[] _menuItems = new RAInterface.MenuItem[40];
// Memory may be accessed by another thread (for rich presence)
// Memory may be accessed by another thread (mainly rich presence, some other things too)
// and peeks for us are not thread safe, so we need to guard it
private readonly AutoResetEvent _memAccessReady = new(false);
private readonly AutoResetEvent _memAccessGo = new(false);
private readonly AutoResetEvent _memAccessDone = new(false);
private readonly RAMemGuard _memGuard;
private readonly RAMemGuard _memGuard = new();
private bool _firstRestart = true;
private void RebuildMenu()
{
@ -111,8 +110,6 @@ namespace BizHawk.Client.EmuHawk
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback)
: base(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback)
{
_memGuard = new(_memAccessReady, _memAccessGo, _memAccessDone);
RA.InitClient(_mainForm.Handle, "BizHawk", VersionInfo.GetEmuVersion());
_isActive = () => !Emu.IsNull();
@ -125,7 +122,7 @@ namespace BizHawk.Client.EmuHawk
Marshal.Copy(name, 0, buffer, Math.Min(name.Length, 256));
};
_resetEmulator = () => _mainForm.RebootCore();
_loadROM = path => _ = _mainForm.LoadRom(path, new LoadRomArgs { OpenAdvanced = OpenAdvancedSerializer.ParseWithLegacy(path) });
_loadROM = path => _ = _mainForm.LoadRom(path, new() { OpenAdvanced = OpenAdvancedSerializer.ParseWithLegacy(path) });
RA.InstallSharedFunctionsExt(_isActive, _unpause, _pause, _rebuildMenu, _estimateTitle, _resetEmulator, _loadROM);
@ -159,6 +156,25 @@ namespace BizHawk.Client.EmuHawk
public override void Restart()
{
if (_firstRestart)
{
_firstRestart = false;
if (RA.HardcoreModeIsActive())
{
if (!_mainForm.RebootCore())
{
// unset hardcore mode if we fail to reboot core somehow
HandleHardcoreModeDisable("Failed to reboot core.");
}
if (RA.HardcoreModeIsActive() && _mainForm.CurrentlyOpenRomArgs is not null)
{
// if we aren't hardcore anymore, we failed to reboot the core (and didn't call Restart probably)
// if CurrentlyOpenRomArgs is null, then Restart won't be called (as RebootCore returns true immediately), so
return;
}
}
}
var consoleId = SystemIdToConsoleId();
RA.SetConsoleID(consoleId);
@ -168,7 +184,7 @@ namespace BizHawk.Client.EmuHawk
{
_memFunctions = CreateMemoryBanks(consoleId, Domains, Emu.CanDebug() ? Emu.AsDebuggable() : null);
for (int i = 0; i < _memFunctions.Count; i++)
for (var i = 0; i < _memFunctions.Count; i++)
{
_memFunctions[i].MemGuard = _memGuard;
RA.InstallMemoryBank(i, _memFunctions[i].ReadFunc, _memFunctions[i].WriteFunc, _memFunctions[i].BankSize);
@ -209,6 +225,8 @@ namespace BizHawk.Client.EmuHawk
public override void Update()
{
using var access = _memGuard.GetAccess();
if (RA.HardcoreModeIsActive())
{
CheckHardcoreModeConditions();
@ -219,41 +237,32 @@ namespace BizHawk.Client.EmuHawk
RA.SetPaused(true);
}
if (RA.IsOverlayFullyVisible())
if (!RA.IsOverlayFullyVisible()) return;
var ci = new RAInterface.ControllerInput
{
var ci = new RAInterface.ControllerInput
{
UpPressed = _inputManager.ClientControls["RA Up"],
DownPressed = _inputManager.ClientControls["RA Down"],
LeftPressed = _inputManager.ClientControls["RA Left"],
RightPressed = _inputManager.ClientControls["RA Right"],
ConfirmPressed = _inputManager.ClientControls["RA Confirm"],
CancelPressed = _inputManager.ClientControls["RA Cancel"],
QuitPressed = _inputManager.ClientControls["RA Quit"],
};
UpPressed = _inputManager.ClientControls["RA Up"],
DownPressed = _inputManager.ClientControls["RA Down"],
LeftPressed = _inputManager.ClientControls["RA Left"],
RightPressed = _inputManager.ClientControls["RA Right"],
ConfirmPressed = _inputManager.ClientControls["RA Confirm"],
CancelPressed = _inputManager.ClientControls["RA Cancel"],
QuitPressed = _inputManager.ClientControls["RA Quit"],
};
RA.NavigateOverlay(ref ci);
RA.NavigateOverlay(ref ci);
// todo: suppress user inputs with overlay active?
}
if (_memAccessReady.WaitOne(0))
{
_memAccessGo.Set();
_memAccessDone.WaitOne();
}
// todo: suppress user inputs with overlay active?
}
public override void OnFrameAdvance()
{
using var access = _memGuard.GetAccess();
var input = _inputManager.ControllerOutput;
foreach (var resetButton in input.Definition.BoolButtons.Where(b => b.Contains("Power") || b.Contains("Reset")))
if (input.Definition.BoolButtons.Any(b => (b.Contains("Power") || b.Contains("Reset")) && input.IsPressed(b)))
{
if (input.IsPressed(resetButton))
{
RA.OnReset();
break;
}
RA.OnReset();
}
if (Emu.HasMemoryDomains())

View File

@ -102,7 +102,7 @@ namespace BizHawk.Client.EmuHawk
private readonly byte[] _cheevoFormatBuffer = new byte[1024];
public string GetCheevoProgress(int id)
private string GetCheevoProgress(int id)
{
var len = _lib.rc_runtime_format_achievement_measured(ref _runtime, id, _cheevoFormatBuffer, _cheevoFormatBuffer.Length);
return Encoding.ASCII.GetString(_cheevoFormatBuffer, 0, len);

View File

@ -53,9 +53,9 @@ namespace BizHawk.Client.EmuHawk
unsafe
{
var unlocks = (int*)resp.achievement_ids;
for (int i = 0; i < resp.num_achievement_ids; i++)
for (var i = 0; i < resp.num_achievement_ids; i++)
{
if (_cheevos.TryGetValue(unlocks[i], out var cheevo))
if (_cheevos.TryGetValue(unlocks![i], out var cheevo))
{
cheevo.SetUnlocked(hardcore, true);
}
@ -104,18 +104,18 @@ namespace BizHawk.Client.EmuHawk
var cheevos = new Dictionary<int, Cheevo>();
var cptr = (LibRCheevos.rc_api_achievement_definition_t*)resp.achievements;
for (int i = 0; i < resp.num_achievements; i++)
for (var i = 0; i < resp.num_achievements; i++)
{
cheevos.Add(cptr[i].id, new(in cptr[i], allowUnofficialCheevos));
cheevos.Add(cptr![i].id, new(in cptr[i], allowUnofficialCheevos));
}
_cheevos = cheevos;
var lboards = new Dictionary<int, LBoard>();
var lptr = (LibRCheevos.rc_api_leaderboard_definition_t*)resp.leaderboards;
for (int i = 0; i < resp.num_leaderboards; i++)
for (var i = 0; i < resp.num_leaderboards; i++)
{
lboards.Add(lptr[i].id, new(in lptr[i]));
lboards.Add(lptr![i].id, new(in lptr[i]));
}
_lboards = lboards;
@ -133,21 +133,8 @@ namespace BizHawk.Client.EmuHawk
GameBadge = null;
RichPresenseScript = gameData.RichPresenseScript;
var cheevos = new Dictionary<int, Cheevo>();
foreach (var cheevo in gameData.CheevoEnumerable)
{
cheevos.Add(cheevo.ID, new(in cheevo, allowUnofficialCheevos));
}
_cheevos = cheevos;
var lboards = new Dictionary<int, LBoard>();
foreach (var lboard in gameData.LBoardEnumerable)
{
lboards.Add(lboard.ID, new(in lboard));
}
_lboards = lboards;
_cheevos = gameData.CheevoEnumerable.ToDictionary<Cheevo, int, Cheevo>(cheevo => cheevo.ID, cheevo => new(in cheevo, allowUnofficialCheevos));
_lboards = gameData.LBoardEnumerable.ToDictionary<LBoard, int, LBoard>(lboard => lboard.ID, lboard => new(in lboard));
SoftcoreInitUnlocksReady = new(false);
HardcoreInitUnlocksReady = new(false);
@ -159,7 +146,7 @@ namespace BizHawk.Client.EmuHawk
}
}
private async Task<int> SendHashAsync(string hash)
private static async Task<int> SendHashAsync(string hash)
{
var api_params = new LibRCheevos.rc_api_resolve_hash_request_t(null, null, hash);
var ret = 0;
@ -244,7 +231,7 @@ namespace BizHawk.Client.EmuHawk
try
{
var serv_resp = await SendAPIRequest(in api_req).ConfigureAwait(false);
ret = new Bitmap(new MemoryStream(serv_resp));
ret = new(new MemoryStream(serv_resp));
}
catch
{

View File

@ -7,7 +7,6 @@ namespace BizHawk.Client.EmuHawk
public partial class RCheevos
{
private bool RichPresenceActive { get; set; }
private string CurrentRichPresence { get; set; }
private static async Task<bool> StartGameSessionAsync(string username, string api_token, int id)
@ -63,11 +62,9 @@ namespace BizHawk.Client.EmuHawk
}
var now = DateTime.Now;
if ((now - _lastPingTime) >= _pingCooldown)
{
SendPing(Username, ApiToken, _gameData.GameID, CurrentRichPresence);
_lastPingTime = now;
}
if (now - _lastPingTime < _pingCooldown) return;
SendPing(Username, ApiToken, _gameData.GameID, CurrentRichPresence);
_lastPingTime = now;
}
}
}

View File

@ -29,6 +29,7 @@ namespace BizHawk.Client.EmuHawk
}
catch
{
// ignored
}
}
}

View File

@ -246,12 +246,11 @@ namespace BizHawk.Client.EmuHawk
_lib.rc_runtime_reset(ref _runtime);
if (File.Exists(path + ".rap"))
{
using var file = File.OpenRead(path + ".rap");
var buffer = file.ReadAllBytes();
_lib.rc_runtime_deserialize_progress(ref _runtime, buffer, IntPtr.Zero);
}
if (!File.Exists(path + ".rap")) return;
using var file = File.OpenRead(path + ".rap");
var buffer = file.ReadAllBytes();
_lib.rc_runtime_deserialize_progress(ref _runtime, buffer, IntPtr.Zero);
}
// not sure if we really need to do anything here...
@ -333,14 +332,9 @@ namespace BizHawk.Client.EmuHawk
if (gameId != 0)
{
if (_cachedGameDatas.TryGetValue(gameId, out var cachedGameData))
{
_gameData = new GameData(cachedGameData, () => AllowUnofficialCheevos);
}
else
{
_gameData = GetGameData(Username, ApiToken, gameId, () => AllowUnofficialCheevos);
}
_gameData = _cachedGameDatas.TryGetValue(gameId, out var cachedGameData)
? new(cachedGameData, () => AllowUnofficialCheevos)
: GetGameData(Username, ApiToken, gameId, () => AllowUnofficialCheevos);
StartGameSession(Username, ApiToken, gameId);
@ -373,12 +367,12 @@ namespace BizHawk.Client.EmuHawk
}
else
{
_gameData = new GameData();
_gameData = new();
}
}
else
{
_gameData = new GameData();
_gameData = new();
}
// validate addresses now that we have cheevos init
@ -593,13 +587,9 @@ namespace BizHawk.Client.EmuHawk
}
var input = _inputManager.ControllerOutput;
foreach (var resetButton in input.Definition.BoolButtons.Where(b => b.Contains("Power") || b.Contains("Reset")))
if (input.Definition.BoolButtons.Any(b => (b.Contains("Power") || b.Contains("Reset")) && input.IsPressed(b)))
{
if (input.IsPressed(resetButton))
{
_lib.rc_runtime_reset(ref _runtime);
break;
}
_lib.rc_runtime_reset(ref _runtime);
}
if (Emu.HasMemoryDomains())

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using BizHawk.Common;
@ -48,7 +49,7 @@ namespace BizHawk.Client.EmuHawk
buffer.AddRange(new ArraySegment<byte>(buf2048, 0, 128));
var bootSector = (buf2048[35] << 24) | (buf2048[34] << 16) | (buf2048[33] << 8) | buf2048[32];
var numSectors = (buf2048[39] << 24) | (buf2048[38] << 16) | (buf2048[37] << 8) | buf2048[36];
for (int i = 0; i < numSectors; i++)
for (var i = 0; i < numSectors; i++)
{
dsr.ReadLBA_2048(bootSector + i, buf2048, 0);
buffer.AddRange(buf2048);
@ -82,7 +83,7 @@ namespace BizHawk.Client.EmuHawk
return -1;
}
string exePath = "PSX.EXE";
var exePath = "PSX.EXE";
// find SYSTEM.CNF sector
var sector = GetFileSector("SYSTEM.CNF", out _);
@ -154,36 +155,30 @@ namespace BizHawk.Client.EmuHawk
var numLbas = disc.Session1.FirstInformationTrack.NextTrack.LBA - disc.Session1.FirstInformationTrack.LBA;
int bootLen = 0, bootLba = 0, bootOff = 0;
bool byteswapped = false, foundHeader = false;
for (int i = 0; i < numLbas; i++)
for (var i = 0; i < numLbas; i++)
{
dsr.ReadLBA_2352(startLba + i, buf2352, 0);
for (int j = 0; j < (2352 - 32 - 4 - 4); j++)
for (var j = 0; j < 2352 - 32 - 4 - 4; j++)
{
if (buf2352[j] == _jaguarHeader[0])
{
if (_jaguarHeader == Encoding.ASCII.GetString(buf2352, j, 32))
{
bootLen = (buf2352[j + 36] << 24) | (buf2352[j + 37] << 16) | (buf2352[j + 38] << 8) | buf2352[j + 39];
bootLba = startLba + i;
bootOff = j + 32 + 4 + 4;
byteswapped = false;
foundHeader = true;
break;
}
}
else if (buf2352[j] == _jaguarBSHeader[0])
{
if (_jaguarBSHeader == Encoding.ASCII.GetString(buf2352, j, 32))
{
bootLen = (buf2352[j + 37] << 24) | (buf2352[j + 36] << 16) | (buf2352[j + 39] << 8) | buf2352[j + 38];
bootLba = startLba + i;
bootOff = j + 32 + 4 + 4;
byteswapped = true;
foundHeader = true;
break;
}
if (_jaguarHeader != Encoding.ASCII.GetString(buf2352, j, 32)) continue;
bootLen = (buf2352[j + 36] << 24) | (buf2352[j + 37] << 16) | (buf2352[j + 38] << 8) | buf2352[j + 39];
bootLba = startLba + i;
bootOff = j + 32 + 4 + 4;
byteswapped = false;
foundHeader = true;
break;
}
if (buf2352[j] != _jaguarBSHeader[0] || _jaguarBSHeader != Encoding.ASCII.GetString(buf2352, j, 32)) continue;
bootLen = (buf2352[j + 37] << 24) | (buf2352[j + 36] << 16) | (buf2352[j + 39] << 8) | buf2352[j + 38];
bootLba = startLba + i;
bootOff = j + 32 + 4 + 4;
byteswapped = true;
foundHeader = true;
break;
}
if (foundHeader)
@ -256,14 +251,9 @@ namespace BizHawk.Client.EmuHawk
using var sr = new StreamReader(file.GetStream());
var m3u = M3U_File.Read(sr);
m3u.Rebase(Path.GetDirectoryName(ioa.SimplePath));
foreach (var entry in m3u.Entries)
{
var id = HashDisc(entry.Path, consoleID, ++discCount);
if (id.HasValue)
{
ret.Add(id.Value);
}
}
ret.AddRange(m3u.Entries.Select(entry => HashDisc(entry.Path, consoleID, ++discCount))
.Where(id => id.HasValue)
.Select(id => id.Value));
}
else if (ext == ".xml")
{

View File

@ -29,7 +29,7 @@ namespace BizHawk.Client.EmuHawk
// To keep changes outside this file minimal, we'll simply check if any problematic condition arises and disable hardcore mode
// (with the exception of frame advance and rewind, which we can just suppress)
private static readonly Type[] HardcoreProhibitedTools = new[]
private static readonly Type[] HardcoreProhibitedTools =
{
typeof(LuaConsole), typeof(RamWatch), typeof(RamSearch),
typeof(GameShark), typeof(SNESGraphicsDebugger), typeof(PceBgViewer),
@ -93,11 +93,9 @@ namespace BizHawk.Client.EmuHawk
foreach (var t in HardcoreProhibitedTools)
{
if (_tools.IsLoaded(t))
{
HandleHardcoreModeDisable($"Using {t.Name} in hardcore mode is not allowed.");
return;
}
if (!_tools.IsLoaded(t)) continue;
HandleHardcoreModeDisable($"Using {t.Name} in hardcore mode is not allowed.");
return;
}
// can't know what external tools are doing, so just don't allow them here
@ -107,58 +105,53 @@ namespace BizHawk.Client.EmuHawk
return;
}
if (Emu is SubNESHawk or SubBsnesCore or SubGBHawk)
switch (Emu)
{
// this is mostly due to wonkiness with subframes which can be used as pseudo slowdown
HandleHardcoreModeDisable($"Using subframes in hardcore mode is not allowed.");
return;
}
else if (Emu is NymaCore nyma)
{
if (nyma.GetSettings().DisabledLayers.Any())
{
HandleHardcoreModeDisable($"Disabling {Emu.GetType().Name}'s graphics layers in hardcore mode is not allowed.");
return;
}
}
else if (Emu is GambatteLink gl)
{
foreach (var ss in gl.GetSyncSettings()._linkedSyncSettings)
{
if (!ss.DisplayBG || !ss.DisplayOBJ || !ss.DisplayWindow)
case SubNESHawk or SubBsnesCore or SubGBHawk:
// this is mostly due to wonkiness with subframes which can be used as pseudo slowdown
HandleHardcoreModeDisable($"Using subframes in hardcore mode is not allowed.");
break;
case NymaCore nyma:
if (nyma.GetSettings().DisabledLayers.Any())
{
HandleHardcoreModeDisable($"Disabling {Emu.GetType().Name}'s graphics layers in hardcore mode is not allowed.");
}
break;
case GambatteLink gl:
if (gl.GetSyncSettings()._linkedSyncSettings.Any(ss => !ss.DisplayBG || !ss.DisplayOBJ || !ss.DisplayWindow))
{
HandleHardcoreModeDisable($"Disabling GambatteLink's graphics layers in hardcore mode is not allowed.");
return;
}
}
}
else if (Emu is Gameboy gb)
{
var ss = gb.GetSyncSettings();
if (!ss.DisplayBG || !ss.DisplayOBJ || !ss.DisplayWindow)
break;
case Gameboy gb:
{
HandleHardcoreModeDisable($"Disabling Gambatte's graphics layers in hardcore mode is not allowed.");
return;
}
if (ss.FrameLength is Gameboy.GambatteSyncSettings.FrameLengthType.UserDefinedFrames)
{
HandleHardcoreModeDisable($"Using subframes in hardcore mode is not allowed.");
return;
}
}
else if (CoreGraphicsLayers.TryGetValue(Emu.GetType(), out var layers))
{
var s = _mainForm.GetSettingsAdapterForLoadedCoreUntyped().GetSettings();
var t = s.GetType();
foreach (var layer in layers)
{
// annoyingly NES has fields instead of properties for layers
if (!(bool)(t.GetProperty(layer)?.GetValue(s) ?? t.GetField(layer).GetValue(s)))
var ss = gb.GetSyncSettings();
if (!ss.DisplayBG || !ss.DisplayOBJ || !ss.DisplayWindow)
{
HandleHardcoreModeDisable($"Disabling {Emu.GetType().Name}'s {layer} in hardcore mode is not allowed.");
return;
HandleHardcoreModeDisable($"Disabling Gambatte's graphics layers in hardcore mode is not allowed.");
}
else if (ss.FrameLength is Gameboy.GambatteSyncSettings.FrameLengthType.UserDefinedFrames)
{
HandleHardcoreModeDisable($"Using subframes in hardcore mode is not allowed.");
}
break;
}
default:
if (CoreGraphicsLayers.TryGetValue(Emu.GetType(), out var layers))
{
var s = _mainForm.GetSettingsAdapterForLoadedCoreUntyped().GetSettings();
var t = s.GetType();
foreach (var layer in layers)
{
// annoyingly NES has fields instead of properties for layers
if ((bool)(t.GetProperty(layer)
?.GetValue(s) ?? t.GetField(layer)
.GetValue(s))) continue;
HandleHardcoreModeDisable($"Disabling {Emu.GetType().Name}'s {layer} in hardcore mode is not allowed.");
return;
}
}
break;
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
@ -10,45 +11,55 @@ namespace BizHawk.Client.EmuHawk
{
public abstract partial class RetroAchievements
{
public struct RAMemGuard : IMonitor, IDisposable
public class RAMemGuard : IMonitor, IDisposable
{
private readonly AutoResetEvent _start, _go, _end;
private readonly ThreadLocal<bool> _isMainThread;
private bool IsNotMainThread => !_isMainThread.Value;
public RAMemGuard(AutoResetEvent start, AutoResetEvent go, AutoResetEvent end)
{
_start = start;
_go = go;
_end = end;
_isMainThread = new() { Value = true };
}
public void Dispose()
{
_start.Dispose();
_go.Dispose();
_end.Dispose();
_isMainThread.Dispose();
}
private readonly ManualResetEventSlim MemLock = new(false);
private readonly SemaphoreSlim MemSema = new(1);
private readonly object MemSync = new();
public void Enter()
{
if (IsNotMainThread)
lock (MemSync)
{
_start.Set();
_go.WaitOne();
MemSema.Wait();
MemLock.Wait();
}
}
public void Exit()
{
if (IsNotMainThread)
MemSema.Release();
}
public void Dispose()
{
MemLock.Dispose();
MemSema.Dispose();
}
public readonly ref struct AccessWrapper
{
private readonly RAMemGuard _guard;
internal AccessWrapper(RAMemGuard guard)
{
_end.Set();
_guard = guard;
_guard.MemLock.Set();
}
public void Dispose()
{
lock (_guard.MemSync)
{
_guard.MemLock.Reset();
_guard.MemSema.Wait();
_guard.MemSema.Release();
}
}
}
public AccessWrapper GetAccess()
=> new(this);
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -60,7 +71,7 @@ namespace BizHawk.Client.EmuHawk
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int ReadMemoryBlockFunc(int address, IntPtr buffer, int bytes);
public class MemFunctions
protected class MemFunctions
{
protected readonly MemoryDomain _domain;
private readonly int _domainAddrStart; // addr of _domain where bank begins
@ -72,7 +83,7 @@ namespace BizHawk.Client.EmuHawk
public readonly int BankSize;
public RAMemGuard? MemGuard { get; set; }
public RAMemGuard MemGuard { get; set; }
protected virtual int FixAddr(int addr)
=> _domainAddrStart + addr;
@ -119,7 +130,7 @@ namespace BizHawk.Client.EmuHawk
{
for (var i = addr; i < end; i++)
{
((byte*)buffer)[i - addr] = _domain.PeekByte(i ^ _addressMangler);
((byte*)buffer)![i - addr] = _domain.PeekByte(i ^ _addressMangler);
}
}
}
@ -202,11 +213,11 @@ namespace BizHawk.Client.EmuHawk
{
if ((i & 2) != 0)
{
((byte*)buffer)[i - addr] = 0;
((byte*)buffer)![i - addr] = 0;
}
else
{
((byte*)buffer)[i - addr] = _domain.PeekByte(FixAddr(i));
((byte*)buffer)![i - addr] = _domain.PeekByte(FixAddr(i));
}
}
}
@ -279,7 +290,7 @@ namespace BizHawk.Client.EmuHawk
{
var regs = _debuggable.GetCpuFlagsAndRegisters();
var end = Math.Min(addr + bytes, BankSize);
for (int i = addr; i < end; i++)
for (var i = addr; i < end; i++)
{
byte val;
if (i < 0x40)
@ -293,7 +304,7 @@ namespace BizHawk.Client.EmuHawk
unsafe
{
((byte*)buffer)[i - addr] = val;
((byte*)buffer)![i - addr] = val;
}
}
@ -310,13 +321,13 @@ namespace BizHawk.Client.EmuHawk
}
// these consoles will use the entire system bus
private static readonly ConsoleID[] UseFullSysBus = new[]
private static readonly ConsoleID[] UseFullSysBus =
{
ConsoleID.NES, ConsoleID.C64, ConsoleID.AmstradCPC, ConsoleID.Atari7800,
};
// these consoles will use the entire main memory domain
private static readonly ConsoleID[] UseFullMainMem = new[]
private static readonly ConsoleID[] UseFullMainMem =
{
ConsoleID.PlayStation, ConsoleID.Lynx, ConsoleID.Lynx, ConsoleID.NeoGeoPocket,
ConsoleID.Jaguar, ConsoleID.JaguarCD, ConsoleID.DS, ConsoleID.AppleII,
@ -370,10 +381,7 @@ namespace BizHawk.Client.EmuHawk
}
else if (UsePartialSysBus.TryGetValue(consoleId, out var pairs))
{
foreach (var pair in pairs)
{
mfs.Add(new(domains.SystemBus, pair.Start, pair.Size));
}
mfs.AddRange(pairs.Select(pair => new MemFunctions(domains.SystemBus, pair.Start, pair.Size)));
}
else
{
@ -502,13 +510,8 @@ namespace BizHawk.Client.EmuHawk
mfs.Add(new(domains.MainMemory, 0, domains.MainMemory.Size, 3));
break;
case ConsoleID.Arcade:
foreach (var domain in domains)
{
if (domain.Name.Contains("ram"))
{
mfs.Add(new(domain, 0, domain.Size));
}
}
mfs.AddRange(domains.Where(domain => domain.Name.Contains("ram"))
.Select(domain => new MemFunctions(domain, 0, domain.Size)));
break;
case ConsoleID.UnknownConsoleID:
case ConsoleID.ZXSpectrum: // this doesn't actually have anything standardized, so...