Camhack support (#1725)

for the camhack to work we have to save a state, hack memory, advance twice to see the changes, then load the state to prevent desync. since we can omit the framebuffer in savestates, loading them can happen without updating the screen, so the hacked camera remains visible.

advancing 2 frames automatically is done like tastudio does it when it seeks to a frame, only from lua now.

and the most questionable part is "invisible emulation", which is how Gens calls this IIRC, when everything that can distract or slow us down is skipped: sound, video, tools updates.

new lua functions:
- client.invisibleemulation()
- client.seekframe()

* for a test, mGBA core uses fake video and audio buffers and renders to them when we want to "skip" rendering. proper setup would involve actually skipping rendering those inside the core.
* allow disabling video and audio updates for gpgx too (proper approach, no fake buffers involved)
* add the script for Sonic Advance
This commit is contained in:
feos 2019-11-23 12:27:14 +03:00 committed by GitHub
parent 825f10d52c
commit 5ca08b6c29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 111 additions and 10 deletions

View File

@ -0,0 +1,32 @@
-- feos, 2019
local offX, offY, camX, camY
local addr_offX = 0x5B96
local addr_offY = 0x5B98
local addr_camX = 0x59D0
local addr_camY = 0x59D2
while true do
client.invisibleemulation(true)
local memorystate = memorysavestate.savecorestate()
offX = mainmemory.read_u16_le(addr_offX)
offY = mainmemory.read_u16_le(addr_offY)
camX = mainmemory.read_u16_le(addr_camX)
camY = mainmemory.read_u16_le(addr_camY)
Xval = camX + offX - 128
Yval = camY + offY - 80
mainmemory.write_u16_le(addr_camX, Xval)
mainmemory.write_u16_le(addr_camY, Yval)
client.seekframe(emu.framecount()+1)
client.invisibleemulation(false)
client.seekframe(emu.framecount()+1)
client.invisibleemulation(true)
memorysavestate.loadcorestate(memorystate)
memorysavestate.removestate(memorystate)
-- client.invisibleemulation(false)
emu.frameadvance()
end

View File

@ -571,6 +571,28 @@ namespace BizHawk.Client.EmuHawk
public bool PressRewind { get; set; } // necessary for tastudio < button
public bool FastForward { get; set; }
/// <summary>
/// Disables updates for video/audio, and enters "turbo" mode.
/// Can be used to replicate Gens-rr's "latency compensation" that involves:
/// <list type="bullet">
/// <item><description>Saving a no-framebuffer state that is stored in RAM</description></item>
/// <item><description>Emulating forth for some frames with updates disabled</description></item>
/// <item><list type="bullet">
/// <item><description>Optionally hacking in-game memory
/// (like camera position, to show off-screen areas)</description></item>
/// </list></item>
/// <item><description>Updating the screen</description></item>
/// <item><description>Loading the no-framebuffer state from RAM</description></item>
/// </list>
/// The most common usecase is CamHack for Sonic games.
/// Accessing this from Lua allows to keep internal code hacks to minimum.
/// <list type="bullet">
/// <item><description><see cref="EmuHawkLuaLibrary.InvisibleEmulation(bool)"/></description></item>
/// <item><description><see cref="EmuHawkLuaLibrary.SeekFrame(int)"/></description></item>
/// </list>
/// </summary>
public bool InvisibleEmulation { get; set; }
// runloop won't exec lua
public bool SuppressLua { get; set; }
@ -2809,7 +2831,7 @@ namespace BizHawk.Client.EmuHawk
if (runFrame || force)
{
var isFastForwarding = Global.ClientControls["Fast Forward"] || IsTurboing;
var isFastForwarding = Global.ClientControls["Fast Forward"] || IsTurboing || InvisibleEmulation;
var isFastForwardingOrRewinding = isFastForwarding || isRewinding || _unthrottled;
if (isFastForwardingOrRewinding != _lastFastForwardingOrRewinding)
@ -2842,10 +2864,13 @@ namespace BizHawk.Client.EmuHawk
GlobalWin.Tools.UpdateToolsBefore();
}
CaptureRewind(isRewinding);
if (!InvisibleEmulation)
{
CaptureRewind(isRewinding);
}
// Set volume, if enabled
if (Global.Config.SoundEnabledNormal)
if (Global.Config.SoundEnabledNormal && !InvisibleEmulation)
{
atten = Global.Config.SoundVolume / 100.0f;
@ -2878,13 +2903,14 @@ namespace BizHawk.Client.EmuHawk
}
}
// why not skip audio if the user doesn't want sound
bool renderSound = (Global.Config.SoundEnabled && !IsTurboing) || (_currAviWriter?.UsesAudio ?? false);
bool renderSound = (Global.Config.SoundEnabled && !IsTurboing)
|| (_currAviWriter?.UsesAudio ?? false);
if (!renderSound)
{
atten = 0;
}
bool render = !_throttle.skipNextFrame || (_currAviWriter?.UsesVideo ?? false);
bool render = !InvisibleEmulation && (!_throttle.skipNextFrame || (_currAviWriter?.UsesVideo ?? false));
bool new_frame = Emulator.FrameAdvance(Global.ControllerOutput, render, renderSound);
Global.MovieSession.HandleMovieAfterFrameLoop();
@ -2924,7 +2950,7 @@ namespace BizHawk.Client.EmuHawk
UpdateToolsAfter(SuppressLua);
}
if (!PauseAvi && new_frame)
if (!PauseAvi && new_frame && !InvisibleEmulation)
{
AvFrameAdvance();
}

View File

@ -120,6 +120,42 @@ namespace BizHawk.Client.EmuHawk
}
}
/// <summary>
/// Use with <see cref="SeekFrame(int)"/> for CamHack.
/// Refer to <see cref="MainForm.InvisibleEmulation"/> for the workflow details.
/// </summary>
[LuaMethodExample("client.invisibleemulation( true );")]
[LuaMethod("invisibleemulation", "Disables and enables emulator updates")]
public void InvisibleEmulation(bool invisible)
{
GlobalWin.MainForm.InvisibleEmulation = invisible;
}
/// <summary>
/// Use with <see cref="InvisibleEmulation(bool)"/> for CamHack.
/// Refer to <see cref="MainForm.InvisibleEmulation"/> for the workflow details.
/// </summary>
[LuaMethodExample("client.seekframe( 100 );")]
[LuaMethod("seekframe", "Makes the emulator seek to the frame specified")]
public void SeekFrame(int frame)
{
bool wasPaused = GlobalWin.MainForm.EmulatorPaused;
// can't re-enter lua while doing this
GlobalWin.MainForm.SuppressLua = true;
while (Emulator.Frame != frame)
{
GlobalWin.MainForm.SeekFrameAdvance();
}
GlobalWin.MainForm.SuppressLua = false;
if (!wasPaused)
{
GlobalWin.MainForm.UnpauseEmulator();
}
}
[LuaMethodExample("local incliget = client.gettargetscanlineintensity( );")]
[LuaMethod("gettargetscanlineintensity", "Gets the current scanline intensity setting, used for the scanline display filter")]
public static int GetTargetScanlineIntensity()

View File

@ -6,6 +6,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
public partial class MGBAHawk : ISoundProvider
{
private readonly short[] _soundbuff = new short[2048];
private readonly short[] _dummysoundbuff = new short[2048];
private int _nsamp;
public bool CanProvideAsync => false;

View File

@ -23,5 +23,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
public int VsyncDenominator => 4389;
private readonly int[] _videobuff = new int[240 * 160];
private readonly int[] _dummyvideobuff = new int[240 * 160];
}
}

View File

@ -81,9 +81,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
IsLagFrame = LibmGBA.BizAdvance(
_core,
VBANext.GetButtons(controller),
_videobuff,
render ? _videobuff : _dummyvideobuff,
ref _nsamp,
_soundbuff,
rendersound ? _soundbuff : _dummysoundbuff,
RTCTime(),
(short)controller.GetFloat("Tilt X"),
(short)controller.GetFloat("Tilt Y"),

View File

@ -58,8 +58,12 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
_drivelight = false;
Core.gpgx_advance();
UpdateVideo();
update_audio();
if (render)
UpdateVideo();
if (rendersound)
update_audio();
if (IsLagFrame)
LagCount++;