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);
}
// 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)
m_listener->onAdvanceFrame(this);
ticks = SDL_GetTicks();
m_frameTime = ticks - m_prevFrame;
m_prevFrame = ticks;
return result;
return 0;
}
void HQNState::setFramerate(int fps)

View File

@ -20,6 +20,8 @@ struct input_t
bool reset = false;
port_t port1 = 0;
port_t port2 = 0;
port_t arkanoidLatch = 0;
uint8_t arkanoidFire = 0;
};
class InputParser
@ -30,9 +32,14 @@ class InputParser
none,
joypad,
fourscore1,
fourscore2
fourscore2,
arkanoidNES,
arkanoidFamicom
};
controller_t _controller1Type;
controller_t _controller2Type;
InputParser(const nlohmann::json &config)
{
// Parsing controller 1 type
@ -59,6 +66,16 @@ class InputParser
_controller1Type = controller_t::fourscore2;
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());
}
@ -105,10 +122,12 @@ class InputParser
parseConsoleInputs(input.reset, input.power, ss, inputString);
// 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
parseControllerInputs(_controller2Type, input.port2, ss, inputString);
// Parsing controller 2 inputs
if (_controller2Type == joypad || _controller2Type == fourscore2) parseControllerInputs(_controller2Type, input.port2, ss, inputString);
// End separator
if (ss.get() != '|') reportBadInputString(inputString);
@ -175,6 +194,55 @@ class InputParser
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)
{
// If no controller assigned then, its port is all zeroes.
@ -254,9 +322,6 @@ class InputParser
if (c == '.') reset = false;
}
controller_t _controller1Type;
controller_t _controller2Type;
}; // class InputParser
} // namespace jaffar

View File

@ -84,7 +84,6 @@ class NESInstanceBase
// Flag to determine whether to enable/disable rendering
bool _doRendering = true;
private:
// Input parser instance
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());
// Building sequence information
const auto sequence = jaffarCommon::string::split(inputSequence, ' ');
const auto sequence = jaffarCommon::string::split(inputSequence, '\n');
// Initializing terminal
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("[] Input: %s\n", inputString.c_str());
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
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
};
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 unused[3];
};
static_assert(sizeof(joypad_state_t) == 12);
struct cpu_state_t
{
@ -98,13 +98,22 @@ class Core : private Cpu
bool CHRRBlockEnabled = true;
bool SRAMBlockEnabled = true;
// APU and Joypad
enum controllerType_t
{
none_t,
joypad_t,
arkanoidNES_t,
arkanoidFamicom_t,
};
Core() : ppu(this)
{
cart = NULL;
impl = NULL;
mapper = NULL;
memset(&nes, 0, sizeof nes);
memset(&joypad, 0, sizeof joypad);
memset(&input_state, 0, sizeof input_state);
}
~Core()
@ -206,8 +215,8 @@ class Core : private Cpu
// CTRL Block
if (CTRLBlockEnabled == true)
{
const auto inputDataSize = sizeof(joypad_state_t);
const auto inputData = (uint8_t *)&joypad;
const auto inputDataSize = sizeof(input_state_t);
const auto inputData = (uint8_t *)&input_state;
serializer.pushContiguous(inputData, inputDataSize);
}
@ -323,8 +332,8 @@ class Core : private Cpu
// CTRL Block
if (CTRLBlockEnabled == true)
{
const auto outputData = (uint8_t *)&joypad;
const auto inputDataSize = sizeof(joypad_state_t);
const auto outputData = (uint8_t *)&input_state;
const auto inputDataSize = sizeof(input_state_t);
deserializer.popContiguous(outputData, inputDataSize);
}
@ -549,8 +558,10 @@ class Core : private Cpu
if (!cart->has_battery_ram() || erase_battery_ram)
memset(impl->sram, 0xFF, impl->sram_size);
joypad.joypad_latches[0] = 0;
joypad.joypad_latches[1] = 0;
input_state.joypad_latches[0] = 0;
input_state.joypad_latches[1] = 0;
input_state.arkanoid_latch = 0;
input_state.arkanoid_fire = 0;
nes.frame_count = 0;
}
@ -572,7 +583,7 @@ class Core : private Cpu
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
joypad_read_count = 0;
@ -580,6 +591,8 @@ class Core : private Cpu
current_joypad[0] = joypad1;
current_joypad[1] = joypad2;
current_arkanoid_latch = arkanoid_latch;
current_arkanoid_fire = arkanoid_fire;
cpu_time_offset = ppu.begin_frame(nes.timestamp) - 1;
ppu_2002_time = 0;
@ -649,6 +662,8 @@ class Core : private Cpu
public:
uint32_t current_joypad[2];
uint32_t current_arkanoid_latch;
uint8_t current_arkanoid_fire;
Cart const *cart;
Mapper *mapper;
nes_state_t nes;
@ -697,27 +712,74 @@ class Core : private Cpu
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)
{
if ((addr & 0xFFFE) == 0x4016)
{
// For performance's sake, this counter is only kept on demand
#ifdef _QUICKERNES_DETECT_JOYPAD_READS
joypad_read_count++;
#endif
// For performance's sake, this counter is only kept on demand
#ifdef _QUICKERNES_DETECT_JOYPAD_READS
joypad_read_count++;
#endif
// to do: to aid with recording, doesn't emulate transparent latch,
// so a game that held strobe at 1 and read $4016 or $4017 would not get
// the current A status as occurs on a NES
if (joypad.w4016 & 1) return 0;
const uint8_t result = joypad.joypad_latches[addr & 1] & 1;
joypad.joypad_latches[addr & 1] >>= 1;
return result;
// If write flag is put into w4016, reading from it returns nothing
if (input_state.w4016 & 1) return 0;
// Proceed depending on input type
switch(_controllerType)
{
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;
}
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)
return impl->apu.read_status(clock());
@ -734,16 +796,18 @@ class Core : private Cpu
return;
}
// joypad strobe
// input_state strobe
if (addr == 0x4016)
{
// if strobe goes low, latch data
if (joypad.w4016 & 1 & ~data)
if (input_state.w4016 & 1 & ~data)
{
joypad.joypad_latches[0] = current_joypad[0];
joypad.joypad_latches[1] = current_joypad[1];
input_state.joypad_latches[0] = current_joypad[0];
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;
}

View File

@ -104,16 +104,16 @@ void Emu::set_palette_range(int begin, int end)
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;
host_pixels = NULL;
emu.emulate_frame(joypad1, joypad2);
emu.emulate_frame(joypad1, joypad2, arkanoid_latch, arkanoid_fire);
host_pixels = old_host_pixels;
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;
@ -144,7 +144,7 @@ const char *Emu::emulate_frame(uint32_t joypad1, uint32_t joypad2)
if (sound_buf->samples_avail())
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);
f = frame_;
@ -159,7 +159,7 @@ const char *Emu::emulate_frame(uint32_t joypad1, uint32_t joypad2)
else
{
emu.ppu.max_palette_size = 0;
emu.emulate_frame(joypad1, joypad2);
emu.emulate_frame(joypad1, joypad2, arkanoid_latch, arkanoid_fire);
}
return 0;

View File

@ -54,15 +54,17 @@ class Emu
void enableStateBlock(const std::string &block) { emu.enableStateBlock(block); };
void disableStateBlock(const std::string &block) { emu.disableStateBlock(block); };
void setControllerType(Core::controllerType_t type) { emu.setControllerType(type); }
// Basic emulation
// Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image
// 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.
// 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
static const uint16_t max_palette_size = 256;

View File

@ -8,7 +8,12 @@ typedef quickerNES::Emu emulator_t;
class NESInstance final : public NESInstanceBase
{
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(); };
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
{
if (_doRendering == true) _nes.emulate_frame(input.port1, input.port2);
if (_doRendering == false) _nes.emulate_skip_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, input.arkanoidLatch, input.arkanoidFire);
}
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