Skip to content

Commit cb7e14f

Browse files
committed
Desktop: add a UI for editing AR cheats
1 parent ad4cf42 commit cb7e14f

7 files changed

+285
-11
lines changed

src/action_replay.cpp

+29-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ bool ActionReplay::loadCheats()
4848
FILE *file = openFile("r");
4949
if (!file) return false;
5050
char data[512];
51+
mutex.lock();
5152

52-
// Load cheats from the file
53+
// Reload cheats from the file
54+
cheats.clear();
5355
while (fgets(data, 512, file) != nullptr)
5456
{
5557
// Create a new cheat when one is found
@@ -59,7 +61,7 @@ bool ActionReplay::loadCheats()
5961

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

@@ -72,13 +74,37 @@ bool ActionReplay::loadCheats()
7274
}
7375

7476
// Close the file after reading it
77+
mutex.unlock();
78+
fclose(file);
79+
return true;
80+
}
81+
82+
bool ActionReplay::saveCheats()
83+
{
84+
// Attempt to open the cheat file
85+
FILE *file = openFile("w");
86+
if (!file) return false;
87+
mutex.lock();
88+
89+
// Write cheats back to the file
90+
for (uint32_t i = 0; i < cheats.size(); i++)
91+
{
92+
fprintf(file, "[%s]%c\n", cheats[i].name.c_str(), cheats[i].enabled ? '+' : '-');
93+
for (uint32_t j = 0; j < cheats[i].code.size(); j += 2)
94+
fprintf(file, "%08X %08X\n", cheats[i].code[j], cheats[i].code[j + 1]);
95+
fprintf(file, "\n");
96+
}
97+
98+
// Close the file after writing it
99+
mutex.unlock();
75100
fclose(file);
76101
return true;
77102
}
78103

79104
void ActionReplay::applyCheats()
80105
{
81106
// Execute the code of enabled cheats
107+
mutex.lock();
82108
for (uint32_t i = 0; i < cheats.size(); i++)
83109
{
84110
// Define registers for executing a cheat
@@ -330,4 +356,5 @@ void ActionReplay::applyCheats()
330356
}
331357
}
332358
}
359+
mutex.unlock();
333360
}

src/action_replay.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#define ACTION_REPLAY_H
2222

2323
#include <cstdint>
24+
#include <mutex>
2425
#include <string>
2526
#include <vector>
2627

@@ -36,18 +37,19 @@ struct ARCheat
3637
class ActionReplay
3738
{
3839
public:
39-
bool shouldRun = false;
40+
std::vector<ARCheat> cheats;
4041

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

4546
bool loadCheats();
47+
bool saveCheats();
4648
void applyCheats();
4749

4850
private:
4951
Core *core;
50-
std::vector<ARCheat> cheats;
52+
std::mutex mutex;
5153
std::string path;
5254
int fd = -1;
5355

src/desktop/cheat_dialog.cpp

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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 "cheat_dialog.h"
21+
22+
enum CheatEvent
23+
{
24+
ADD_CHEAT = 1,
25+
REMOVE_CHEAT
26+
};
27+
28+
wxBEGIN_EVENT_TABLE(CheatDialog, wxDialog)
29+
EVT_CHECKLISTBOX(wxID_ANY, CheatDialog::checkCheat)
30+
EVT_LISTBOX(wxID_ANY, CheatDialog::selectCheat)
31+
EVT_BUTTON(ADD_CHEAT, CheatDialog::addCheat)
32+
EVT_BUTTON(REMOVE_CHEAT, CheatDialog::removeCheat)
33+
EVT_BUTTON(wxID_CANCEL, CheatDialog::cancel)
34+
EVT_BUTTON(wxID_OK, CheatDialog::confirm)
35+
wxEND_EVENT_TABLE()
36+
37+
CheatDialog::CheatDialog(Core *core): wxDialog(nullptr, wxID_ANY, "Action Replay Cheats"), core(core)
38+
{
39+
// Use the height of a button as a unit to scale pixel values based on DPI/font
40+
wxButton *dummy = new wxButton(this, wxID_ANY, "");
41+
int size = dummy->GetSize().y;
42+
delete dummy;
43+
44+
// Set up the cheat name and code editors
45+
wxBoxSizer *editSizer = new wxBoxSizer(wxVERTICAL);
46+
nameEditor = new wxTextCtrl();
47+
nameEditor->Create(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(size * 8, size));
48+
editSizer->Add(nameEditor, 0, wxEXPAND | wxBOTTOM, size / 16);
49+
codeEditor = new wxTextCtrl();
50+
codeEditor->Create(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
51+
editSizer->Add(codeEditor, 1, wxEXPAND | wxTOP, size / 16);
52+
53+
// Set up the cheat list and combine it with the editors
54+
wxBoxSizer *cheatSizer = new wxBoxSizer(wxHORIZONTAL);
55+
cheatList = new wxCheckListBox();
56+
cheatList->Create(this, wxID_ANY, wxDefaultPosition, wxSize(size * 8, size * 12));
57+
cheatSizer->Add(cheatList, 1, wxEXPAND | wxRIGHT, size / 16);
58+
cheatSizer->Add(editSizer, 1, wxEXPAND | wxLEFT, size / 16);
59+
60+
// Disable editors and populate the cheat list
61+
nameEditor->Disable();
62+
codeEditor->Disable();
63+
std::vector<ARCheat> &cheats = core->actionReplay.cheats;
64+
for (uint32_t i = 0; i < cheats.size(); i++)
65+
{
66+
cheatList->Append(cheats[i].name);
67+
cheatList->Check(i, cheats[i].enabled);
68+
}
69+
70+
// Set up the add, remove, cancel, and confirm buttons
71+
wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
72+
buttonSizer->Add(new wxButton(this, ADD_CHEAT, "Add"), 0, wxRIGHT, size / 16);
73+
buttonSizer->Add(new wxButton(this, REMOVE_CHEAT, "Remove"), 0, wxLEFT, size / 16);
74+
buttonSizer->Add(new wxStaticText(this, wxID_ANY, ""), 1);
75+
buttonSizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxRIGHT, size / 16);
76+
buttonSizer->Add(new wxButton(this, wxID_OK, "Confirm"), 0, wxLEFT, size / 16);
77+
78+
// Combine all of the contents
79+
wxBoxSizer *contents = new wxBoxSizer(wxVERTICAL);
80+
contents->Add(cheatSizer, 1, wxEXPAND | wxBOTTOM, size / 16);
81+
contents->Add(buttonSizer, 0, wxEXPAND | wxTOP, size / 16);
82+
83+
// Add a final border around everything
84+
wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
85+
sizer->Add(contents, 1, wxEXPAND | wxALL, size / 8);
86+
SetSizer(sizer);
87+
88+
// Size the window to fit the contents and prevent resizing
89+
sizer->Fit(this);
90+
SetMinSize(GetSize());
91+
SetMaxSize(GetSize());
92+
}
93+
94+
void CheatDialog::updateCheat()
95+
{
96+
// Get the current cheat and update its name
97+
ARCheat *cheat = &core->actionReplay.cheats[curCheat];
98+
cheatList->SetString(curCheat, cheat->name = nameEditor->GetValue());
99+
cheat->code.clear();
100+
101+
// Parse the cheat's code from the editor string
102+
std::string code = codeEditor->GetValue().ToStdString();
103+
for (size_t i = 0, p; i < code.size(); i = p + 1)
104+
{
105+
p = code.find_first_of(" \n", i);
106+
if (p == std::string::npos) p = code.size();
107+
cheat->code.push_back(strtol(code.substr(i, p).c_str(), nullptr, 16));
108+
}
109+
110+
// Ensure the code's word count is a multiple of 2
111+
if (cheat->code.size() & 0x1)
112+
cheat->code.push_back(0);
113+
}
114+
115+
void CheatDialog::checkCheat(wxCommandEvent &event)
116+
{
117+
// Enable or disable a cheat
118+
ARCheat &cheat = core->actionReplay.cheats[event.GetInt()];
119+
cheat.enabled = !cheat.enabled;
120+
}
121+
122+
void CheatDialog::selectCheat(wxCommandEvent &event)
123+
{
124+
// Select a new cheat and put its name in the editor
125+
if (curCheat >= 0) updateCheat();
126+
ARCheat *cheat = &core->actionReplay.cheats[curCheat = event.GetInt()];
127+
nameEditor->SetValue(cheat->name);
128+
codeEditor->Clear();
129+
130+
// Write the code to the editor as a string
131+
for (uint32_t i = 0; i < cheat->code.size(); i += 2)
132+
{
133+
char line[19];
134+
sprintf(line, "%08X %08X\n", cheat->code[i], cheat->code[i + 1]);
135+
codeEditor->AppendText(line);
136+
}
137+
138+
// Enable the cheat editors
139+
nameEditor->Enable();
140+
codeEditor->Enable();
141+
}
142+
143+
void CheatDialog::addCheat(wxCommandEvent &event)
144+
{
145+
// Create a new cheat
146+
ARCheat cheat;
147+
cheatList->Append(cheat.name = "New Cheat");
148+
cheat.enabled = false;
149+
core->actionReplay.cheats.push_back(cheat);
150+
}
151+
152+
void CheatDialog::removeCheat(wxCommandEvent &event)
153+
{
154+
// Remove a cheat and reset the list
155+
if (curCheat < 0) return;
156+
std::vector<ARCheat> &cheats = core->actionReplay.cheats;
157+
cheats.erase(cheats.begin() + curCheat);
158+
cheatList->Clear();
159+
160+
// Repopulate the cheat list
161+
for (uint32_t i = 0; i < cheats.size(); i++)
162+
{
163+
cheatList->Append(cheats[i].name);
164+
cheatList->Check(i, cheats[i].enabled);
165+
}
166+
167+
// Reset and disable the editors
168+
curCheat = -1;
169+
nameEditor->Clear();
170+
codeEditor->Clear();
171+
nameEditor->Disable();
172+
codeEditor->Disable();
173+
}
174+
175+
void CheatDialog::cancel(wxCommandEvent &event)
176+
{
177+
// Reload cheats to discard changes
178+
core->actionReplay.loadCheats();
179+
event.Skip(true);
180+
}
181+
182+
void CheatDialog::confirm(wxCommandEvent &event)
183+
{
184+
// Update the current cheat and save changes
185+
if (curCheat >= 0) updateCheat();
186+
core->actionReplay.saveCheats();
187+
event.Skip(true);
188+
}

src/desktop/cheat_dialog.h

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 CHEAT_DIALOG_H
21+
#define CHEAT_DIALOG_H
22+
23+
#include "noo_frame.h"
24+
25+
class CheatDialog: public wxDialog
26+
{
27+
public:
28+
CheatDialog(Core *core);
29+
30+
private:
31+
Core *core;
32+
wxCheckListBox *cheatList;
33+
wxTextCtrl *nameEditor;
34+
wxTextCtrl *codeEditor;
35+
int curCheat = -1;
36+
37+
void updateCheat();
38+
void checkCheat(wxCommandEvent &event);
39+
void selectCheat(wxCommandEvent &event);
40+
void addCheat(wxCommandEvent &event);
41+
void removeCheat(wxCommandEvent &event);
42+
void cancel(wxCommandEvent &event);
43+
void confirm(wxCommandEvent &event);
44+
45+
wxDECLARE_EVENT_TABLE();
46+
};
47+
48+
#endif // CHEAT_DIALOG_H

0 commit comments

Comments
 (0)