Creating emulator interface class to mix both versions. This will be useful for verification purposes. Also fixed mapper070 for quicknes

This commit is contained in:
Sergio Martin 2024-01-15 20:56:58 +01:00
parent ad9858a564
commit a254482102
133 changed files with 434 additions and 256 deletions

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "extern/QuickNES_Core"]
path = extern/QuickNES_Core
path = source/quickNES/QuickNES_Core
url = https://github.com/libretro/QuickNES_Core.git

9
extern/hqn/hqn.h vendored
View File

@ -1,7 +1,7 @@
#ifndef __HQN_H__
#define __HQN_H__
#include <core/Nes_Emu.h>
#include <Nes_Emu.h>
#include <cstdint>
#include <stdio.h>
@ -46,6 +46,8 @@ public:
HQNState();
~HQNState();
void setEmulatorPointer(void* const emuPtr) { m_emu = (Nes_Emu*)emuPtr; }
/*
The joypad data for the two joypads available to an NES.
This is directly available because I'm lazy.
@ -148,7 +150,7 @@ void printUsage(const char *filename);
} // end namespace hqn
// Copied from bizinterface.cpp in BizHawk/quicknes
inline void saveBlit(const Nes_Emu *e, int32_t *dest, const int32_t *colors, int cropleft, int croptop, int cropright, int cropbottom)
inline void saveBlit(const void *ePtr, int32_t *dest, const int32_t *colors, int cropleft, int croptop, int cropright, int cropbottom)
{
// what is the point of the 256 color bitmap and the dynamic color allocation to it?
// why not just render directly to a 512 color bitmap with static palette positions?
@ -172,10 +174,11 @@ inline void saveBlit(const Nes_Emu *e, int32_t *dest, const int32_t *colors, int
// }
// }
const Nes_Emu *e = (Nes_Emu*) ePtr;
const unsigned char *in_pixels = e->frame().pixels;
if (in_pixels == NULL) return;
int32_t *out_pixels = dest;
for (unsigned h = 0; h < Nes_Emu::image_height; h++, in_pixels += e->frame().pitch, out_pixels += Nes_Emu::image_width)
for (unsigned w = 0; w < Nes_Emu::image_width; w++)
{

View File

@ -9,7 +9,6 @@ pageSize = get_option('CPUFunctionAlignment')
# Loading dependencies
subdir('source')
subdir('extern')
# Common application flags
commonCompileArgs = [ '-Wfatal-errors', '-Wall', '-Wno-multichar' ]
@ -27,7 +26,7 @@ quickerNESPlayerSrc = [
executable('player',
'source/player.cpp',
cpp_args : [ commonCompileArgs, '-DNCURSES' ],
dependencies : [ quickerNESCoreDependency, quickerNESApplicationDependency, dependency('sdl2'), dependency('SDL2_image') ],
dependencies : [ quickerNESDependency, toolDependency, dependency('sdl2'), dependency('SDL2_image') ],
include_directories : include_directories(['source']),
link_args : [ '-lncurses' ],
sources : quickerNESPlayerSrc
@ -38,7 +37,7 @@ executable('player',
quickerNESTester = executable('quickerNESTester',
'source/tester.cpp',
cpp_args : [ commonCompileArgs, '-Werror' ],
dependencies : [ quickerNESCoreDependency, quickerNESApplicationDependency ],
dependencies : [ quickerNESDependency, toolDependency ],
include_directories : include_directories(['../extern/json'])
)
@ -47,7 +46,7 @@ quickerNESTester = executable('quickerNESTester',
quickNESTester = executable('quickNESTester',
'source/tester.cpp',
cpp_args : [ commonCompileArgs ],
dependencies : [ quickNESCoreDependency, quickerNESApplicationDependency ],
dependencies : [ quickNESDependency, toolDependency ],
include_directories : include_directories(['../extern/json'])
)

View File

@ -1,20 +1,16 @@
#pragma once
#include <Nes_Emu.h>
#include <Nes_State.h>
#include <string>
#include <utils.hpp>
#include "sha1/sha1.hpp"
#ifdef USE_ORIGINAL_QUICKNES
extern void register_misc_mappers();
extern void register_extra_mappers();
#endif
#define _LOW_MEM_SIZE 0x800
#define _HIGH_MEM_SIZE 0x2000
#define _NAMETABLES_MEM_SIZE 0x1000
// Size of image generated in graphics buffer
static const uint16_t image_width = 256;
static const uint16_t image_height = 240;
class EmuInstance
{
public:
@ -22,117 +18,8 @@ class EmuInstance
typedef uint8_t inputType;
// Deleting default constructors
EmuInstance() = delete;
EmuInstance(EmuInstance& e) = delete;
~EmuInstance() = default;
EmuInstance(const std::string& romFilePath, const std::string& stateFilePath)
{
// Creating new emulator
_nes = new Nes_Emu;
// If running the original QuickNES, register extra mappers now
#ifdef USE_ORIGINAL_QUICKNES
register_misc_mappers();
register_extra_mappers();
#endif
// Loading ROM
std::string romData;
bool status = loadStringFromFile(romData, romFilePath.c_str());
if (status == false) EXIT_WITH_ERROR("Could not find/read state file: %s\n", romFilePath.c_str());
// Calculating ROM hash value
_romSHA1String = SHA1::GetHash((uint8_t*)romData.data(), romData.size());
// Loading the rom into the emulator
#ifdef USE_ORIGINAL_QUICKNES
Mem_File_Reader romReader(romData.data(), (int)romData.size());
Auto_File_Reader romFile(romReader);
auto result = _nes->load_ines(romFile);
if (result != 0) EXIT_WITH_ERROR("Could not initialize emulator with rom file: %s\n", romFilePath.c_str());
#else
auto result = _nes->load_ines((const uint8_t*) romData.data());
if (result != 0) EXIT_WITH_ERROR("Could not initialize emulator with rom file: %s\n", romFilePath.c_str());
#endif
// Getting state size to use
_stateSize = getStateSizeImpl();
// Loading state file, if specified
if (stateFilePath != "") loadStateFile(stateFilePath);
}
uint8_t* getLowMem() { return _nes->low_mem(); };
uint8_t* getNametableMem() { return _nes->nametable_mem(); };
uint8_t* getHighMem() { return _nes->high_mem();};
const uint8_t* getChrMem() { return _nes->chr_mem();};
size_t getChrMemSize() { return _nes->chr_size();};
// uint8_t* getSpriteRAM() { return _nes->spr_mem(); }
// uint16_t getSpriteRAMSize() { return _nes->spr_mem_size(); }
const std::string getRomSHA1() const { return _romSHA1String; };
void loadStateFile(const std::string& stateFilePath)
{
// Loading state data
std::string stateData;
bool status = loadStringFromFile(stateData, stateFilePath.c_str());
if (status == false) EXIT_WITH_ERROR("Could not find/read state file: %s\n", stateFilePath.c_str());
Mem_File_Reader stateReader(stateData.data(), (int)stateData.size());
Auto_File_Reader stateFile(stateReader);
// Loading state data into state object
Nes_State state;
state.read(stateFile);
// Loading state object into the emulator
_nes->load_state(state);
}
inline size_t getStateSize() const { return _stateSize; }
inline hash_t getStateHash()
{
MetroHash128 hash;
uint8_t stateData[_stateSize];
serializeState(stateData);
hash.Update(getLowMem(), _LOW_MEM_SIZE);
hash.Update(getHighMem(), _HIGH_MEM_SIZE);
hash.Update(getNametableMem(), _NAMETABLES_MEM_SIZE);
hash.Update(getChrMem(), getChrMemSize());
hash_t result;
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
return result;
}
void saveStateFile(const std::string& stateFilePath) const
{
std::string stateData;
stateData.resize(_stateSize);
serializeState((uint8_t*)stateData.data());
saveStringToFile(stateData, stateFilePath.c_str());
}
void serializeState(uint8_t* state) const
{
Mem_Writer w(state, _stateSize, 0);
Auto_File_Writer a(w);
_nes->save_state(a);
}
void deserializeState(const uint8_t* state)
{
Mem_File_Reader r(state, _stateSize);
Auto_File_Reader a(r);
_nes->load_state(a);
}
// Controller input bits
// 0 - A / 1
// 1 - B / 2
@ -190,55 +77,102 @@ class EmuInstance
moveString += "|";
return moveString;
}
void enableRendering() { _doRendering = true; }
void disableRendering() { _doRendering = false; }
void advanceState(const std::string& move)
inline void advanceState(const std::string& move)
{
if (move.find("r") != std::string::npos) _nes->reset(false);
if (move.find("r") != std::string::npos) doSoftReset();
advanceState(moveStringToCode(move), 0);
advanceStateImpl(moveStringToCode(move), 0);
}
void advanceState(const inputType controller1, const inputType controller2)
inline size_t getStateSize() const { return _stateSize; }
inline std::string getRomSHA1() const { return _romSHA1String; }
inline hash_t getStateHash() const
{
if (_doRendering == true) _nes->emulate_frame(controller1, controller2);
if (_doRendering == false) _nes->emulate_skip_frame(controller1, controller2);
MetroHash128 hash;
uint8_t stateData[_stateSize];
serializeState(stateData);
hash.Update(getLowMem(), _LOW_MEM_SIZE);
hash.Update(getHighMem(), _HIGH_MEM_SIZE);
hash.Update(getNametableMem(), _NAMETABLES_MEM_SIZE);
hash.Update(getChrMem(), getChrMemSize());
hash_t result;
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
return result;
}
Nes_Emu* getInternalEmulator() const { return _nes; }
inline void enableRendering() { _doRendering = true; };
inline void disableRendering() { _doRendering = false; };
private:
inline void loadStateFile(const std::string& stateFilePath)
{
std::string stateData;
bool status = loadStringFromFile(stateData, stateFilePath);
if (status == false) EXIT_WITH_ERROR("Could not find/read state file: %s\n", stateFilePath.c_str());
deserializeState((uint8_t*)stateData.data());
}
inline void saveStateFile(const std::string& stateFilePath) const
{
std::string stateData;
stateData.resize(_stateSize);
serializeState((uint8_t*)stateData.data());
saveStringToFile(stateData, stateFilePath.c_str());
}
inline void loadROMFile(const std::string& romFilePath)
{
// Loading ROM data
bool status = loadStringFromFile(_romData, romFilePath);
if (status == false) EXIT_WITH_ERROR("Could not find/read ROM file: %s\n", romFilePath.c_str());
// Calculating ROM hash value
_romSHA1String = SHA1::GetHash((uint8_t*)_romData.data(), _romData.size());
// Actually loading rom file
status = loadROMFileImpl(_romData);
if (status == false) EXIT_WITH_ERROR("Could not process ROM file: %s\n", romFilePath.c_str());
// Detecting state size
_stateSize = getStateSizeImpl();
}
// Virtual functions
virtual bool loadROMFileImpl(const std::string& romFilePath) = 0;
virtual void advanceStateImpl(const inputType controller1, const inputType controller2) = 0;
virtual uint8_t* getLowMem() const = 0;
virtual uint8_t* getNametableMem() const = 0;
virtual uint8_t* getHighMem() const = 0;
virtual const uint8_t* getChrMem() const = 0;
virtual size_t getChrMemSize() const = 0;
virtual void serializeState(uint8_t* state) const = 0;
virtual void deserializeState(const uint8_t* state) = 0;
virtual size_t getStateSizeImpl() const = 0;
virtual void doSoftReset() = 0;
virtual void doHardReset() = 0;
virtual std::string getCoreName() const = 0;
virtual void* getInternalEmulatorPointer() const = 0;
protected:
EmuInstance() = default;
// Storage for the state
size_t _stateSize;
// Flag to determine whether to enable/disable rendering
bool _doRendering = true;
private:
inline size_t getStateSizeImpl() const
{
#ifdef USE_ORIGINAL_QUICKNES
#define _DUMMY_SIZE 65536
uint8_t data[_DUMMY_SIZE];
Mem_Writer w(data, _DUMMY_SIZE);
Auto_File_Writer a(w);
_nes->save_state(a);
return w.size();
#else
// Using dry writer to just obtain the state size
Dry_Writer w;
Auto_File_Writer a(w);
_nes->save_state(a);
return w.size();
#endif
}
// Emulator instance
Nes_Emu* _nes;
// State size for the given rom
size_t _stateSize;
// Storage for the ROM data
std::string _romData;
// SHA1 rom hash
std::string _romSHA1String;
};

View File

@ -1,9 +1,10 @@
# Getting core configuration
subdir('core')
# Getting core configurations
subdir('quickNES')
subdir('quickerNES')
# quickerNES Application Configuration
# Tool Configuration
quickerNESApplicationDependency = declare_dependency(
toolDependency = declare_dependency(
include_directories : include_directories(['.', '../extern']),
sources : [ '../extern/metrohash128/metrohash128.cpp' ]
)
)

View File

@ -11,6 +11,8 @@
#define _INVERSE_FRAME_RATE 16667
class Nes_Emu;
struct stepData_t
{
std::string input;
@ -37,8 +39,11 @@ class PlaybackInstance
// Initializes the playback module instance
PlaybackInstance(EmuInstance* emu, const std::vector<std::string>& sequence, const std::string& overlayPath = "") : _emu(emu)
{
// Enabling emulation rendering
_emu->enableRendering();
// Loading Emulator instance HQN
_hqnState.m_emu = _emu->getInternalEmulator();
_hqnState.setEmulatorPointer(_emu->getInternalEmulatorPointer());
static uint8_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height];
_hqnState.m_emu->set_pixels(video_buffer, Nes_Emu::image_width+8);
@ -149,7 +154,7 @@ class PlaybackInstance
// Since we do not store the blit information (too much memory), we need to load the previous frame and re-run the input
// If its the first step, then simply reset
if (stepId == 0) _emu->getInternalEmulator()->reset();
if (stepId == 0) _emu->doHardReset();
// Else we load the previous frame
if (stepId > 0)
@ -161,7 +166,7 @@ class PlaybackInstance
// Updating image
int32_t curBlit[BLIT_SIZE];
saveBlit(_emu->getInternalEmulator(), curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
saveBlit(_emu->getInternalEmulatorPointer(), curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
_hqnGUI->update_blit(curBlit, _overlayBaseSurface, overlayButtonASurface, overlayButtonBSurface, overlayButtonSelectSurface, overlayButtonStartSurface, overlayButtonLeftSurface, overlayButtonRightSurface, overlayButtonUpSurface, overlayButtonDownSurface);
}

View File

@ -4,6 +4,14 @@
#include "emuInstance.hpp"
#include "playbackInstance.hpp"
#ifdef _USE_QUICKNES
#include "quickNESInstance.hpp"
#endif
#ifdef _USE_QUICKERNES
#include "quickerNESInstance.hpp"
#endif
int main(int argc, char *argv[])
{
// Parsing command line arguments
@ -71,7 +79,13 @@ int main(int argc, char *argv[])
refreshTerminal();
// Creating emulator instance
auto e = EmuInstance(romFilePath, stateFilePath);
#ifdef _USE_QUICKNES
auto e = QuickNESInstance();
#endif
#ifdef _USE_QUICKERNES
auto e = QuickerNESInstance();
#endif
// Creating playback instance
auto p = PlaybackInstance(&e, sequence);

View File

@ -0,0 +1,80 @@
/* Copyright notice for this file:
* Copyright (C) 2018
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* This mapper was added by retrowertz for Libretro port of QuickNES.
*
* Mapper 180 Crazy Climber
*
*/
#include "Nes_Mapper.h"
template < bool _is152 >
class Mapper_74x161x162x32 : public Nes_Mapper {
public:
Mapper_74x161x162x32()
{
register_state( &bank, 1 );
}
virtual void reset_state()
{
if ( _is152 == 0 )
bank = ~0;
}
virtual void apply_mapping()
{
if ( _is152 )
write( 0, 0, bank );
else
{
intercept_writes( 0x6000, 1 );
write_intercepted( 0, 0x6000, bank );
}
}
virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data )
{
if ( ( addr != 0x6000 ) || _is152 )
return false;
bank = data;
set_prg_bank( 0x8000, bank_32k, ( bank >> 4 ) & 0x03 );
set_chr_bank( 0x0000, bank_8k, ( ( bank >> 4 ) & 0x04 ) | ( bank & 0x03 ) );
return true;
}
virtual void write( nes_time_t, nes_addr_t addr, int data )
{
if ( _is152 == 0) return;
bank = handle_bus_conflict (addr, data );
set_prg_bank( 0x8000, bank_16k, ( bank >> 4 ) & 0x07 );
set_chr_bank( 0x0000, bank_8k, bank & 0x0F );
mirror_single( ( bank >> 7) & 0x01 );
}
uint8_t bank;
};
void register_mapper_70();
void register_mapper_70()
{
register_mapper< Mapper_74x161x162x32 <true> > ( 70 );
}

View File

@ -1,6 +1,6 @@
# quickNES Core sources
quickNESCoreSrc = [
quickNESSrc = [
'QuickNES_Core/nes_emu/abstract_file.cpp',
'QuickNES_Core/nes_emu/apu_state.cpp',
'QuickNES_Core/nes_emu/Blip_Buffer.cpp',
@ -64,13 +64,15 @@ quickNESCoreSrc = [
'QuickNES_Core/nes_emu/Mapper_TaitoX1005.cpp',
'QuickNES_Core/nes_emu/Mapper_TaitoTC0190.cpp',
'QuickNES_Core/nes_emu/Mapper_Un1rom.cpp',
'QuickNES_Core/nes_emu/nes_ntsc.cpp'
'QuickNES_Core/nes_emu/nes_ntsc.cpp',
'Mapper_70.cpp',
'quickNESInstance.hpp'
]
# quickNES Core Configuration
quickNESCoreDependency = declare_dependency(
compile_args : [ '-DUSE_ORIGINAL_QUICKNES'],
include_directories : include_directories(['QuickNES_Core/nes_emu']),
sources : [ quickNESCoreSrc ]
quickNESDependency = declare_dependency(
compile_args : [ '-D_USE_QUICKNES' ],
include_directories : include_directories(['.', 'QuickNES_Core/nes_emu']),
sources : [ quickNESSrc ]
)

View File

@ -0,0 +1,89 @@
#pragma once
#include <Nes_Emu.h>
#include <Nes_State.h>
#include <emuInstance.hpp>
#define _DUMMY_SIZE 65536
extern void register_misc_mappers();
extern void register_extra_mappers();
extern void register_mapper_70();
class QuickNESInstance : public EmuInstance
{
public:
QuickNESInstance() : EmuInstance()
{
// Creating new emulator
_nes = new Nes_Emu;
// Setting video buffer
_nes->set_pixels(video_buffer, image_width+8);
// If running the original QuickNES, register extra mappers now
register_misc_mappers();
register_extra_mappers();
register_mapper_70();
}
virtual bool loadROMFileImpl(const std::string& romData) override
{
// Loading rom data
Mem_File_Reader romReader(romData.data(), (int)romData.size());
Auto_File_Reader romFile(romReader);
auto result = _nes->load_ines(romFile);
return result == 0;
}
uint8_t* getLowMem() const override { return _nes->low_mem(); };
uint8_t* getNametableMem() const override { return _nes->nametable_mem(); };
uint8_t* getHighMem() const override { return _nes->high_mem();};
const uint8_t* getChrMem() const override { return _nes->chr_mem();};
size_t getChrMemSize() const override { return _nes->chr_size();};
void serializeState(uint8_t* state) const override
{
Mem_Writer w(state, _stateSize, 0);
Auto_File_Writer a(w);
_nes->save_state(a);
}
void deserializeState(const uint8_t* state) override
{
Mem_File_Reader r(state, _stateSize);
Auto_File_Reader a(r);
_nes->load_state(a);
}
void advanceStateImpl(const inputType controller1, const inputType controller2) override
{
if (_doRendering == true) _nes->emulate_frame(controller1, controller2);
if (_doRendering == false) _nes->emulate_skip_frame(controller1, controller2);
}
std::string getCoreName() const override { return "QuickNES"; }
void doSoftReset() override { _nes->reset(false); }
void doHardReset() override { _nes->reset(true); }
void* getInternalEmulatorPointer() const override { return _nes; }
private:
inline size_t getStateSizeImpl() const override
{
uint8_t* data = (uint8_t*) malloc (_DUMMY_SIZE);
Mem_Writer w(data, _DUMMY_SIZE);
Auto_File_Writer a(w);
_nes->save_state(a);
free(data);
return w.size();
}
// Video buffer
uint8_t video_buffer[image_width * image_height];
// Emulator instance
Nes_Emu* _nes;
};

Some files were not shown because too many files have changed in this diff Show More