Skip to content

Commit

Permalink
Desktop: add a UI for editing AR cheats
Browse files Browse the repository at this point in the history
  • Loading branch information
Hydr8gon committed Jul 19, 2024
1 parent ad4cf42 commit cb7e14f
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 11 deletions.
31 changes: 29 additions & 2 deletions src/action_replay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ bool ActionReplay::loadCheats()
FILE *file = openFile("r");
if (!file) return false;
char data[512];
mutex.lock();

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

// 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.enabled = (cheat.name[cheat.name.size() - 2] == '+');
cheat.name = cheat.name.substr(0, cheat.name.size() - 3);
LOG("Loaded cheat: %s (%s)\n", cheat.name.c_str(), cheat.enabled ? "enabled" : "disabled");

Expand All @@ -72,13 +74,37 @@ bool ActionReplay::loadCheats()
}

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

bool ActionReplay::saveCheats()
{
// Attempt to open the cheat file
FILE *file = openFile("w");
if (!file) return false;
mutex.lock();

// Write cheats back to the file
for (uint32_t i = 0; i < cheats.size(); i++)
{
fprintf(file, "[%s]%c\n", cheats[i].name.c_str(), cheats[i].enabled ? '+' : '-');
for (uint32_t j = 0; j < cheats[i].code.size(); j += 2)
fprintf(file, "%08X %08X\n", cheats[i].code[j], cheats[i].code[j + 1]);
fprintf(file, "\n");
}

// Close the file after writing it
mutex.unlock();
fclose(file);
return true;
}

void ActionReplay::applyCheats()
{
// Execute the code of enabled cheats
mutex.lock();
for (uint32_t i = 0; i < cheats.size(); i++)
{
// Define registers for executing a cheat
Expand Down Expand Up @@ -330,4 +356,5 @@ void ActionReplay::applyCheats()
}
}
}
mutex.unlock();
}
6 changes: 4 additions & 2 deletions src/action_replay.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#define ACTION_REPLAY_H

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

Expand All @@ -36,18 +37,19 @@ struct ARCheat
class ActionReplay
{
public:
bool shouldRun = false;
std::vector<ARCheat> cheats;

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

bool loadCheats();
bool saveCheats();
void applyCheats();

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

Expand Down
188 changes: 188 additions & 0 deletions src/desktop/cheat_dialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
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 "cheat_dialog.h"

enum CheatEvent
{
ADD_CHEAT = 1,
REMOVE_CHEAT
};

wxBEGIN_EVENT_TABLE(CheatDialog, wxDialog)
EVT_CHECKLISTBOX(wxID_ANY, CheatDialog::checkCheat)
EVT_LISTBOX(wxID_ANY, CheatDialog::selectCheat)
EVT_BUTTON(ADD_CHEAT, CheatDialog::addCheat)
EVT_BUTTON(REMOVE_CHEAT, CheatDialog::removeCheat)
EVT_BUTTON(wxID_CANCEL, CheatDialog::cancel)
EVT_BUTTON(wxID_OK, CheatDialog::confirm)
wxEND_EVENT_TABLE()

CheatDialog::CheatDialog(Core *core): wxDialog(nullptr, wxID_ANY, "Action Replay Cheats"), core(core)
{
// Use the height of a button as a unit to scale pixel values based on DPI/font
wxButton *dummy = new wxButton(this, wxID_ANY, "");
int size = dummy->GetSize().y;
delete dummy;

// Set up the cheat name and code editors
wxBoxSizer *editSizer = new wxBoxSizer(wxVERTICAL);
nameEditor = new wxTextCtrl();
nameEditor->Create(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(size * 8, size));
editSizer->Add(nameEditor, 0, wxEXPAND | wxBOTTOM, size / 16);
codeEditor = new wxTextCtrl();
codeEditor->Create(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
editSizer->Add(codeEditor, 1, wxEXPAND | wxTOP, size / 16);

// Set up the cheat list and combine it with the editors
wxBoxSizer *cheatSizer = new wxBoxSizer(wxHORIZONTAL);
cheatList = new wxCheckListBox();
cheatList->Create(this, wxID_ANY, wxDefaultPosition, wxSize(size * 8, size * 12));
cheatSizer->Add(cheatList, 1, wxEXPAND | wxRIGHT, size / 16);
cheatSizer->Add(editSizer, 1, wxEXPAND | wxLEFT, size / 16);

// Disable editors and populate the cheat list
nameEditor->Disable();
codeEditor->Disable();
std::vector<ARCheat> &cheats = core->actionReplay.cheats;
for (uint32_t i = 0; i < cheats.size(); i++)
{
cheatList->Append(cheats[i].name);
cheatList->Check(i, cheats[i].enabled);
}

// Set up the add, remove, cancel, and confirm buttons
wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
buttonSizer->Add(new wxButton(this, ADD_CHEAT, "Add"), 0, wxRIGHT, size / 16);
buttonSizer->Add(new wxButton(this, REMOVE_CHEAT, "Remove"), 0, wxLEFT, size / 16);
buttonSizer->Add(new wxStaticText(this, wxID_ANY, ""), 1);
buttonSizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxRIGHT, size / 16);
buttonSizer->Add(new wxButton(this, wxID_OK, "Confirm"), 0, wxLEFT, size / 16);

// Combine all of the contents
wxBoxSizer *contents = new wxBoxSizer(wxVERTICAL);
contents->Add(cheatSizer, 1, wxEXPAND | wxBOTTOM, size / 16);
contents->Add(buttonSizer, 0, wxEXPAND | wxTOP, size / 16);

// Add a final border around everything
wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(contents, 1, wxEXPAND | wxALL, size / 8);
SetSizer(sizer);

// Size the window to fit the contents and prevent resizing
sizer->Fit(this);
SetMinSize(GetSize());
SetMaxSize(GetSize());
}

void CheatDialog::updateCheat()
{
// Get the current cheat and update its name
ARCheat *cheat = &core->actionReplay.cheats[curCheat];
cheatList->SetString(curCheat, cheat->name = nameEditor->GetValue());
cheat->code.clear();

// Parse the cheat's code from the editor string
std::string code = codeEditor->GetValue().ToStdString();
for (size_t i = 0, p; i < code.size(); i = p + 1)
{
p = code.find_first_of(" \n", i);
if (p == std::string::npos) p = code.size();
cheat->code.push_back(strtol(code.substr(i, p).c_str(), nullptr, 16));
}

// Ensure the code's word count is a multiple of 2
if (cheat->code.size() & 0x1)
cheat->code.push_back(0);
}

void CheatDialog::checkCheat(wxCommandEvent &event)
{
// Enable or disable a cheat
ARCheat &cheat = core->actionReplay.cheats[event.GetInt()];
cheat.enabled = !cheat.enabled;
}

void CheatDialog::selectCheat(wxCommandEvent &event)
{
// Select a new cheat and put its name in the editor
if (curCheat >= 0) updateCheat();
ARCheat *cheat = &core->actionReplay.cheats[curCheat = event.GetInt()];
nameEditor->SetValue(cheat->name);
codeEditor->Clear();

// Write the code to the editor as a string
for (uint32_t i = 0; i < cheat->code.size(); i += 2)
{
char line[19];
sprintf(line, "%08X %08X\n", cheat->code[i], cheat->code[i + 1]);
codeEditor->AppendText(line);
}

// Enable the cheat editors
nameEditor->Enable();
codeEditor->Enable();
}

void CheatDialog::addCheat(wxCommandEvent &event)
{
// Create a new cheat
ARCheat cheat;
cheatList->Append(cheat.name = "New Cheat");
cheat.enabled = false;
core->actionReplay.cheats.push_back(cheat);
}

void CheatDialog::removeCheat(wxCommandEvent &event)
{
// Remove a cheat and reset the list
if (curCheat < 0) return;
std::vector<ARCheat> &cheats = core->actionReplay.cheats;
cheats.erase(cheats.begin() + curCheat);
cheatList->Clear();

// Repopulate the cheat list
for (uint32_t i = 0; i < cheats.size(); i++)
{
cheatList->Append(cheats[i].name);
cheatList->Check(i, cheats[i].enabled);
}

// Reset and disable the editors
curCheat = -1;
nameEditor->Clear();
codeEditor->Clear();
nameEditor->Disable();
codeEditor->Disable();
}

void CheatDialog::cancel(wxCommandEvent &event)
{
// Reload cheats to discard changes
core->actionReplay.loadCheats();
event.Skip(true);
}

void CheatDialog::confirm(wxCommandEvent &event)
{
// Update the current cheat and save changes
if (curCheat >= 0) updateCheat();
core->actionReplay.saveCheats();
event.Skip(true);
}
48 changes: 48 additions & 0 deletions src/desktop/cheat_dialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
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 CHEAT_DIALOG_H
#define CHEAT_DIALOG_H

#include "noo_frame.h"

class CheatDialog: public wxDialog
{
public:
CheatDialog(Core *core);

private:
Core *core;
wxCheckListBox *cheatList;
wxTextCtrl *nameEditor;
wxTextCtrl *codeEditor;
int curCheat = -1;

void updateCheat();
void checkCheat(wxCommandEvent &event);
void selectCheat(wxCommandEvent &event);
void addCheat(wxCommandEvent &event);
void removeCheat(wxCommandEvent &event);
void cancel(wxCommandEvent &event);
void confirm(wxCommandEvent &event);

wxDECLARE_EVENT_TABLE();
};

#endif // CHEAT_DIALOG_H
Loading

0 comments on commit cb7e14f

Please sign in to comment.