metaforce/NESEmulator/CNESEmulator.cpp

750 lines
19 KiB
C++
Raw Normal View History

2018-02-01 23:19:34 +00:00
#include "CNESEmulator.hpp"
#include "CNESShader.hpp"
#include "CGameState.hpp"
#include "Input/CFinalInput.hpp"
#include "logvisor/logvisor.hpp"
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cstdbool>
#include <cstring>
#include "malloc.h"
#include <cinttypes>
#include <cctype>
#include <ctime>
#include <cmath>
static urde::MP1::CNESEmulator* EmulatorInst = nullptr;
2018-12-08 05:30:43 +00:00
extern "C" {
2018-02-01 23:19:34 +00:00
#include "fixNES/mapper.h"
#include "fixNES/cpu.h"
#include "fixNES/ppu.h"
#include "fixNES/mem.h"
#include "fixNES/input.h"
#include "fixNES/fm2play.h"
#include "fixNES/apu.h"
#include "fixNES/audio_fds.h"
#include "fixNES/audio_vrc7.h"
#include "fixNES/mapper_h/nsf.h"
/*
2018-02-03 00:35:19 +00:00
* Portions Copyright (C) 2017 FIX94
2018-02-01 23:19:34 +00:00
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#define DEBUG_HZ 0
#define DEBUG_MAIN_CALLS 0
#define DEBUG_KEY 0
#define DEBUG_LOAD_INFO 1
2018-02-03 03:08:10 +00:00
#ifndef _WIN32
2018-02-01 23:19:34 +00:00
std::chrono::steady_clock::time_point s_tp = std::chrono::steady_clock::now();
2018-12-08 05:30:43 +00:00
static std::chrono::milliseconds::rep GetTickCount() {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - s_tp).count();
2018-02-01 23:19:34 +00:00
}
2018-02-03 03:08:10 +00:00
#endif
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
static const char* VERSION_STRING = "fixNES Alpha v1.0.5";
2018-02-01 23:19:34 +00:00
static char window_title[256];
static char window_title_pause[256];
enum {
2018-12-08 05:30:43 +00:00
FTYPE_UNK = 0,
FTYPE_NES,
FTYPE_NSF,
FTYPE_FDS,
FTYPE_QD,
2018-02-01 23:19:34 +00:00
#if ZIPSUPPORT
2018-12-08 05:30:43 +00:00
FTYPE_ZIP,
2018-02-01 23:19:34 +00:00
#endif
};
2018-12-08 05:30:43 +00:00
// static void nesEmuFdsSetup(uint8_t *src, uint8_t *dst);
2018-02-01 23:19:34 +00:00
static int emuFileType = FTYPE_UNK;
static char emuFileName[1024];
2018-12-08 05:30:43 +00:00
static uint8_t* emuNesROM = NULL;
2018-02-01 23:19:34 +00:00
static uint32_t emuNesROMsize = 0;
static char emuSaveName[1024];
2018-12-08 05:30:43 +00:00
static uint8_t* emuPrgRAM = NULL;
2018-02-01 23:19:34 +00:00
static uint32_t emuPrgRAMsize = 0;
2018-12-08 05:30:43 +00:00
// used externally
2018-02-01 23:19:34 +00:00
uint32_t textureImage[0xF000];
bool nesPause = false;
bool ppuDebugPauseFrame = false;
2018-02-03 00:35:19 +00:00
bool doOverscan = false;
2018-02-01 23:19:34 +00:00
bool nesPAL = false;
bool nesEmuNSFPlayback = false;
2018-12-08 05:30:43 +00:00
// static bool inPause = false;
// static bool inOverscanToggle = false;
// static bool inResize = false;
// static bool inDiskSwitch = false;
// static bool inReset = false;
2018-02-01 23:19:34 +00:00
#if DEBUG_HZ
static int emuFrameStart = 0;
static int emuTimesCalled = 0;
static int emuTotalElapsed = 0;
#endif
#if DEBUG_MAIN_CALLS
static int emuMainFrameStart = 0;
static int emuMainTimesCalled = 0;
static int emuMainTimesSkipped = 0;
static int emuMainTotalElapsed = 0;
#endif
#define DOTS 341
#define VISIBLE_DOTS 256
#define VISIBLE_LINES 240
static uint32_t linesToDraw = VISIBLE_LINES;
2018-12-08 05:30:43 +00:00
static const uint32_t visibleImg = VISIBLE_DOTS * VISIBLE_LINES * 4;
// static uint8_t scaleFactor = 2;
2018-02-01 23:19:34 +00:00
static bool emuSaveEnabled = false;
static bool emuFdsHasSideB = false;
2018-06-16 20:09:12 +00:00
2018-12-08 05:30:43 +00:00
// static uint16_t ppuCycleTimer;
2018-06-16 20:09:12 +00:00
uint32_t cpuCycleTimer;
uint32_t vrc7CycleTimer;
2018-12-08 05:30:43 +00:00
// from input.c
2018-02-01 23:19:34 +00:00
extern uint8_t inValReads[8];
2018-12-08 05:30:43 +00:00
// from mapper.c
2018-02-01 23:19:34 +00:00
extern bool mapperUse78A;
2018-12-08 05:30:43 +00:00
// from m32.c
2018-02-01 23:19:34 +00:00
extern bool m32_singlescreen;
static volatile bool emuRenderFrame = false;
2018-06-16 20:09:12 +00:00
extern uint8_t audioExpansion;
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
// used externally
2018-02-01 23:19:34 +00:00
bool emuSkipVsync = false;
bool emuSkipFrame = false;
2018-12-08 05:30:43 +00:00
// static uint32_t mCycles = 0;
2018-02-01 23:19:34 +00:00
extern bool fdsSwitch;
2018-06-18 20:12:40 +00:00
uint32_t apuGetMaxBufSize();
2018-06-18 21:38:51 +00:00
void apuResetPos();
2018-02-03 00:35:19 +00:00
uint8_t* ppuGetVRAM();
2018-12-08 05:30:43 +00:00
int audioUpdate() {
if (!EmulatorInst)
return 0;
return EmulatorInst->audioUpdate();
2018-02-01 23:19:34 +00:00
}
}
2018-12-08 05:30:43 +00:00
namespace urde::MP1 {
2018-02-01 23:19:34 +00:00
bool CNESEmulator::EmulatorConstructed = false;
static logvisor::Module Log("CNESEmulator");
#define NESEMUP_ROM_OFFSET 0xa3f8
#define METROID_PAL false
#define METROID_MAPPER 1
#define METROID_SAVE_ENABLED false
#define METROID_TRAINER false
#define METROID_PRG_SIZE (8 * 0x4000)
#define METROID_CHR_SIZE (0 * 0x2000)
#define METROID_PRG_RAM_SIZE 0x2000
2018-12-08 05:30:43 +00:00
CNESEmulator::CNESEmulator() {
if (EmulatorConstructed)
Log.report(logvisor::Fatal, "Attempted constructing more than 1 CNESEmulator");
EmulatorConstructed = true;
CDvdFile NESEmuFile("NESemuP.rel");
if (NESEmuFile) {
m_nesEmuPBuf.reset(new u8[0x20000]);
m_dvdReq = NESEmuFile.AsyncSeekRead(m_nesEmuPBuf.get(), 0x20000, ESeekOrigin::Begin, NESEMUP_ROM_OFFSET);
} else {
Log.report(logvisor::Fatal, "Unable to open NESemuP.rel");
}
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
void CNESEmulator::InitializeEmulator() {
nesPause = false;
ppuDebugPauseFrame = false;
puts(VERSION_STRING);
strcpy(window_title, VERSION_STRING);
memset(textureImage, 0, visibleImg);
emuFileType = FTYPE_UNK;
memset(emuFileName, 0, 1024);
memset(emuSaveName, 0, 1024);
nesPAL = METROID_PAL;
uint8_t mapper = METROID_MAPPER;
emuSaveEnabled = METROID_SAVE_ENABLED;
bool trainer = METROID_TRAINER;
uint32_t prgROMsize = METROID_PRG_SIZE;
uint32_t chrROMsize = METROID_CHR_SIZE;
emuPrgRAMsize = METROID_PRG_RAM_SIZE;
emuPrgRAM = (uint8_t*)malloc(emuPrgRAMsize);
uint8_t* prgROM = emuNesROM;
if (trainer) {
memcpy(emuPrgRAM + 0x1000, prgROM, 0x200);
prgROM += 512;
}
uint8_t* chrROM = NULL;
if (chrROMsize) {
chrROM = emuNesROM + prgROMsize;
if (trainer)
chrROM += 512;
}
apuInitBufs();
cpuInit();
ppuInit();
memInit();
apuInit();
inputInit();
ppuSetNameTblVertical();
2018-02-01 23:19:34 +00:00
#if DEBUG_LOAD_INFO
2018-12-08 05:30:43 +00:00
printf("Used Mapper: %i\n", mapper);
printf("PRG: 0x%x bytes PRG RAM: 0x%x bytes CHR: 0x%x bytes\n", prgROMsize, emuPrgRAMsize, chrROMsize);
2018-02-01 23:19:34 +00:00
#endif
2018-12-08 05:30:43 +00:00
if (!mapperInit(mapper, prgROM, prgROMsize, emuPrgRAM, emuPrgRAMsize, chrROM, chrROMsize)) {
printf("Mapper init failed!\n");
return;
}
2018-02-01 23:19:34 +00:00
#if DEBUG_LOAD_INFO
2018-12-08 05:30:43 +00:00
printf("Trainer: %i Saving: %i VRAM Mode: Vertical\n", trainer, emuSaveEnabled);
2018-02-01 23:19:34 +00:00
#endif
2018-12-08 05:30:43 +00:00
sprintf(window_title, "%s NES - %s\n", nesPAL ? "PAL" : "NTSC", VERSION_STRING);
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
sprintf(window_title_pause, "%s (Pause)", window_title);
sprintf(window_title_pause, "%s (Pause)", window_title);
#if DEBUG_HZ
emuFrameStart = GetTickCount();
#endif
#if DEBUG_MAIN_CALLS
emuMainFrameStart = GetTickCount();
#endif
cpuCycleTimer = nesPAL ? 16 : 12;
vrc7CycleTimer = 432 / cpuCycleTimer;
// do one scanline per idle loop
// ppuCycleTimer = nesPAL ? 5 : 4;
// mainLoopRuns = nesPAL ? DOTS*ppuCycleTimer : DOTS*ppuCycleTimer;
// mainLoopPos = mainLoopRuns;
CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) {
// Nearest-neighbor FTW!
m_texture = ctx.newDynamicTexture(VISIBLE_DOTS, linesToDraw, boo::TextureFormat::RGBA8,
boo::TextureClampMode::ClampToEdgeNearest);
Vert verts[4] = {
{{-1.f, -1.f, 0.f}, {0.f, 1.f}},
{{-1.f, 1.f, 0.f}, {0.f, 0.f}},
{{1.f, -1.f, 0.f}, {1.f, 1.f}},
{{1.f, 1.f, 0.f}, {1.f, 0.f}},
};
m_vbo = ctx.newStaticBuffer(boo::BufferUse::Vertex, verts, sizeof(Vert), 4);
m_uniBuf = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(Uniform), 1);
m_shadBind = CNESShader::BuildShaderDataBinding(ctx, m_vbo, m_uniBuf, m_texture);
return true;
} BooTrace);
// double useFreq = 223740;
double useFreq = apuGetFrequency();
m_booVoice = CAudioSys::GetVoiceEngine()->allocateNewStereoVoice(useFreq, this);
m_booVoice->start();
uint32_t apuBufSz = apuGetMaxBufSize();
m_audioBufBlock.reset(new u8[apuBufSz * NUM_AUDIO_BUFFERS]);
memset(m_audioBufBlock.get(), 0, apuBufSz * NUM_AUDIO_BUFFERS);
for (int i = 0; i < NUM_AUDIO_BUFFERS; ++i)
m_audioBufs[i] = m_audioBufBlock.get() + apuBufSz * i;
EmulatorInst = this;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
void CNESEmulator::DeinitializeEmulator() {
// printf("\n");
emuRenderFrame = false;
m_booVoice->stop();
m_booVoice.reset();
apuDeinitBufs();
if (emuNesROM != NULL) {
if (!nesEmuNSFPlayback && (audioExpansion & EXP_FDS)) {
FILE* save = fopen(emuSaveName, "wb");
if (save) {
if (emuFdsHasSideB)
fwrite(emuNesROM, 1, 0x20000, save);
else
fwrite(emuNesROM, 1, 0x10000, save);
fclose(save);
}
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
}
if (emuPrgRAM != NULL) {
if (emuSaveEnabled) {
FILE* save = fopen(emuSaveName, "wb");
if (save) {
fwrite(emuPrgRAM, 1, emuPrgRAMsize, save);
fclose(save);
}
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
free(emuPrgRAM);
}
emuPrgRAM = NULL;
// printf("Bye!\n");
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
EmulatorInst = nullptr;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
CNESEmulator::~CNESEmulator() {
if (m_dvdReq)
m_dvdReq->PostCancelRequest();
if (EmulatorInst)
DeinitializeEmulator();
if (emuNesROM) {
free(emuNesROM);
emuNesROM = nullptr;
}
EmulatorConstructed = false;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
int CNESEmulator::audioUpdate() {
int origProcBufs = m_procBufs;
2018-02-02 07:12:41 +00:00
2018-12-08 05:30:43 +00:00
uint8_t* data = apuGetBuf();
if (data != NULL && m_procBufs) {
uint32_t apuBufSz = apuGetMaxBufSize();
uint32_t remBytes = apuGetBufSize();
while (remBytes != 0) {
size_t thisBytes = std::min(remBytes, apuBufSz - m_posInHeadBuf);
memmove(m_audioBufs[m_headBuf] + m_posInHeadBuf, data, thisBytes);
data += thisBytes;
m_posInHeadBuf += thisBytes;
if (m_posInHeadBuf == apuBufSz) {
m_posInHeadBuf = 0;
--m_procBufs;
++m_headBuf;
if (m_headBuf == NUM_AUDIO_BUFFERS)
m_headBuf = 0;
// printf("PUSH\n");
}
remBytes -= thisBytes;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
}
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
// if (!origProcBufs)
// printf("OVERRUN\n");
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
return origProcBufs;
2018-02-01 23:19:34 +00:00
}
2018-06-18 21:38:51 +00:00
static constexpr uint32_t AudioFrameSz = 2 * sizeof(int16_t);
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
size_t CNESEmulator::supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data) {
uint32_t remFrames = uint32_t(frames);
while (remFrames) {
if (m_posInTailBuf == apuGetMaxBufSize()) {
++m_tailBuf;
if (m_tailBuf == NUM_AUDIO_BUFFERS)
m_tailBuf = 0;
m_posInTailBuf = 0;
++m_procBufs;
// printf("POP\n");
}
if (m_procBufs == NUM_AUDIO_BUFFERS) {
memset(data, 0, remFrames * AudioFrameSz);
// printf("UNDERRUN\n");
return frames;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
size_t copySz = std::min(apuGetMaxBufSize() - m_posInTailBuf, remFrames * AudioFrameSz);
memmove(data, m_audioBufs[m_tailBuf] + m_posInTailBuf, copySz);
data += copySz / sizeof(int16_t);
m_posInTailBuf += copySz;
remFrames -= copySz / AudioFrameSz;
}
return frames;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
void CNESEmulator::NesEmuMainLoop(bool forceDraw) {
// int start = GetTickCount();
int loopCount = 0;
do {
if (emuRenderFrame || nesPause) {
2018-02-03 03:08:10 +00:00
#if DEBUG_MAIN_CALLS
2018-12-08 05:30:43 +00:00
emuMainTimesSkipped++;
2018-02-01 23:19:34 +00:00
#endif
2018-12-08 05:30:43 +00:00
// printf("LC RENDER: %d\n", loopCount);
m_texture->load(textureImage, visibleImg);
emuRenderFrame = false;
break;
}
++loopCount;
// main CPU clock
if (!cpuCycle())
exit(EXIT_SUCCESS);
// run graphics
ppuCycle();
// run audio
apuCycle();
// mapper related irqs
mapperCycle();
// mCycles++;
if (ppuDrawDone()) {
// printf("%i\n",mCycles);
// mCycles = 0;
2018-06-16 20:09:12 +00:00
#ifndef __LIBRETRO__
2018-12-08 05:30:43 +00:00
emuRenderFrame = true;
2018-06-16 20:09:12 +00:00
#if 0
if(fm2playRunning())
fm2playUpdate();
2018-02-01 23:19:34 +00:00
#endif
2018-06-18 21:38:51 +00:00
#if DEBUG_HZ
2018-12-08 05:30:43 +00:00
emuTimesCalled++;
auto end = GetTickCount();
emuTotalElapsed += end - emuFrameStart;
if (emuTotalElapsed >= 1000) {
printf("\r%iHz ", emuTimesCalled);
emuTimesCalled = 0;
emuTotalElapsed = 0;
}
emuFrameStart = end;
2018-06-16 20:09:12 +00:00
#endif
2018-12-08 05:30:43 +00:00
// update audio before drawing
if (!apuUpdate()) {
apuResetPos();
break;
}
// glutPostRedisplay();
2018-06-16 20:09:12 +00:00
#if 0
if(ppuDebugPauseFrame)
2018-02-01 23:19:34 +00:00
{
2018-06-16 20:09:12 +00:00
ppuDebugPauseFrame = false;
nesPause = true;
2018-02-01 23:19:34 +00:00
}
2018-06-16 20:09:12 +00:00
#endif
#endif
2018-12-08 05:30:43 +00:00
if (nesEmuNSFPlayback)
nsfVsync();
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
// keep processing frames if audio buffers are underrunning
if (emuSkipFrame)
emuRenderFrame = false;
2018-06-18 21:38:51 +00:00
2018-12-08 05:30:43 +00:00
continue;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
} while (true);
2018-02-01 23:19:34 +00:00
2018-02-03 03:08:10 +00:00
#if 0
2018-02-01 23:19:34 +00:00
int end = GetTickCount();
2018-02-02 07:12:41 +00:00
printf("%dms %d %d\n", end - start, loopCount, m_procBufs);
2018-02-03 03:08:10 +00:00
#endif
2018-02-01 23:19:34 +00:00
2018-02-03 03:08:10 +00:00
#if DEBUG_MAIN_CALLS
2018-12-08 05:30:43 +00:00
emuMainTimesCalled++;
int end = GetTickCount();
// printf("%dms\n", end - start);
emuMainTotalElapsed += end - emuMainFrameStart;
if (emuMainTotalElapsed >= 1000) {
printf("\r%i calls, %i skips ", emuMainTimesCalled, emuMainTimesSkipped);
fflush(stdout);
emuMainTimesCalled = 0;
emuMainTimesSkipped = 0;
emuMainTotalElapsed = 0;
}
emuMainFrameStart = end;
2018-02-01 23:19:34 +00:00
#endif
}
#if 0
2018-02-01 23:19:34 +00:00
static void nesEmuFdsSetup(uint8_t *src, uint8_t *dst)
{
memcpy(dst, src, 0x38);
memcpy(dst+0x3A, src+0x38, 2);
uint16_t cDiskPos = 0x3E;
uint16_t cROMPos = 0x3A;
do
{
if(src[cROMPos] != 0x03)
break;
memcpy(dst+cDiskPos, src+cROMPos, 0x10);
uint16_t copySize = (*(uint16_t*)(src+cROMPos+0xD))+1;
cDiskPos+=0x12;
cROMPos+=0x10;
memcpy(dst+cDiskPos, src+cROMPos, copySize);
cDiskPos+=copySize+2;
cROMPos+=copySize;
} while(cROMPos < 0xFFDC && cDiskPos < 0xFFFF);
printf("%04x -> %04x\n", cROMPos, cDiskPos);
}
#endif
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
struct BitstreamState {
u8* rPos;
int position = 0;
int tmpBuf = 0;
int decBit = 0;
BitstreamState(u8* pos) : rPos(pos) {}
void resetDecBit() { decBit = 0; }
void runDecBit() {
if (position == 0) {
position = 8;
tmpBuf = *rPos++;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
decBit <<= 1;
if (tmpBuf & 0x80)
decBit |= 1;
tmpBuf <<= 1;
position--;
}
};
2018-02-01 23:19:34 +00:00
// Based on https://gist.github.com/FIX94/7593640c5cee6c37e3b23e7fcf8fe5b7
2018-12-08 05:30:43 +00:00
void CNESEmulator::DecryptMetroid(u8* dataIn, u8* dataOut, u32 decLen, u8 decByte, u32 xorLen, u32 xorVal) {
int i, j;
// simple add obfuscation
for (i = 0; i < 0x100; i++) {
dataIn[i] += decByte;
decByte = dataIn[i];
}
// flip the first 0x100 bytes around
for (i = 0; i < 128; ++i)
std::swap(dataIn[255 - i], dataIn[i]);
// set up buffer pointers
BitstreamState bState(dataIn + 0x100);
// unscramble buffer
for (i = 0; i < decLen; i++) {
bState.resetDecBit();
bState.runDecBit();
if (bState.decBit) {
bState.resetDecBit();
for (j = 0; j < 8; j++)
bState.runDecBit();
dataOut[i] = dataIn[bState.decBit + 0x49];
} else {
bState.resetDecBit();
bState.runDecBit();
if (bState.decBit) {
bState.resetDecBit();
for (j = 0; j < 6; j++)
bState.runDecBit();
dataOut[i] = dataIn[bState.decBit + 9];
} else {
bState.resetDecBit();
bState.runDecBit();
2018-12-08 05:30:43 +00:00
if (bState.decBit) {
bState.resetDecBit();
for (j = 0; j < 3; j++)
bState.runDecBit();
2018-12-08 05:30:43 +00:00
dataOut[i] = dataIn[bState.decBit + 1];
} else
dataOut[i] = dataIn[bState.decBit];
}
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
}
// do checksum fixups
unsigned int xorTmpVal = 0;
for (i = 0; i < xorLen; i++) {
xorTmpVal ^= dataOut[i];
for (j = 0; j < 8; j++) {
if (xorTmpVal & 1) {
xorTmpVal >>= 1;
xorTmpVal ^= xorVal;
} else
xorTmpVal >>= 1;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
}
// write in calculated checksum
dataOut[xorLen - 1] = u8((xorTmpVal >> 8) & 0xFF);
dataOut[xorLen - 2] = u8(xorTmpVal & 0xFF);
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
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_SELECT] = input.DZ();
inValReads[BUTTON_START] = input.DStart();
2018-02-03 00:35:19 +00:00
}
2018-12-08 05:30:43 +00:00
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;
2018-02-03 00:35:19 +00:00
}
2018-12-08 05:30:43 +00:00
}
2018-02-03 00:35:19 +00:00
2018-12-08 05:30:43 +00:00
if (passwordOut)
memmove(passwordOut, pwOut, 18);
return true;
2018-02-03 00:35:19 +00:00
}
2018-12-08 05:30:43 +00:00
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;
2018-02-03 00:35:19 +00:00
2018-12-08 05:30:43 +00:00
for (int i = 0; i < 13; ++i)
if (vram[0x109 + i] < 0x40 || vram[0x149 + i] < 0x40)
return EPasswordEntryState::Entered;
2018-02-03 00:35:19 +00:00
2018-12-08 05:30:43 +00:00
return EPasswordEntryState::NotEntered;
2018-02-03 00:35:19 +00:00
}
2018-12-08 05:30:43 +00:00
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;
2018-02-03 00:35:19 +00:00
}
2018-12-08 05:30:43 +00:00
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;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
void CNESEmulator::Update() {
if (!EmulatorInst) {
if (m_dvdReq && m_dvdReq->IsComplete()) {
m_dvdReq.reset();
emuNesROMsize = 0x20000;
emuNesROM = (uint8_t*)malloc(emuNesROMsize);
DecryptMetroid(m_nesEmuPBuf.get(), emuNesROM);
m_nesEmuPBuf.reset();
InitializeEmulator();
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
} else {
if (nesPause) {
DeinitializeEmulator();
InitializeEmulator();
return;
}
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;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
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;
}
2018-02-01 23:19:34 +00:00
}
static const float NESAspect = VISIBLE_DOTS / float(VISIBLE_LINES);
2018-12-08 05:30:43 +00:00
void CNESEmulator::Draw(const zeus::CColor& mulColor, bool filtering) {
if (!EmulatorInst)
return;
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
float aspect = g_Viewport.x8_width / float(g_Viewport.xc_height);
float widthFac = NESAspect / aspect;
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
Uniform uniform = {zeus::CMatrix4f{}, mulColor};
uniform.m_matrix[0][0] = widthFac;
m_uniBuf->load(&uniform, sizeof(Uniform));
2018-02-01 23:19:34 +00:00
2018-12-08 05:30:43 +00:00
CGraphics::SetShaderDataBinding(m_shadBind);
CGraphics::DrawArray(0, 4);
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
void CNESEmulator::LoadPassword(const u8* state) {
memmove(x39_passwordToNES, state, 18);
x38_passwordPending = true;
2018-02-01 23:19:34 +00:00
}
2018-12-08 05:30:43 +00:00
} // namespace urde::MP1