Skip to content

Commit

Permalink
Implement an Action Replay cheat backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Hydr8gon committed Jul 15, 2024
1 parent 7786f71 commit ad4cf42
Show file tree
Hide file tree
Showing 12 changed files with 457 additions and 29 deletions.
333 changes: 333 additions & 0 deletions src/action_replay.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
/*
Copyright 2019-2024 Hydr8gon
This file is part of NooDS.
NooDS 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 3 of the License, or
(at your option) any later version.
NooDS 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 NooDS. If not, see <https://www.gnu.org/licenses/>.
*/

#include "action_replay.h"
#include "core.h"

void ActionReplay::setPath(std::string path)
{
// Set the cheat path
this->path = path;
}

void ActionReplay::setFd(int fd)
{
// Set the cheat descriptor
this->fd = fd;
}

FILE *ActionReplay::openFile(const char *mode)
{
// Open the cheat file if one is set
if (fd != -1)
return fdopen(dup(fd), mode);
else if (path != "")
return fopen(path.c_str(), mode);
return nullptr;
}

bool ActionReplay::loadCheats()
{
// Attempt to open the cheat file
FILE *file = openFile("r");
if (!file) return false;
char data[512];

// Load cheats from the file
while (fgets(data, 512, file) != nullptr)
{
// Create a new cheat when one is found
if (data[0] != '[') continue;
cheats.push_back(ARCheat());
ARCheat &cheat = cheats[cheats.size() - 1];

// Parse the cheat name and enabled state from the file
cheat.name = &data[1];
if ((cheat.enabled = (cheat.name[cheat.name.size() - 2] == '+'))) shouldRun = true;
cheat.name = cheat.name.substr(0, cheat.name.size() - 3);
LOG("Loaded cheat: %s (%s)\n", cheat.name.c_str(), cheat.enabled ? "enabled" : "disabled");

// Load the cheat code up until an empty line
while (fgets(data, 512, file) != nullptr && data[0] != '\n')
{
cheat.code.push_back(strtol(&data[0], nullptr, 16));
cheat.code.push_back(strtol(&data[8], nullptr, 16));
}
}

// Close the file after reading it
fclose(file);
return true;
}

void ActionReplay::applyCheats()
{
// Execute the code of enabled cheats
for (uint32_t i = 0; i < cheats.size(); i++)
{
// Define registers for executing a cheat
if (!cheats[i].enabled) continue;
uint32_t offset = 0;
uint32_t dataReg = 0;
uint32_t counter = 0;
uint32_t loopCount = 0;
uint32_t loopAddress = 0;
bool condFlag = false;

// Loop through lines of a cheat's code
for (uint32_t address = 0; address < cheats[i].code.size(); address += 2)
{
// Check the condition flag
uint32_t *line = &cheats[i].code[address];
if (condFlag)
{
// Handle adjustments that happen regardless of condition
uint8_t op = (line[0] >> 24);
if ((op >> 4) == 0xE) // Parameter copy
address += ((line[1] + 0x7) & ~0x7) >> 2;
else if (op == 0xC5) // If counter
counter++;

// Skip non-control opcodes if the flag is set
if (op != 0xD0 && op != 0xD1 && op != 0xD2)
continue;
}

// Interpret a line of the code
switch (line[0] >> 28)
{
case 0x0: // Write word
// Write a word to memory
core->memory.write<uint32_t>(1, (line[0] & 0xFFFFFFF) + offset, line[1]);
continue;

case 0x1: // Write half
// Write a half-word to memory
core->memory.write<uint16_t>(1, (line[0] & 0xFFFFFFF) + offset, line[1]);
continue;

case 0x2: // Write byte
// Write a byte to memory
core->memory.write<uint8_t>(1, (line[0] & 0xFFFFFFF) + offset, line[1]);
continue;

case 0x3: // If greater than word
{
// Set the condition flag if a word isn't greater than a memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] <= core->memory.read<uint32_t>(1, addr));
continue;
}

case 0x4: // If less than word
{
// Set the condition flag if a word isn't less than a memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] >= core->memory.read<uint32_t>(1, addr));
continue;
}

case 0x5: // If equal to word
{
// Set the condition flag if a word isn't equal to a memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] != core->memory.read<uint32_t>(1, addr));
continue;
}

case 0x6: // If not equal to word
{
// Set the condition flag if a word is equal to a memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] == core->memory.read<uint32_t>(1, addr));
continue;
}

case 0x7: // If greater than half
{
// Set the condition flag if a half-word isn't greater than a masked memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] & 0xFFFF) <= (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
continue;
}

case 0x8: // If less than half
{
// Set the condition flag if a half-word isn't less than a masked memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] & 0xFFFF) >= (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
continue;
}

case 0x9: // If equal to half
{
// Set the condition flag if a half-word isn't equal to a masked memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] & 0xFFFF) != (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
continue;
}

case 0xA: // If not equal to half
{
// Set the condition flag if a half-word is equal to a masked memory value
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
condFlag = (line[1] & 0xFFFF) == (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
continue;
}

case 0xB: // Load offset
// Set the offset to a word from memory
offset = core->memory.read<uint32_t>(1, (line[0] & 0xFFFFFFF) + offset);
continue;

case 0xC:
switch (line[0] >> 24)
{
case 0xC0: // For loop
// Set the loop count and address to loop to
loopCount = line[1];
loopAddress = address;
continue;

case 0xC5: // If counter
// Set the condition flag if the masked counter isn't equal to a half-word
condFlag = (++counter & line[1] & 0xFFFF) != (line[1] >> 16);
continue;

case 0xC6: // Write offset
// Write the offset value to a memory word
core->memory.write<uint32_t>(1, line[1], offset);
continue;
}
goto invalid;

case 0xD:
switch (line[0] >> 24)
{
case 0xD0: // End if
// Clear the condition flag
condFlag = false;
continue;

case 0xD1: // Next loop
// Jump to the loop address until the loop count runs out
if (loopCount)
{
loopCount--;
address = loopAddress;
continue;
}
condFlag = false;
continue;

case 0xD2: // Next loop and flush
// Jump to the loop address and reset registers after looping
if (loopCount)
{
loopCount--;
address = loopAddress;
continue;
}
offset = 0;
dataReg = 0;
condFlag = false;
continue;

case 0xD3: // Set offset
// Set the offset to a word
offset = line[1];
continue;

case 0xD4: // Add data
// Add a word to the data register
dataReg += line[1];
continue;

case 0xD5: // Set data
// Set the data register to a word
dataReg = line[1];
continue;

case 0xD6: // Write data word
// Write the data register to a memory word and increment the offset
core->memory.write<uint32_t>(1, line[1] + offset, dataReg);
offset += 4;
continue;

case 0xD7: // Write data half
// Write the data register to a memory half-word and increment the offset
core->memory.write<uint16_t>(1, line[1] + offset, dataReg);
offset += 2;
continue;

case 0xD8: // Write data byte
// Write the data register to a memory byte and increment the offset
core->memory.write<uint8_t>(1, line[1] + offset, dataReg);
offset += 1;
continue;

case 0xD9: // Read data word
// Set the data register to a word from memory
dataReg = core->memory.read<uint32_t>(1, line[1] + offset);
continue;

case 0xDA: // Read data half
// Set the data register to a half-word from memory
dataReg = core->memory.read<uint16_t>(1, line[1] + offset);
continue;

case 0xDB: // Read data byte
// Set the data register to a byte from memory
dataReg = core->memory.read<uint8_t>(1, line[1] + offset);
continue;

case 0xDC: // Add offset
// Add a word to the offset
offset += line[1];
continue;
}
goto invalid;

case 0xE: // Parameter copy
// Copy parameter bytes to memory and jump to the next opcode
for (uint32_t j = 0; j < line[1]; j++)
{
uint8_t value = line[(j >> 2) + 2] >> ((j & 0x3) * 8);
core->memory.write<uint8_t>(1, (line[0] & 0xFFFFFFF) + offset + j, value);
}
address += ((line[1] + 0x7) & ~0x7) >> 2;
continue;

case 0xF: // Memory copy
// Copy bytes from one memory location to another
for (uint32_t j = 0; j < line[1]; j++)
{
uint8_t value = core->memory.read<uint8_t>(1, offset + j);
core->memory.write<uint8_t>(1, (line[0] & 0xFFFFFFF) + j, value);
}
continue;

default:
invalid:
LOG("Invalid AR code: %08X %08X\n", line[0], line[1]);
continue;
}
}
}
}
57 changes: 57 additions & 0 deletions src/action_replay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
Copyright 2019-2024 Hydr8gon
This file is part of NooDS.
NooDS 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 3 of the License, or
(at your option) any later version.
NooDS 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 NooDS. If not, see <https://www.gnu.org/licenses/>.
*/

#ifndef ACTION_REPLAY_H
#define ACTION_REPLAY_H

#include <cstdint>
#include <string>
#include <vector>

class Core;

struct ARCheat
{
std::string name;
std::vector<uint32_t> code;
bool enabled;
};

class ActionReplay
{
public:
bool shouldRun = false;

ActionReplay(Core *core): core(core) {}
void setPath(std::string path);
void setFd(int fd);

bool loadCheats();
void applyCheats();

private:
Core *core;
std::vector<ARCheat> cheats;
std::string path;
int fd = -1;

FILE *openFile(const char *mode);
};

#endif // ACTION_REPLAY_H
1 change: 1 addition & 0 deletions src/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_library(noods-core SHARED
cpp/interface.cpp
../common/nds_icon.cpp
../common/screen_layout.cpp
../action_replay.cpp
../bios.cpp
../cartridge.cpp
../core.cpp
Expand Down
Loading

0 comments on commit ad4cf42

Please sign in to comment.