Skip to content

Commit ad4cf42

Browse files
committed
Implement an Action Replay cheat backend
1 parent 7786f71 commit ad4cf42

12 files changed

+457
-29
lines changed

src/action_replay.cpp

+333
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
/*
2+
Copyright 2019-2024 Hydr8gon
3+
4+
This file is part of NooDS.
5+
6+
NooDS is free software: you can redistribute it and/or modify it
7+
under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
NooDS is distributed in the hope that it will be useful, but
12+
WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with NooDS. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
#include "action_replay.h"
21+
#include "core.h"
22+
23+
void ActionReplay::setPath(std::string path)
24+
{
25+
// Set the cheat path
26+
this->path = path;
27+
}
28+
29+
void ActionReplay::setFd(int fd)
30+
{
31+
// Set the cheat descriptor
32+
this->fd = fd;
33+
}
34+
35+
FILE *ActionReplay::openFile(const char *mode)
36+
{
37+
// Open the cheat file if one is set
38+
if (fd != -1)
39+
return fdopen(dup(fd), mode);
40+
else if (path != "")
41+
return fopen(path.c_str(), mode);
42+
return nullptr;
43+
}
44+
45+
bool ActionReplay::loadCheats()
46+
{
47+
// Attempt to open the cheat file
48+
FILE *file = openFile("r");
49+
if (!file) return false;
50+
char data[512];
51+
52+
// Load cheats from the file
53+
while (fgets(data, 512, file) != nullptr)
54+
{
55+
// Create a new cheat when one is found
56+
if (data[0] != '[') continue;
57+
cheats.push_back(ARCheat());
58+
ARCheat &cheat = cheats[cheats.size() - 1];
59+
60+
// Parse the cheat name and enabled state from the file
61+
cheat.name = &data[1];
62+
if ((cheat.enabled = (cheat.name[cheat.name.size() - 2] == '+'))) shouldRun = true;
63+
cheat.name = cheat.name.substr(0, cheat.name.size() - 3);
64+
LOG("Loaded cheat: %s (%s)\n", cheat.name.c_str(), cheat.enabled ? "enabled" : "disabled");
65+
66+
// Load the cheat code up until an empty line
67+
while (fgets(data, 512, file) != nullptr && data[0] != '\n')
68+
{
69+
cheat.code.push_back(strtol(&data[0], nullptr, 16));
70+
cheat.code.push_back(strtol(&data[8], nullptr, 16));
71+
}
72+
}
73+
74+
// Close the file after reading it
75+
fclose(file);
76+
return true;
77+
}
78+
79+
void ActionReplay::applyCheats()
80+
{
81+
// Execute the code of enabled cheats
82+
for (uint32_t i = 0; i < cheats.size(); i++)
83+
{
84+
// Define registers for executing a cheat
85+
if (!cheats[i].enabled) continue;
86+
uint32_t offset = 0;
87+
uint32_t dataReg = 0;
88+
uint32_t counter = 0;
89+
uint32_t loopCount = 0;
90+
uint32_t loopAddress = 0;
91+
bool condFlag = false;
92+
93+
// Loop through lines of a cheat's code
94+
for (uint32_t address = 0; address < cheats[i].code.size(); address += 2)
95+
{
96+
// Check the condition flag
97+
uint32_t *line = &cheats[i].code[address];
98+
if (condFlag)
99+
{
100+
// Handle adjustments that happen regardless of condition
101+
uint8_t op = (line[0] >> 24);
102+
if ((op >> 4) == 0xE) // Parameter copy
103+
address += ((line[1] + 0x7) & ~0x7) >> 2;
104+
else if (op == 0xC5) // If counter
105+
counter++;
106+
107+
// Skip non-control opcodes if the flag is set
108+
if (op != 0xD0 && op != 0xD1 && op != 0xD2)
109+
continue;
110+
}
111+
112+
// Interpret a line of the code
113+
switch (line[0] >> 28)
114+
{
115+
case 0x0: // Write word
116+
// Write a word to memory
117+
core->memory.write<uint32_t>(1, (line[0] & 0xFFFFFFF) + offset, line[1]);
118+
continue;
119+
120+
case 0x1: // Write half
121+
// Write a half-word to memory
122+
core->memory.write<uint16_t>(1, (line[0] & 0xFFFFFFF) + offset, line[1]);
123+
continue;
124+
125+
case 0x2: // Write byte
126+
// Write a byte to memory
127+
core->memory.write<uint8_t>(1, (line[0] & 0xFFFFFFF) + offset, line[1]);
128+
continue;
129+
130+
case 0x3: // If greater than word
131+
{
132+
// Set the condition flag if a word isn't greater than a memory value
133+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
134+
condFlag = (line[1] <= core->memory.read<uint32_t>(1, addr));
135+
continue;
136+
}
137+
138+
case 0x4: // If less than word
139+
{
140+
// Set the condition flag if a word isn't less than a memory value
141+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
142+
condFlag = (line[1] >= core->memory.read<uint32_t>(1, addr));
143+
continue;
144+
}
145+
146+
case 0x5: // If equal to word
147+
{
148+
// Set the condition flag if a word isn't equal to a memory value
149+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
150+
condFlag = (line[1] != core->memory.read<uint32_t>(1, addr));
151+
continue;
152+
}
153+
154+
case 0x6: // If not equal to word
155+
{
156+
// Set the condition flag if a word is equal to a memory value
157+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
158+
condFlag = (line[1] == core->memory.read<uint32_t>(1, addr));
159+
continue;
160+
}
161+
162+
case 0x7: // If greater than half
163+
{
164+
// Set the condition flag if a half-word isn't greater than a masked memory value
165+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
166+
condFlag = (line[1] & 0xFFFF) <= (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
167+
continue;
168+
}
169+
170+
case 0x8: // If less than half
171+
{
172+
// Set the condition flag if a half-word isn't less than a masked memory value
173+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
174+
condFlag = (line[1] & 0xFFFF) >= (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
175+
continue;
176+
}
177+
178+
case 0x9: // If equal to half
179+
{
180+
// Set the condition flag if a half-word isn't equal to a masked memory value
181+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
182+
condFlag = (line[1] & 0xFFFF) != (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
183+
continue;
184+
}
185+
186+
case 0xA: // If not equal to half
187+
{
188+
// Set the condition flag if a half-word is equal to a masked memory value
189+
uint32_t addr = (line[0] & 0xFFFFFFF) ? (line[0] & 0xFFFFFFF) : offset;
190+
condFlag = (line[1] & 0xFFFF) == (core->memory.read<uint16_t>(1, addr) & ~(line[1] >> 16));
191+
continue;
192+
}
193+
194+
case 0xB: // Load offset
195+
// Set the offset to a word from memory
196+
offset = core->memory.read<uint32_t>(1, (line[0] & 0xFFFFFFF) + offset);
197+
continue;
198+
199+
case 0xC:
200+
switch (line[0] >> 24)
201+
{
202+
case 0xC0: // For loop
203+
// Set the loop count and address to loop to
204+
loopCount = line[1];
205+
loopAddress = address;
206+
continue;
207+
208+
case 0xC5: // If counter
209+
// Set the condition flag if the masked counter isn't equal to a half-word
210+
condFlag = (++counter & line[1] & 0xFFFF) != (line[1] >> 16);
211+
continue;
212+
213+
case 0xC6: // Write offset
214+
// Write the offset value to a memory word
215+
core->memory.write<uint32_t>(1, line[1], offset);
216+
continue;
217+
}
218+
goto invalid;
219+
220+
case 0xD:
221+
switch (line[0] >> 24)
222+
{
223+
case 0xD0: // End if
224+
// Clear the condition flag
225+
condFlag = false;
226+
continue;
227+
228+
case 0xD1: // Next loop
229+
// Jump to the loop address until the loop count runs out
230+
if (loopCount)
231+
{
232+
loopCount--;
233+
address = loopAddress;
234+
continue;
235+
}
236+
condFlag = false;
237+
continue;
238+
239+
case 0xD2: // Next loop and flush
240+
// Jump to the loop address and reset registers after looping
241+
if (loopCount)
242+
{
243+
loopCount--;
244+
address = loopAddress;
245+
continue;
246+
}
247+
offset = 0;
248+
dataReg = 0;
249+
condFlag = false;
250+
continue;
251+
252+
case 0xD3: // Set offset
253+
// Set the offset to a word
254+
offset = line[1];
255+
continue;
256+
257+
case 0xD4: // Add data
258+
// Add a word to the data register
259+
dataReg += line[1];
260+
continue;
261+
262+
case 0xD5: // Set data
263+
// Set the data register to a word
264+
dataReg = line[1];
265+
continue;
266+
267+
case 0xD6: // Write data word
268+
// Write the data register to a memory word and increment the offset
269+
core->memory.write<uint32_t>(1, line[1] + offset, dataReg);
270+
offset += 4;
271+
continue;
272+
273+
case 0xD7: // Write data half
274+
// Write the data register to a memory half-word and increment the offset
275+
core->memory.write<uint16_t>(1, line[1] + offset, dataReg);
276+
offset += 2;
277+
continue;
278+
279+
case 0xD8: // Write data byte
280+
// Write the data register to a memory byte and increment the offset
281+
core->memory.write<uint8_t>(1, line[1] + offset, dataReg);
282+
offset += 1;
283+
continue;
284+
285+
case 0xD9: // Read data word
286+
// Set the data register to a word from memory
287+
dataReg = core->memory.read<uint32_t>(1, line[1] + offset);
288+
continue;
289+
290+
case 0xDA: // Read data half
291+
// Set the data register to a half-word from memory
292+
dataReg = core->memory.read<uint16_t>(1, line[1] + offset);
293+
continue;
294+
295+
case 0xDB: // Read data byte
296+
// Set the data register to a byte from memory
297+
dataReg = core->memory.read<uint8_t>(1, line[1] + offset);
298+
continue;
299+
300+
case 0xDC: // Add offset
301+
// Add a word to the offset
302+
offset += line[1];
303+
continue;
304+
}
305+
goto invalid;
306+
307+
case 0xE: // Parameter copy
308+
// Copy parameter bytes to memory and jump to the next opcode
309+
for (uint32_t j = 0; j < line[1]; j++)
310+
{
311+
uint8_t value = line[(j >> 2) + 2] >> ((j & 0x3) * 8);
312+
core->memory.write<uint8_t>(1, (line[0] & 0xFFFFFFF) + offset + j, value);
313+
}
314+
address += ((line[1] + 0x7) & ~0x7) >> 2;
315+
continue;
316+
317+
case 0xF: // Memory copy
318+
// Copy bytes from one memory location to another
319+
for (uint32_t j = 0; j < line[1]; j++)
320+
{
321+
uint8_t value = core->memory.read<uint8_t>(1, offset + j);
322+
core->memory.write<uint8_t>(1, (line[0] & 0xFFFFFFF) + j, value);
323+
}
324+
continue;
325+
326+
default:
327+
invalid:
328+
LOG("Invalid AR code: %08X %08X\n", line[0], line[1]);
329+
continue;
330+
}
331+
}
332+
}
333+
}

src/action_replay.h

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
Copyright 2019-2024 Hydr8gon
3+
4+
This file is part of NooDS.
5+
6+
NooDS is free software: you can redistribute it and/or modify it
7+
under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
NooDS is distributed in the hope that it will be useful, but
12+
WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with NooDS. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
#ifndef ACTION_REPLAY_H
21+
#define ACTION_REPLAY_H
22+
23+
#include <cstdint>
24+
#include <string>
25+
#include <vector>
26+
27+
class Core;
28+
29+
struct ARCheat
30+
{
31+
std::string name;
32+
std::vector<uint32_t> code;
33+
bool enabled;
34+
};
35+
36+
class ActionReplay
37+
{
38+
public:
39+
bool shouldRun = false;
40+
41+
ActionReplay(Core *core): core(core) {}
42+
void setPath(std::string path);
43+
void setFd(int fd);
44+
45+
bool loadCheats();
46+
void applyCheats();
47+
48+
private:
49+
Core *core;
50+
std::vector<ARCheat> cheats;
51+
std::string path;
52+
int fd = -1;
53+
54+
FILE *openFile(const char *mode);
55+
};
56+
57+
#endif // ACTION_REPLAY_H

src/android/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ add_library(noods-core SHARED
77
cpp/interface.cpp
88
../common/nds_icon.cpp
99
../common/screen_layout.cpp
10+
../action_replay.cpp
1011
../bios.cpp
1112
../cartridge.cpp
1213
../core.cpp

0 commit comments

Comments
 (0)