Supporting arkanoidNES
This commit is contained in:
parent
9f164b4a58
commit
29beac9732
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue