Merge branch 'nes-emulator' of ssh://gitlab.axiodl.com:6431/AxioDL/urde into nes-emulator

This commit is contained in:
Jack Andersen 2018-02-02 17:13:24 -10:00
commit fa44ab57e8
11 changed files with 438 additions and 88 deletions

View File

@ -1,6 +1,6 @@
include_directories(${CMAKE_SOURCE_DIR}/DataSpec ${CMAKE_SOURCE_DIR}/Runtime ${CMAKE_CURRENT_SOURCE_DIR})
file(GLOB MAPPER_SRCS fixNES/mapper/*.c)
add_library(NESEmulator CNESEmulator.hpp CNESEmulator.cpp CNESShader.hpp CNESShader.cpp malloc.h
fixNES/apu.c fixNES/audio_fds.c fixNES/audio_mmc5.c fixNES/audio_vrc6.c fixNES/audio_vrc7.c
fixNES/audio_n163.c fixNES/audio_s5b.c fixNES/cpu.c fixNES/ppu.c fixNES/mem.c fixNES/input.c
apu.c fixNES/audio_fds.c fixNES/audio_mmc5.c fixNES/audio_vrc6.c fixNES/audio_vrc7.c
fixNES/audio_n163.c fixNES/audio_s5b.c fixNES/cpu.c ppu.c fixNES/mem.c fixNES/input.c
fixNES/mapper.c fixNES/mapperList.c fixNES/fm2play.c fixNES/vrc_irq.c ${MAPPER_SRCS})

View File

@ -31,7 +31,7 @@ extern "C"
#include "fixNES/mapper_h/nsf.h"
/*
* Copyright (C) 2017 FIX94
* Portions Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
@ -78,7 +78,7 @@ static uint32_t emuPrgRAMsize = 0;
uint32_t textureImage[0xF000];
bool nesPause = false;
bool ppuDebugPauseFrame = false;
bool doOverscan = true;
bool doOverscan = false;
bool nesPAL = false;
bool nesEmuNSFPlayback = false;
@ -135,6 +135,9 @@ static uint16_t vrc7Clock = 1;
extern bool fdsSwitch;
bool apuCycleURDE();
uint8_t* ppuGetVRAM();
int audioUpdate()
{
if (!EmulatorInst)
@ -250,6 +253,7 @@ void CNESEmulator::InitializeEmulator()
CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx)
{
// Nearest-neighbor FTW!
m_texture = ctx.newDynamicTexture(VISIBLE_DOTS, linesToDraw,
boo::TextureFormat::RGBA8,
boo::TextureClampMode::ClampToEdgeNearest);
@ -330,6 +334,8 @@ CNESEmulator::~CNESEmulator()
int CNESEmulator::audioUpdate()
{
int origProcBufs = m_procBufs;
uint8_t *data = apuGetBuf();
if(data != NULL && m_procBufs)
{
@ -341,13 +347,13 @@ int CNESEmulator::audioUpdate()
m_headBuf = 0;
}
//if (!m_procBufs)
//if (!origProcBufs)
//printf("OVERRUN\n");
return m_procBufs;
return origProcBufs;
}
static const size_t AudioFrameSz = 2 * sizeof(int16_t);
static constexpr size_t AudioFrameSz = 2 * sizeof(int16_t);
size_t CNESEmulator::supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data)
{
@ -380,18 +386,18 @@ size_t CNESEmulator::supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t
return frames;
}
#define CATCHUP_SKIP 0
#define CATCHUP_SKIP 1
#if CATCHUP_SKIP
static int catchupFrames = 0;
#endif
void CNESEmulator::NesEmuMainLoop()
void CNESEmulator::NesEmuMainLoop(bool forceDraw)
{
int start = GetTickCount();
int loopCount = 0;
do
{
if((!emuSkipVsync && emuRenderFrame) || nesPause)
if(emuRenderFrame || nesPause)
{
#if DEBUG_MAIN_CALLS
emuMainTimesSkipped++;
@ -409,26 +415,36 @@ void CNESEmulator::NesEmuMainLoop()
++loopCount;
if(mainClock == cpuCycleTimer)
{
//runs every 8th cpu clock
if(apuClock == 8)
//URDE uses this loop to pre-fill audio buffers,
//rather than executing multiple frame loops
bool breakout = false;
do
{
if(!apuCycle())
//runs every 8th cpu clock
if(apuClock == 8)
{
if(!apuCycleURDE() && !forceDraw)
{
#if DEBUG_MAIN_CALLS
emuMainTimesSkipped++;
emuMainTimesSkipped++;
#endif
#if CATCHUP_SKIP
catchupFrames = 0;
catchupFrames = 0;
#endif
//printf("LC SKIP\n");
break;
//printf("LC SKIP\n");
breakout = true;
break;
}
apuClock = 1;
}
apuClock = 1;
}
else
apuClock++;
//runs every cpu cycle
apuClockTimers();
else
apuClock++;
//runs every cpu cycle
apuClockTimers();
} while (emuSkipVsync);
if (breakout)
break;
//main CPU clock
if(!cpuCycle())
exit(EXIT_SUCCESS);
@ -490,7 +506,8 @@ void CNESEmulator::NesEmuMainLoop()
vrc7Clock++;
}
if ((loopCount % 5000) == 0 && GetTickCount() - start >= 14)
#if 1
if (!forceDraw && (loopCount % 10000) == 0 && GetTickCount() - start >= 14)
{
#if CATCHUP_SKIP
if (catchupFrames < 50)
@ -498,28 +515,29 @@ void CNESEmulator::NesEmuMainLoop()
#endif
break;
}
#endif
}
while(true);
#if 0
int end = GetTickCount();
printf("%dms\n", end - start);
printf("%dms %d %d\n", end - start, loopCount, m_procBufs);
#endif
#if DEBUG_MAIN_CALLS
emuMainTimesCalled++;
int end = GetTickCount();
int end = GetTickCount();
//printf("%dms\n", end - start);
emuMainTotalElapsed += end - emuMainFrameStart;
if(emuMainTotalElapsed >= 1000)
{
printf("\r%i calls, %i skips ", emuMainTimesCalled, emuMainTimesSkipped);
emuMainTotalElapsed += end - emuMainFrameStart;
if(emuMainTotalElapsed >= 1000)
{
printf("\r%i calls, %i skips ", emuMainTimesCalled, emuMainTimesSkipped);
fflush(stdout);
emuMainTimesCalled = 0;
emuMainTimesSkipped = 0;
emuMainTotalElapsed = 0;
}
emuMainFrameStart = end;
emuMainTimesCalled = 0;
emuMainTimesSkipped = 0;
emuMainTotalElapsed = 0;
}
emuMainFrameStart = end;
#endif
}
@ -745,16 +763,142 @@ void CNESEmulator::DecompressROM(u8* dataIn, u8* dataOut, u32 dataOutLen, u8 des
void CNESEmulator::ProcessUserInput(const CFinalInput& input, int)
{
if (input.ControllerIdx() != 0)
return;
if (GetPasswordEntryState() != EPasswordEntryState::NotPasswordScreen)
{
// Don't swap A/B
inValReads[BUTTON_A] = input.DA();
inValReads[BUTTON_B] = input.DB();
}
else
{
// Prime controls (B jumps, A shoots)
inValReads[BUTTON_B] = input.DA() | input.DY();
inValReads[BUTTON_A] = input.DB() | input.DX();
}
inValReads[BUTTON_UP] = input.DDPUp() | input.DLAUp();
inValReads[BUTTON_DOWN] = input.DDPDown() | input.DLADown();
inValReads[BUTTON_LEFT] = input.DDPLeft() | input.DLALeft();
inValReads[BUTTON_RIGHT] = input.DDPRight() | input.DLARight();
inValReads[BUTTON_A] = input.DA();
inValReads[BUTTON_B] = input.DB();
inValReads[BUTTON_SELECT] = input.DZ();
inValReads[BUTTON_START] = input.DStart();
if (input.PL())
x20_wantsQuit = true;
}
bool CNESEmulator::CheckForGameOver(const u8* vram, u8* passwordOut)
{
// "PASS WORD"
if (memcmp(vram + 0x14B, "\x19\xa\x1c\x1c\xff\x20\x18\x1b\xd", 9))
return false;
int chOff = 0;
int encOff = 0;
u8 pwOut[18];
for (int i=0 ; i<24 ; ++i)
{
u8 chName = vram[0x1A9 + chOff];
++chOff;
if (chOff == 0x6 || chOff == 0x46)
++chOff; // mid-line space
else if (chOff == 0xd)
chOff = 64; // 2nd line
if (chName > 0x3f)
return false;
switch (i & 0x3)
{
case 0:
pwOut[encOff] = chName;
break;
case 1:
pwOut[encOff] |= chName << 6;
++encOff;
pwOut[encOff] = chName >> 2;
break;
case 2:
pwOut[encOff] |= chName << 4;
++encOff;
pwOut[encOff] = chName >> 4;
break;
case 3:
pwOut[encOff] |= chName << 2;
++encOff;
break;
default:
break;
}
}
if (passwordOut)
memmove(passwordOut, pwOut, 18);
return true;
}
CNESEmulator::EPasswordEntryState CNESEmulator::CheckForPasswordEntryScreen(const u8* vram)
{
// "PASS WORD PLEASE"
if (memcmp(vram + 0x88, "\x19\xa\x1c\x1c\xff\x20\x18\x1b\xd\xff\x19\x15\xe\xa\x1c\xe", 16))
return EPasswordEntryState::NotPasswordScreen;
for (int i=0 ; i<13 ; ++i)
if (vram[0x109 + i] < 0x40 || vram[0x149 + i] < 0x40)
return EPasswordEntryState::Entered;
return EPasswordEntryState::NotEntered;
}
bool CNESEmulator::SetPasswordIntoEntryScreen(u8* vram, u8* wram, const u8* password)
{
if (CheckForPasswordEntryScreen(vram) != EPasswordEntryState::NotEntered)
return false;
int i;
for (i=0 ; i<18 ; ++i)
if (password[i])
break;
if (i == 18)
return false;
int encOff = 0;
int chOff = 0;
u32 lastWord = 0;
for (i=0 ; i<24 ; ++i)
{
switch (i & 0x3)
{
case 0:
lastWord = password[encOff];
++encOff;
break;
case 1:
lastWord = (lastWord >> 6) | (u32(password[encOff]) << 2);
++encOff;
break;
case 2:
lastWord = (lastWord >> 6) | (u32(password[encOff]) << 4);
++encOff;
break;
case 3:
lastWord = (lastWord >> 6);
break;
default:
break;
}
u8 chName = u8(lastWord & 0x3f);
wram[0x99a + i] = chName;
vram[0x109 + chOff] = chName;
++chOff;
if (chOff == 0x6 || chOff == 0x46)
++chOff; // mid-line space
else if (chOff == 0xd)
chOff = 64; // 2nd line
}
return true;
}
void CNESEmulator::Update()
@ -769,7 +913,19 @@ void CNESEmulator::Update()
}
else
{
NesEmuMainLoop();
bool gameOver = CheckForGameOver(ppuGetVRAM(), x21_passwordFromNES);
x34_passwordEntryState = CheckForPasswordEntryScreen(ppuGetVRAM());
if (x34_passwordEntryState == EPasswordEntryState::NotEntered && x38_passwordPending)
{
SetPasswordIntoEntryScreen(ppuGetVRAM(), emuPrgRAM, x39_passwordToNES);
x38_passwordPending = false;
}
if (gameOver && !x20_gameOver)
for (int i=0 ; i<3 ; ++i) // Three draw loops to ensure password display
NesEmuMainLoop(true);
else
NesEmuMainLoop();
x20_gameOver = gameOver;
}
}
@ -791,10 +947,10 @@ void CNESEmulator::Draw(const zeus::CColor& mulColor, bool filtering)
CGraphics::DrawArray(0, 4);
}
void CNESEmulator::LoadState(const u8* state)
void CNESEmulator::LoadPassword(const u8* state)
{
memmove(x39_loadState, state, 18);
x38_stateLoaded = true;
memmove(x39_passwordToNES, state, 18);
x38_passwordPending = true;
}
}

View File

@ -19,6 +19,14 @@ namespace MP1
class CNESEmulator : public boo::IAudioVoiceCallback
{
public:
enum class EPasswordEntryState
{
NotPasswordScreen,
NotEntered,
Entered
};
private:
static bool EmulatorConstructed;
std::unique_ptr<u8[]> m_nesEmuPBuf;
@ -49,26 +57,29 @@ class CNESEmulator : public boo::IAudioVoiceCallback
size_t m_posInBuf = 0;
boo::ObjToken<boo::IAudioVoice> m_booVoice;
bool x20_wantsQuit = false;
u8 x21_saveState[18];
bool x34_wantsLoad = false;
bool x38_stateLoaded = false;
u8 x39_loadState[18];
bool x20_gameOver = false;
u8 x21_passwordFromNES[18];
EPasswordEntryState x34_passwordEntryState = EPasswordEntryState::NotPasswordScreen;
bool x38_passwordPending = false;
u8 x39_passwordToNES[18];
static void DecompressROM(u8* dataIn, u8* dataOut, u32 dataOutLen = 0x20000, u8 descrambleSeed = 0xe9,
u32 checkDataLen = 0x1FFFC, u32 checksumMagic = 0xA663);
void InitializeEmulator();
void DeinitializeEmulator();
void NesEmuMainLoop();
void NesEmuMainLoop(bool forceDraw = false);
static bool CheckForGameOver(const u8* vram, u8* passwordOut = nullptr);
static EPasswordEntryState CheckForPasswordEntryScreen(const uint8_t* vram);
static bool SetPasswordIntoEntryScreen(u8* vram, u8* wram, const u8* password);
public:
CNESEmulator();
~CNESEmulator();
void ProcessUserInput(const CFinalInput& input, int);
void Update();
void Draw(const zeus::CColor& mulColor, bool filtering);
void LoadState(const u8* state);
const u8* GetSaveState() const { return x21_saveState; }
bool WantsQuit() const { return x20_wantsQuit; }
bool WantsLoad() const { return x34_wantsLoad; }
void LoadPassword(const u8* state);
const u8* GetPassword() const { return x21_passwordFromNES; }
bool IsGameOver() const { return x20_gameOver; }
EPasswordEntryState GetPasswordEntryState() const { return x34_passwordEntryState; }
int audioUpdate();
void preSupplyAudio(boo::IAudioVoice& voice, double dt) {}

172
NESEmulator/apu.c Normal file
View File

@ -0,0 +1,172 @@
#include "fixNES/apu.c"
/*
* Alternate apuCycle implementation to avoid processing multiple
* NES frames per URDE frame (costly and jarring to player).
*
* This implementation nominally fills 6/10 buffers, allowing
* emulation to "catch up" by having more buffer headroom available
* (and also reducing audio latency somewhat).
*
* URDE's NesEmuMainLoop uses emuSkipVsync as a signal to proceed
* with the emulation, allowing audio buffers to be pre-filled with
* generated tones independent of the emulated CPU. Granted, this
* compromises accuracy, but doesn't affect NEStroid's behavior and
* reduces audio discontinuities.
*/
bool apuCycleURDE()
{
if(curBufPos == apuBufSize)
{
int updateRes = audioUpdate();
if(updateRes == 0)
{
emuSkipFrame = false;
emuSkipVsync = false;
return false;
}
if(updateRes > 6)
{
emuSkipVsync = true;
emuSkipFrame = true;
}
else
{
emuSkipFrame = false;
if(updateRes > 4) // 6 buffers filled, stop here
emuSkipVsync = true;
else
emuSkipVsync = false;
}
curBufPos = 0;
}
uint8_t p1Out = lastP1Out, p2Out = lastP2Out,
triOut = lastTriOut, noiseOut = lastNoiseOut;
if(p1LengthCtr && (APU_IO_Reg[0x15] & P1_ENABLE))
{
if(p1seq[p1Cycle] && !p1Sweep.mute && freq1 >= 8 && freq1 < 0x7FF)
lastP1Out = p1Out = (p1Env.constant ? p1Env.vol : p1Env.decay);
else
p1Out = 0;
}
if(p2LengthCtr && (APU_IO_Reg[0x15] & P2_ENABLE))
{
if(p2seq[p2Cycle] && !p2Sweep.mute && freq2 >= 8 && freq2 < 0x7FF)
lastP2Out = p2Out = (p2Env.constant ? p2Env.vol : p2Env.decay);
else
p2Out = 0;
}
if(triLengthCtr && triCurLinearCtr && (APU_IO_Reg[0x15] & TRI_ENABLE))
{
if(triSeq[triCycle] && triFreq >= 2)
lastTriOut = triOut = triSeq[triCycle];
else
triOut = 0;
}
if(noiseLengthCtr && (APU_IO_Reg[0x15] & NOISE_ENABLE))
{
if((noiseShiftReg&1) == 0 && noiseFreq > 0)
lastNoiseOut = noiseOut = (noiseEnv.constant ? noiseEnv.vol : noiseEnv.decay);
else
noiseOut = 0;
}
#if AUDIO_FLOAT
float curIn = pulseLookupTbl[p1Out + p2Out] + tndLookupTbl[(3*triOut) + (2*noiseOut) + dmcVol];
//very rough still
if(vrc6enabled)
{
vrc6AudioCycle();
curIn += ((float)vrc6Out)*0.008f;
curIn *= 0.6667f;
}
if(fdsEnabled)
{
fdsAudioCycle();
curIn += ((float)fdsOut)*0.00617f;
curIn *= 0.75f;
}
if(mmc5enabled)
{
mmc5AudioCycle();
curIn += pulseLookupTbl[mmc5Out]+(mmc5pcm*0.002f);
curIn *= 0.75f;
}
if(vrc7enabled)
{
curIn += (((float)(vrc7Out>>7))/32768.f);
curIn *= 0.75f;
}
if(n163enabled)
{
curIn += ((float)n163Out)*0.0008f;
curIn *= 0.6667f;
}
if(s5Benabled)
{
s5BAudioCycle();
curIn += ((float)s5BOut)/32768.f;
curIn *= 0.6667f;
}
//amplify input
curIn *= 3.0f;
float curLPout = lastLPOut+(lpVal*(curIn-lastLPOut));
float curHPOut = hpVal*(lastHPOut+lastLPOut-curLPout);
//set output
apuOutBuf[curBufPos] = curHPOut;
lastLPOut = curLPout;
lastHPOut = curHPOut;
#else
int32_t curIn = pulseLookupTbl[p1Out + p2Out] + tndLookupTbl[(3*triOut) + (2*noiseOut) + dmcVol];
//very rough still
if(vrc6enabled)
{
vrc6AudioCycle();
curIn += ((int32_t)vrc6Out)*262;
curIn <<= 1; curIn /= 3;
}
if(fdsEnabled)
{
fdsAudioCycle();
curIn += ((int32_t)fdsOut)*202;
curIn *= 3; curIn >>= 2;
}
if(mmc5enabled)
{
mmc5AudioCycle();
curIn += pulseLookupTbl[mmc5Out]+(mmc5pcm<<6);
curIn *= 3; curIn >>= 2;
}
if(vrc7enabled)
{
curIn += vrc7Out>>7;
curIn *= 3; curIn >>= 2;
}
if(n163enabled)
{
curIn += n163Out*26;
curIn <<= 1; curIn /= 3;
}
if(s5Benabled)
{
s5BAudioCycle();
curIn += s5BOut;
curIn <<= 1; curIn /= 3;
}
//amplify input
curIn *= 3;
int32_t curOut;
//gen output
curOut = lastLPOut+((lpVal*(curIn-lastLPOut))>>15); //Set Lowpass Output
curIn = (lastHPOut+lastLPOut-curOut); //Set Highpass Input
curIn += (curIn>>31)&1; //Add Sign Bit for proper Downshift later
lastLPOut = curOut; //Save Lowpass Output
curOut = (hpVal*curIn)>>15; //Set Highpass Output
lastHPOut = curOut; //Save Highpass Output
//Save Clipped Highpass Output
apuOutBuf[curBufPos] = (curOut > 32767)?(32767):((curOut < -32768)?(-32768):curOut);
#endif
apuOutBuf[curBufPos+1] = apuOutBuf[curBufPos];
curBufPos+=2;
return true;
}

@ -1 +1 @@
Subproject commit 25527a513dc635fa87014970618fbfb7dfe3e5c6
Subproject commit 01b5bde49a730eb287b18def7ed5e5f510cda7b5

8
NESEmulator/ppu.c Normal file
View File

@ -0,0 +1,8 @@
#include "fixNES/ppu.c"
/* Non-invasive way to access VRAM buffer directly */
uint8_t* ppuGetVRAM()
{
return PPU_VRAM;
}

View File

@ -62,10 +62,10 @@ const std::pair<int, const SGameOption*> GameOptionsRegistry[] =
CPersistentOptions::CPersistentOptions(CBitStreamReader& stream)
{
for (int b=0 ; b<98 ; ++b)
x0_[b] = stream.ReadEncoded(1);
x0_[b] = stream.ReadEncoded(8);
for (int b=0 ; b<64 ; ++b)
x68_[b] = stream.ReadEncoded(1);
x68_[b] = stream.ReadEncoded(8);
xc0_frozenFpsCount = stream.ReadEncoded(2);
xc4_frozenBallCount = stream.ReadEncoded(2);
@ -75,7 +75,7 @@ CPersistentOptions::CPersistentOptions(CBitStreamReader& stream)
xd0_25_normalModeBeat = stream.ReadEncoded(1);
xd0_26_hardModeBeat = stream.ReadEncoded(1);
xd0_27_fusionBeat = stream.ReadEncoded(1);
xd0_28_fusionSuitActive = stream.ReadEncoded(1);
xd0_28_fusionSuitActive = false;
xd0_29_allItemsCollected = stream.ReadEncoded(1);
xbc_autoMapperKeyState = stream.ReadEncoded(2);
@ -108,10 +108,10 @@ CPersistentOptions::CPersistentOptions(CBitStreamReader& stream)
void CPersistentOptions::PutTo(CBitStreamWriter& w) const
{
for (int b=0 ; b<98 ; ++b)
w.WriteEncoded(x0_[b], 1);
w.WriteEncoded(x0_[b], 8);
for (int b=0 ; b<64 ; ++b)
w.WriteEncoded(x68_[b], 1);
w.WriteEncoded(x68_[b], 8);
w.WriteEncoded(xc0_frozenFpsCount, 2);
w.WriteEncoded(xc4_frozenBallCount, 2);
@ -121,7 +121,6 @@ void CPersistentOptions::PutTo(CBitStreamWriter& w) const
w.WriteEncoded(xd0_25_normalModeBeat, 1);
w.WriteEncoded(xd0_26_hardModeBeat, 1);
w.WriteEncoded(xd0_27_fusionBeat, 1);
w.WriteEncoded(xd0_28_fusionSuitActive, 1);
w.WriteEncoded(xd0_29_allItemsCollected, 1);
w.WriteEncoded(xbc_autoMapperKeyState, 2);

View File

@ -8,9 +8,8 @@
namespace urde
{
class CFinalInput
struct CFinalInput
{
friend class CStateManager;
float x0_dt;
u32 x4_controllerIdx;
float x8_anaLeftX;
@ -63,7 +62,6 @@ class CFinalInput
bool x2e_b30_PDPLeft:1;
bool x2e_b31_PStart:1;
public:
CFinalInput();
CFinalInput(int cIdx, float dt,
const boo::DolphinControllerState& data,

View File

@ -1036,8 +1036,8 @@ void CFrontEndUI::SFusionBonusFrame::Update(float dt, CSaveGameScreen* saveUI)
else if (x24_loadedFrame)
x24_loadedFrame->Update(dt);
bool showFusionSuit = g_GameState->SystemOptions().GetPlayerLinkedFusion() &&
g_GameState->SystemOptions().GetPlayerBeatNormalMode();
bool showFusionSuit = (g_GameState->SystemOptions().GetPlayerLinkedFusion() &&
g_GameState->SystemOptions().GetPlayerBeatNormalMode()) || m_gbaOverride;
bool showFusionSuitProceed = showFusionSuit && x28_tablegroup_options->GetUserSelection() == 1;
x2c_tablegroup_fusionsuit->SetIsActive(showFusionSuitProceed);
x2c_tablegroup_fusionsuit->SetIsVisible(showFusionSuitProceed);
@ -1060,11 +1060,6 @@ void CFrontEndUI::SFusionBonusFrame::Update(float dt, CSaveGameScreen* saveUI)
instructionStr = g_MainStringTable->GetString(79); // You have not completed fusion
else if (!g_GameState->SystemOptions().GetPlayerBeatFusion())
instructionStr = g_MainStringTable->GetString(77); // To play NES Metroid
else
{
instructionStr = u"NES Emulator currently unsupported";
x30_textpane_instructions.x0_panes[0]->TextSupport().SetFontColor(zeus::CColor::skYellow);
}
}
x30_textpane_instructions.SetPairText(instructionStr);
@ -1102,15 +1097,22 @@ CFrontEndUI::SFusionBonusFrame::ProcessUserInput(const CFinalInput& input, CSave
}
else if (x24_loadedFrame)
{
bool showFusionSuit = g_GameState->SystemOptions().GetPlayerLinkedFusion() &&
g_GameState->SystemOptions().GetPlayerBeatNormalMode();
CFinalInput useInput = input;
if (input.PZ())
{
useInput.x2d_b28_PA = true;
m_gbaOverride = true;
}
bool showFusionSuit = (g_GameState->SystemOptions().GetPlayerLinkedFusion() &&
g_GameState->SystemOptions().GetPlayerBeatNormalMode()) || m_gbaOverride;
if (m_touchBar.GetPhase() != CFrontEndUITouchBar::EPhase::FusionBonus)
{
m_touchBar.SetFusionBonusPhase(showFusionSuit &&
g_GameState->SystemOptions().GetPlayerFusionSuitActive());
}
x24_loadedFrame->ProcessUserInput(input);
x24_loadedFrame->ProcessUserInput(useInput);
switch (tbAction)
{
@ -1196,7 +1198,7 @@ void CFrontEndUI::SFusionBonusFrame::DoAdvance(CGuiTableGroup* caller)
{
case 1:
/* Fusion Suit */
if (x3a_mpNotComplete)
if (x3a_mpNotComplete || m_gbaOverride)
{
x3a_mpNotComplete = false;
PlayAdvanceSfx();
@ -1216,12 +1218,12 @@ void CFrontEndUI::SFusionBonusFrame::DoAdvance(CGuiTableGroup* caller)
break;
case 0:
/* NES Metroid */
if (x39_fusionNotComplete)
if (x39_fusionNotComplete && !m_gbaOverride)
{
x39_fusionNotComplete = false;
PlayAdvanceSfx();
}
else if (g_GameState->SystemOptions().GetPlayerBeatFusion())
else if (g_GameState->SystemOptions().GetPlayerBeatFusion() || m_gbaOverride)
{
//x8_action = EAction::None;
CSfxManager::SfxStart(1094, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId);
@ -1427,6 +1429,9 @@ CFrontEndUI::SNesEmulatorFrame::SNesEmulatorFrame()
xc_textSupport = std::make_unique<CGuiTextSupport>(deface->id, props, zeus::CColor::skWhite,
zeus::CColor::skBlack, zeus::CColor::skWhite,
0, 0, g_SimplePool, CGuiWidget::EGuiModelDrawFlags::Alpha);
xc_textSupport->SetText(g_MainStringTable->GetString(103));
xc_textSupport->AutoSetExtent();
xc_textSupport->ClearRenderBuffer();
}
void CFrontEndUI::SNesEmulatorFrame::SetMode(EMode mode)
@ -1498,9 +1503,9 @@ bool CFrontEndUI::SNesEmulatorFrame::Update(float dt, CSaveGameScreen* saveUi)
case EMode::Emulator:
{
x4_nesEmu->Update();
if (!x4_nesEmu->WantsQuit())
if (!x4_nesEmu->IsGameOver())
x14_emulationSuspended = false;
if (x4_nesEmu->WantsQuit() && !x14_emulationSuspended)
if (x4_nesEmu->IsGameOver() && !x14_emulationSuspended)
{
x14_emulationSuspended = true;
if (saveUi && !saveUi->IsSavingDisabled())
@ -1511,8 +1516,8 @@ bool CFrontEndUI::SNesEmulatorFrame::Update(float dt, CSaveGameScreen* saveUi)
SetMode(EMode::ContinuePlaying);
break;
}
if (x4_nesEmu->WantsLoad() && saveUi)
x4_nesEmu->LoadState(g_GameState->SystemOptions().GetNESState());
if (x4_nesEmu->GetPasswordEntryState() == CNESEmulator::EPasswordEntryState::NotEntered && saveUi)
x4_nesEmu->LoadPassword(g_GameState->SystemOptions().GetNESState());
break;
}
@ -1523,7 +1528,7 @@ bool CFrontEndUI::SNesEmulatorFrame::Update(float dt, CSaveGameScreen* saveUi)
EQuitAction action = x8_quitScreen->Update(dt);
if (action == EQuitAction::Yes)
{
memmove(g_GameState->SystemOptions().GetNESState(), x4_nesEmu->GetSaveState(), 18);
memmove(g_GameState->SystemOptions().GetNESState(), x4_nesEmu->GetPassword(), 18);
saveUi->SaveNESState();
SetMode(EMode::ContinuePlaying);
}
@ -1581,7 +1586,10 @@ void CFrontEndUI::SNesEmulatorFrame::Draw(CSaveGameScreen* saveUi) const
return;
if (xc_textSupport->GetIsTextSupportFinishedLoading())
{
CGraphics::SetModelMatrix(zeus::CTransform::Translate(-280.f, 0.f, -160.f));
float aspect = g_Viewport.x8_width / float(g_Viewport.xc_height) / 1.33f;
CGraphics::SetOrtho(-320.f * aspect, 320.f * aspect, 240.f, -240.f, -4096.f, 4096.f);
CGraphics::SetViewPointMatrix(zeus::CTransform::Identity());
CGraphics::SetModelMatrix(zeus::CTransform::Translate(-220.f, 0.f, -200.f));
xc_textSupport->Render();
}
}
@ -2533,7 +2541,7 @@ static const float AudioFadeTimeB[] =
CIOWin::EMessageReturn CFrontEndUI::Update(float dt, CArchitectureQueue& queue)
{
if (xdc_saveUI && (x50_curScreen >= EScreen::FileSelect || true))
if (xdc_saveUI && x50_curScreen >= EScreen::FileSelect)
{
switch (xdc_saveUI->Update(dt))
{
@ -2636,11 +2644,6 @@ CIOWin::EMessageReturn CFrontEndUI::Update(float dt, CArchitectureQueue& queue)
}
break;
}
else
{
xec_emuFrme = std::make_unique<SNesEmulatorFrame>();
xdc_saveUI->ResetCardDriver();
}
if (xd2_deferSlideShow)
{

View File

@ -249,6 +249,8 @@ public:
CFrontEndUITouchBar& m_touchBar;
bool m_gbaOverride = false;
SFusionBonusFrame(CFrontEndUITouchBar& touchBar);
void FinishedLoading();
bool PumpLoad();

View File

@ -36,6 +36,7 @@ void CQuitGameScreen::SetColors()
void CQuitGameScreen::FinishedLoading()
{
x10_loadedFrame = x4_frame.GetObj();
x10_loadedFrame->SetMaxAspect(1.33f);
x14_tablegroup_quitgame = static_cast<CGuiTableGroup*>(
x10_loadedFrame->FindWidget("tablegroup_quitgame"));