Supporting arkanoidNES

This commit is contained in:
SergioMartin86 2024-08-07 21:11:24 +02:00
parent 9f164b4a58
commit 29beac9732
11 changed files with 9544 additions and 51 deletions

4
extern/hqn/hqn.cpp vendored
View File

@ -79,13 +79,13 @@ error_t HQNState::advanceFrame(bool sleep)
SDL_Delay(wantTicks - ticks); SDL_Delay(wantTicks - ticks);
} }
// m_frameTime = wantTicks - m_prevFrame; // m_frameTime = wantTicks - m_prevFrame;
error_t result = m_emu->emulate_frame(joypad[0], joypad[1]); // error_t result = m_emu->emulate_frame(joypad[0], joypad[1]);
if (m_listener) if (m_listener)
m_listener->onAdvanceFrame(this); m_listener->onAdvanceFrame(this);
ticks = SDL_GetTicks(); ticks = SDL_GetTicks();
m_frameTime = ticks - m_prevFrame; m_frameTime = ticks - m_prevFrame;
m_prevFrame = ticks; m_prevFrame = ticks;
return result; return 0;
} }
void HQNState::setFramerate(int fps) void HQNState::setFramerate(int fps)

View File

@ -20,6 +20,8 @@ struct input_t
bool reset = false; bool reset = false;
port_t port1 = 0; port_t port1 = 0;
port_t port2 = 0; port_t port2 = 0;
port_t arkanoidLatch = 0;
uint8_t arkanoidFire = 0;
}; };
class InputParser class InputParser
@ -30,9 +32,14 @@ class InputParser
none, none,
joypad, joypad,
fourscore1, fourscore1,
fourscore2 fourscore2,
arkanoidNES,
arkanoidFamicom
}; };
controller_t _controller1Type;
controller_t _controller2Type;
InputParser(const nlohmann::json &config) InputParser(const nlohmann::json &config)
{ {
// Parsing controller 1 type // Parsing controller 1 type
@ -59,6 +66,16 @@ class InputParser
_controller1Type = controller_t::fourscore2; _controller1Type = controller_t::fourscore2;
isTypeRecognized = true; isTypeRecognized = true;
} }
if (controller1Type == "ArkanoidNES")
{
_controller1Type = controller_t::arkanoidNES;
isTypeRecognized = true;
}
if (controller1Type == "ArkanoidFamicom")
{
_controller1Type = controller_t::arkanoidFamicom;
isTypeRecognized = true;
}
if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Controller 1 type not recognized: '%s'\n", controller1Type.c_str()); if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Controller 1 type not recognized: '%s'\n", controller1Type.c_str());
} }
@ -105,10 +122,12 @@ class InputParser
parseConsoleInputs(input.reset, input.power, ss, inputString); parseConsoleInputs(input.reset, input.power, ss, inputString);
// Parsing controller 1 inputs // Parsing controller 1 inputs
parseControllerInputs(_controller1Type, input.port1, ss, inputString); if (_controller1Type == arkanoidNES) parseArkanoidInput(input, ss, inputString);
if (_controller1Type == arkanoidFamicom) parseArkanoidInput(input, ss, inputString);
if (_controller1Type == joypad || _controller1Type == fourscore1) parseControllerInputs(_controller1Type, input.port1, ss, inputString);
// Parsing controller 1 inputs // Parsing controller 2 inputs
parseControllerInputs(_controller2Type, input.port2, ss, inputString); if (_controller2Type == joypad || _controller2Type == fourscore2) parseControllerInputs(_controller2Type, input.port2, ss, inputString);
// End separator // End separator
if (ss.get() != '|') reportBadInputString(inputString); if (ss.get() != '|') reportBadInputString(inputString);
@ -175,6 +194,55 @@ class InputParser
if (c == 'A') code |= 0b00000001; if (c == 'A') code |= 0b00000001;
} }
static inline void parseArkanoidInput(input_t& input, std::istringstream& ss, const std::string& inputString)
{
uint8_t potentiometer = 0;
uint8_t fire = 0;
// Controller separator
if (ss.get() != '|') reportBadInputString(inputString);
if (ss.get() != ' ') reportBadInputString(inputString);
if (ss.get() != ' ') reportBadInputString(inputString);
char c = ss.get(); // Hundreds
if (c != ' ' && c < 48 && c > 57) reportBadInputString(inputString);
if (c != ' ') potentiometer += 100 * ( (uint8_t)c - 48 );
c = ss.get(); // Tenths
if (c != ' ' && c < 48 && c > 57) reportBadInputString(inputString);
if (c != ' ') potentiometer += 10 * ( (uint8_t)c - 48 );
c = ss.get(); // Units
if (c != ' ' && c < 48 && c > 57) reportBadInputString(inputString);
if (c != ' ') potentiometer += (uint8_t)c - 48;
// Comma
if (ss.get() != ',') reportBadInputString(inputString);
// Fire
c = ss.get();
if (c != '.' && c != 'F') reportBadInputString(inputString);
if (c == 'F') fire = 1;
// Fire is encoded in port 1
input.arkanoidFire = fire;
// Potentiometer is encoded in port 2 - MSB and adding one bit for signalling the presence of the potentiometer, subtracted from 173
uint8_t subtracter = 171 - potentiometer;
input.arkanoidLatch = 0;
if ((subtracter & 128) > 0) input.arkanoidLatch += 1;
if ((subtracter & 64) > 0) input.arkanoidLatch += 2;
if ((subtracter & 32) > 0) input.arkanoidLatch += 4;
if ((subtracter & 16) > 0) input.arkanoidLatch += 8;
if ((subtracter & 8) > 0) input.arkanoidLatch += 16;
if ((subtracter & 4) > 0) input.arkanoidLatch += 32;
if ((subtracter & 2) > 0) input.arkanoidLatch += 64;
if ((subtracter & 1) > 0) input.arkanoidLatch += 128;
}
static void parseControllerInputs(const controller_t type, port_t &port, std::istringstream &ss, const std::string &inputString) static void parseControllerInputs(const controller_t type, port_t &port, std::istringstream &ss, const std::string &inputString)
{ {
// If no controller assigned then, its port is all zeroes. // If no controller assigned then, its port is all zeroes.
@ -254,9 +322,6 @@ class InputParser
if (c == '.') reset = false; if (c == '.') reset = false;
} }
controller_t _controller1Type;
controller_t _controller2Type;
}; // class InputParser }; // class InputParser
} // namespace jaffar } // namespace jaffar

View File

@ -84,7 +84,6 @@ class NESInstanceBase
// Flag to determine whether to enable/disable rendering // Flag to determine whether to enable/disable rendering
bool _doRendering = true; bool _doRendering = true;
private:
// Input parser instance // Input parser instance
std::unique_ptr<jaffar::InputParser> _inputParser; std::unique_ptr<jaffar::InputParser> _inputParser;
}; };

View File

@ -97,7 +97,7 @@ int main(int argc, char *argv[])
if (status == false) JAFFAR_THROW_LOGIC("[ERROR] Could not find or read from sequence file: %s\n", sequenceFilePath.c_str()); if (status == false) JAFFAR_THROW_LOGIC("[ERROR] Could not find or read from sequence file: %s\n", sequenceFilePath.c_str());
// Building sequence information // Building sequence information
const auto sequence = jaffarCommon::string::split(inputSequence, ' '); const auto sequence = jaffarCommon::string::split(inputSequence, '\n');
// Initializing terminal // Initializing terminal
jaffarCommon::logger::initializeTerminal(); jaffarCommon::logger::initializeTerminal();
@ -182,6 +182,7 @@ int main(int argc, char *argv[])
jaffarCommon::logger::log("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength); jaffarCommon::logger::log("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength);
jaffarCommon::logger::log("[] Input: %s\n", inputString.c_str()); jaffarCommon::logger::log("[] Input: %s\n", inputString.c_str());
jaffarCommon::logger::log("[] State Hash: 0x%lX%lX\n", hash.first, hash.second); jaffarCommon::logger::log("[] State Hash: 0x%lX%lX\n", hash.first, hash.second);
jaffarCommon::logger::log("[] Paddle X: %u\n", e.getLowMem()[0x11A]);
// Only print commands if not in reproduce mode // Only print commands if not in reproduce mode
if (isReproduce == false) jaffarCommon::logger::log("[] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit\n"); if (isReproduce == false) jaffarCommon::logger::log("[] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit\n");

View File

@ -58,13 +58,13 @@ struct nes_state_lite_t
uint8_t frame_count; // number of frames emulated since power-up uint8_t frame_count; // number of frames emulated since power-up
}; };
struct joypad_state_t struct input_state_t
{ {
uint32_t joypad_latches[2]; // joypad 1 & 2 shift registers uint32_t joypad_latches[2]; // input_state 1 & 2 shift registers
uint32_t arkanoid_latch; // arkanoid latch
uint8_t arkanoid_fire; // arkanoid latch
uint8_t w4016; // strobe uint8_t w4016; // strobe
uint8_t unused[3];
}; };
static_assert(sizeof(joypad_state_t) == 12);
struct cpu_state_t struct cpu_state_t
{ {
@ -98,13 +98,22 @@ class Core : private Cpu
bool CHRRBlockEnabled = true; bool CHRRBlockEnabled = true;
bool SRAMBlockEnabled = true; bool SRAMBlockEnabled = true;
// APU and Joypad
enum controllerType_t
{
none_t,
joypad_t,
arkanoidNES_t,
arkanoidFamicom_t,
};
Core() : ppu(this) Core() : ppu(this)
{ {
cart = NULL; cart = NULL;
impl = NULL; impl = NULL;
mapper = NULL; mapper = NULL;
memset(&nes, 0, sizeof nes); memset(&nes, 0, sizeof nes);
memset(&joypad, 0, sizeof joypad); memset(&input_state, 0, sizeof input_state);
} }
~Core() ~Core()
@ -206,8 +215,8 @@ class Core : private Cpu
// CTRL Block // CTRL Block
if (CTRLBlockEnabled == true) if (CTRLBlockEnabled == true)
{ {
const auto inputDataSize = sizeof(joypad_state_t); const auto inputDataSize = sizeof(input_state_t);
const auto inputData = (uint8_t *)&joypad; const auto inputData = (uint8_t *)&input_state;
serializer.pushContiguous(inputData, inputDataSize); serializer.pushContiguous(inputData, inputDataSize);
} }
@ -323,8 +332,8 @@ class Core : private Cpu
// CTRL Block // CTRL Block
if (CTRLBlockEnabled == true) if (CTRLBlockEnabled == true)
{ {
const auto outputData = (uint8_t *)&joypad; const auto outputData = (uint8_t *)&input_state;
const auto inputDataSize = sizeof(joypad_state_t); const auto inputDataSize = sizeof(input_state_t);
deserializer.popContiguous(outputData, inputDataSize); deserializer.popContiguous(outputData, inputDataSize);
} }
@ -549,8 +558,10 @@ class Core : private Cpu
if (!cart->has_battery_ram() || erase_battery_ram) if (!cart->has_battery_ram() || erase_battery_ram)
memset(impl->sram, 0xFF, impl->sram_size); memset(impl->sram, 0xFF, impl->sram_size);
joypad.joypad_latches[0] = 0; input_state.joypad_latches[0] = 0;
joypad.joypad_latches[1] = 0; input_state.joypad_latches[1] = 0;
input_state.arkanoid_latch = 0;
input_state.arkanoid_fire = 0;
nes.frame_count = 0; nes.frame_count = 0;
} }
@ -572,7 +583,7 @@ class Core : private Cpu
error_count = 0; error_count = 0;
} }
nes_time_t emulate_frame(uint32_t joypad1, uint32_t joypad2) nes_time_t emulate_frame(uint32_t joypad1, uint32_t joypad2, uint32_t arkanoid_latch, uint8_t arkanoid_fire)
{ {
#ifdef _QUICKERNES_DETECT_JOYPAD_READS #ifdef _QUICKERNES_DETECT_JOYPAD_READS
joypad_read_count = 0; joypad_read_count = 0;
@ -580,6 +591,8 @@ class Core : private Cpu
current_joypad[0] = joypad1; current_joypad[0] = joypad1;
current_joypad[1] = joypad2; current_joypad[1] = joypad2;
current_arkanoid_latch = arkanoid_latch;
current_arkanoid_fire = arkanoid_fire;
cpu_time_offset = ppu.begin_frame(nes.timestamp) - 1; cpu_time_offset = ppu.begin_frame(nes.timestamp) - 1;
ppu_2002_time = 0; ppu_2002_time = 0;
@ -649,6 +662,8 @@ class Core : private Cpu
public: public:
uint32_t current_joypad[2]; uint32_t current_joypad[2];
uint32_t current_arkanoid_latch;
uint8_t current_arkanoid_fire;
Cart const *cart; Cart const *cart;
Mapper *mapper; Mapper *mapper;
nes_state_t nes; nes_state_t nes;
@ -697,8 +712,12 @@ class Core : private Cpu
return t; return t;
} }
// APU and Joypad
joypad_state_t joypad; controllerType_t _controllerType = controllerType_t::none_t;
input_state_t input_state;
void setControllerType(controllerType_t type) { _controllerType = type; }
int read_io(nes_addr_t addr) int read_io(nes_addr_t addr)
{ {
@ -709,15 +728,58 @@ class Core : private Cpu
joypad_read_count++; joypad_read_count++;
#endif #endif
// to do: to aid with recording, doesn't emulate transparent latch, // If write flag is put into w4016, reading from it returns nothing
// so a game that held strobe at 1 and read $4016 or $4017 would not get if (input_state.w4016 & 1) return 0;
// the current A status as occurs on a NES
if (joypad.w4016 & 1) return 0; // Proceed depending on input type
const uint8_t result = joypad.joypad_latches[addr & 1] & 1; switch(_controllerType)
joypad.joypad_latches[addr & 1] >>= 1; {
case controllerType_t::joypad_t:
{
const uint8_t result = input_state.joypad_latches[addr & 1] & 1;
input_state.joypad_latches[addr & 1] >>= 1;
return result; return result;
} }
case controllerType_t::arkanoidNES_t:
{
if (addr == 0x4017)
{
// latch 0 encodes fire, latch 1 encodes potentiometer
const uint8_t result = (input_state.arkanoid_latch & 1) * 16 + current_arkanoid_fire * 8;
// Advancing latch 1
input_state.arkanoid_latch >>= 1;
return result;
}
}
case controllerType_t::arkanoidFamicom_t:
{
if (addr == 0x4016)
{
// latch 0 encodes fire
const uint8_t result = (input_state.joypad_latches[0] & 1) * 2;
return result;
}
if (addr == 0x4017)
{
// latch 1 encodes potentiometer
const uint8_t result = (input_state.joypad_latches[1] & 1) * 2;
// Advancing latch 1
input_state.joypad_latches[1] >>= 1;
return result;
}
}
default:
return 0;
}
}
if (addr == Apu::status_addr) if (addr == Apu::status_addr)
return impl->apu.read_status(clock()); return impl->apu.read_status(clock());
@ -734,16 +796,18 @@ class Core : private Cpu
return; return;
} }
// joypad strobe // input_state strobe
if (addr == 0x4016) if (addr == 0x4016)
{ {
// if strobe goes low, latch data // if strobe goes low, latch data
if (joypad.w4016 & 1 & ~data) if (input_state.w4016 & 1 & ~data)
{ {
joypad.joypad_latches[0] = current_joypad[0]; input_state.joypad_latches[0] = current_joypad[0];
joypad.joypad_latches[1] = current_joypad[1]; input_state.joypad_latches[1] = current_joypad[1];
input_state.arkanoid_latch = current_arkanoid_latch;
input_state.arkanoid_fire = current_arkanoid_fire;
} }
joypad.w4016 = data; input_state.w4016 = data;
return; return;
} }

View File

@ -104,16 +104,16 @@ void Emu::set_palette_range(int begin, int end)
host_palette_size = end - emu.ppu.palette_begin; host_palette_size = end - emu.ppu.palette_begin;
} }
const char *Emu::emulate_skip_frame(uint32_t joypad1, uint32_t joypad2) const char *Emu::emulate_skip_frame(uint32_t joypad1, uint32_t joypad2, uint32_t arkanoid_latch, uint8_t arkanoid_fire)
{ {
char *old_host_pixels = host_pixels; char *old_host_pixels = host_pixels;
host_pixels = NULL; host_pixels = NULL;
emu.emulate_frame(joypad1, joypad2); emu.emulate_frame(joypad1, joypad2, arkanoid_latch, arkanoid_fire);
host_pixels = old_host_pixels; host_pixels = old_host_pixels;
return 0; return 0;
} }
const char *Emu::emulate_frame(uint32_t joypad1, uint32_t joypad2) const char *Emu::emulate_frame(uint32_t joypad1, uint32_t joypad2, uint32_t arkanoid_latch, uint8_t arkanoid_fire)
{ {
emu.ppu.host_pixels = NULL; emu.ppu.host_pixels = NULL;
@ -144,7 +144,7 @@ const char *Emu::emulate_frame(uint32_t joypad1, uint32_t joypad2)
if (sound_buf->samples_avail()) if (sound_buf->samples_avail())
clear_sound_buf(); clear_sound_buf();
nes_time_t frame_len = emu.emulate_frame(joypad1, joypad2); nes_time_t frame_len = emu.emulate_frame(joypad1, joypad2, arkanoid_latch, arkanoid_fire);
sound_buf->end_frame(frame_len, false); sound_buf->end_frame(frame_len, false);
f = frame_; f = frame_;
@ -159,7 +159,7 @@ const char *Emu::emulate_frame(uint32_t joypad1, uint32_t joypad2)
else else
{ {
emu.ppu.max_palette_size = 0; emu.ppu.max_palette_size = 0;
emu.emulate_frame(joypad1, joypad2); emu.emulate_frame(joypad1, joypad2, arkanoid_latch, arkanoid_fire);
} }
return 0; return 0;

View File

@ -54,15 +54,17 @@ class Emu
void enableStateBlock(const std::string &block) { emu.enableStateBlock(block); }; void enableStateBlock(const std::string &block) { emu.enableStateBlock(block); };
void disableStateBlock(const std::string &block) { emu.disableStateBlock(block); }; void disableStateBlock(const std::string &block) { emu.disableStateBlock(block); };
void setControllerType(Core::controllerType_t type) { emu.setControllerType(type); }
// Basic emulation // Basic emulation
// Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image // Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image
// and sound are available for output using the accessors below. // and sound are available for output using the accessors below.
virtual const char *emulate_frame(uint32_t joypad1, uint32_t joypad2 = 0); virtual const char *emulate_frame(uint32_t joypad1, uint32_t joypad2, uint32_t arkanoid_latch, uint8_t arkanoid_fire);
// Emulate one video frame using joypad1 and joypad2 as input, but skips drawing. // Emulate one video frame using joypad1 and joypad2 as input, but skips drawing.
// Afterwards, audio is available for output using the accessors below. // Afterwards, audio is available for output using the accessors below.
virtual const char *emulate_skip_frame(uint32_t joypad1, uint32_t joypad2 = 0); virtual const char *emulate_skip_frame(uint32_t joypad1, uint32_t joypad2, uint32_t arkanoid_latch, uint8_t arkanoid_fire);
// Maximum size of palette that can be generated // Maximum size of palette that can be generated
static const uint16_t max_palette_size = 256; static const uint16_t max_palette_size = 256;

View File

@ -8,7 +8,12 @@ typedef quickerNES::Emu emulator_t;
class NESInstance final : public NESInstanceBase class NESInstance final : public NESInstanceBase
{ {
public: public:
NESInstance(const nlohmann::json &config) : NESInstanceBase(config) {} NESInstance(const nlohmann::json &config) : NESInstanceBase(config)
{
_nes.setControllerType(quickerNES::Core::controllerType_t::joypad_t);
if (_inputParser->_controller1Type == jaffar::InputParser::controller_t::arkanoidFamicom) _nes.setControllerType(quickerNES::Core::controllerType_t::arkanoidFamicom_t);
if (_inputParser->_controller1Type == jaffar::InputParser::controller_t::arkanoidNES) _nes.setControllerType(quickerNES::Core::controllerType_t::arkanoidNES_t);
}
uint8_t *getLowMem() const override { return _nes.get_low_mem(); }; uint8_t *getLowMem() const override { return _nes.get_low_mem(); };
size_t getLowMemSize() const override { return _nes.get_low_mem_size(); }; size_t getLowMemSize() const override { return _nes.get_low_mem_size(); };
@ -53,8 +58,8 @@ class NESInstance final : public NESInstanceBase
void advanceState(const jaffar::input_t &input) override void advanceState(const jaffar::input_t &input) override
{ {
if (_doRendering == true) _nes.emulate_frame(input.port1, input.port2); if (_doRendering == true) _nes.emulate_frame(input.port1, input.port2, input.arkanoidLatch, input.arkanoidFire);
if (_doRendering == false) _nes.emulate_skip_frame(input.port1, input.port2); if (_doRendering == false) _nes.emulate_skip_frame(input.port1, input.port2, input.arkanoidLatch, input.arkanoidFire);
} }
protected: protected:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
{
"Rom File": "roms/Arkanoid (U) [!].nes",
"Expected ROM SHA1": "B2B30C4F30DD853C215C17B0C67CFE63D61A3062",
"Initial State File": "",
"Sequence File": "arkanoid.arkNESController.sol",
"Disable State Blocks": [ "SRAM", "CHRR", "NTAB", "SPRT", "MAPR", "CTRL", "APUR" ],
"Controller 1 Type": "Joypad",
"Controller 2 Type": "None",
"Differential Compression":
{
"Enabled": false,
"Max Differences": 2200,
"Use Zlib": true
}
}

View File