mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-01-14 10:55:14 +00:00
Enable patches on cli builds (#897)
Some checks are pending
Reuse / reuse (push) Waiting to run
Clang Format / clang-format (push) Waiting to run
Linux-Qt / build (push) Waiting to run
Linux / build (push) Waiting to run
macOS-Qt / build (push) Waiting to run
macOS / build (push) Waiting to run
Windows-Qt / build (push) Waiting to run
Windows / build (push) Waiting to run
Some checks are pending
Reuse / reuse (push) Waiting to run
Clang Format / clang-format (push) Waiting to run
Linux-Qt / build (push) Waiting to run
Linux / build (push) Waiting to run
macOS-Qt / build (push) Waiting to run
macOS / build (push) Waiting to run
Windows-Qt / build (push) Waiting to run
Windows / build (push) Waiting to run
* patch support for cli * fix mac build * format
This commit is contained in:
parent
de183d3b80
commit
ab201398b2
|
@ -376,6 +376,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||||
src/common/version.h
|
src/common/version.h
|
||||||
src/common/ntapi.h
|
src/common/ntapi.h
|
||||||
src/common/ntapi.cpp
|
src/common/ntapi.cpp
|
||||||
|
src/common/memory_patcher.h
|
||||||
|
src/common/memory_patcher.cpp
|
||||||
src/common/scm_rev.cpp
|
src/common/scm_rev.cpp
|
||||||
src/common/scm_rev.h
|
src/common/scm_rev.h
|
||||||
)
|
)
|
||||||
|
@ -628,8 +630,6 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
|
||||||
src/qt_gui/about_dialog.ui
|
src/qt_gui/about_dialog.ui
|
||||||
src/qt_gui/cheats_patches.cpp
|
src/qt_gui/cheats_patches.cpp
|
||||||
src/qt_gui/cheats_patches.h
|
src/qt_gui/cheats_patches.h
|
||||||
src/qt_gui/memory_patcher.cpp
|
|
||||||
src/qt_gui/memory_patcher.h
|
|
||||||
src/qt_gui/main_window_ui.h
|
src/qt_gui/main_window_ui.h
|
||||||
src/qt_gui/main_window.cpp
|
src/qt_gui/main_window.cpp
|
||||||
src/qt_gui/main_window.h
|
src/qt_gui/main_window.h
|
||||||
|
|
|
@ -2,14 +2,20 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <codecvt>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <pugixml.hpp>
|
||||||
|
#ifdef ENABLE_QT_GUI
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QString>
|
||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
|
#endif
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "memory_patcher.h"
|
#include "memory_patcher.h"
|
||||||
|
@ -17,78 +23,169 @@
|
||||||
namespace MemoryPatcher {
|
namespace MemoryPatcher {
|
||||||
|
|
||||||
uintptr_t g_eboot_address;
|
uintptr_t g_eboot_address;
|
||||||
u64 g_eboot_image_size;
|
uint64_t g_eboot_image_size;
|
||||||
std::string g_game_serial;
|
std::string g_game_serial;
|
||||||
|
std::string patchFile;
|
||||||
std::vector<patchInfo> pending_patches;
|
std::vector<patchInfo> pending_patches;
|
||||||
|
|
||||||
QString toHex(unsigned long long value, size_t byteSize) {
|
std::string toHex(unsigned long long value, size_t byteSize) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::hex << std::setfill('0') << std::setw(byteSize * 2) << value;
|
ss << std::hex << std::setfill('0') << std::setw(byteSize * 2) << value;
|
||||||
return QString::fromStdString(ss.str());
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString convertValueToHex(const QString& type, const QString& valueStr) {
|
std::string convertValueToHex(const std::string type, const std::string valueStr) {
|
||||||
QString result;
|
std::string result;
|
||||||
std::string typeStr = type.toStdString();
|
|
||||||
std::string valueStrStd = valueStr.toStdString();
|
|
||||||
|
|
||||||
if (typeStr == "byte") {
|
if (type == "byte") {
|
||||||
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
|
unsigned int value = std::stoul(valueStr, nullptr, 16);
|
||||||
result = toHex(value, 1);
|
result = toHex(value, 1);
|
||||||
} else if (typeStr == "bytes16") {
|
} else if (type == "bytes16") {
|
||||||
unsigned int value = std::stoul(valueStrStd, nullptr, 16);
|
unsigned int value = std::stoul(valueStr, nullptr, 16);
|
||||||
result = toHex(value, 2);
|
result = toHex(value, 2);
|
||||||
} else if (typeStr == "bytes32") {
|
} else if (type == "bytes32") {
|
||||||
unsigned long value = std::stoul(valueStrStd, nullptr, 16);
|
unsigned long value = std::stoul(valueStr, nullptr, 16);
|
||||||
result = toHex(value, 4);
|
result = toHex(value, 4);
|
||||||
} else if (typeStr == "bytes64") {
|
} else if (type == "bytes64") {
|
||||||
unsigned long long value = std::stoull(valueStrStd, nullptr, 16);
|
unsigned long long value = std::stoull(valueStr, nullptr, 16);
|
||||||
result = toHex(value, 8);
|
result = toHex(value, 8);
|
||||||
} else if (typeStr == "float32") {
|
} else if (type == "float32") {
|
||||||
union {
|
union {
|
||||||
float f;
|
float f;
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
} floatUnion;
|
} floatUnion;
|
||||||
floatUnion.f = std::stof(valueStrStd);
|
floatUnion.f = std::stof(valueStr);
|
||||||
result = toHex(floatUnion.i, sizeof(floatUnion.i));
|
result = toHex(floatUnion.i, sizeof(floatUnion.i));
|
||||||
} else if (typeStr == "float64") {
|
} else if (type == "float64") {
|
||||||
union {
|
union {
|
||||||
double d;
|
double d;
|
||||||
uint64_t i;
|
uint64_t i;
|
||||||
} doubleUnion;
|
} doubleUnion;
|
||||||
doubleUnion.d = std::stod(valueStrStd);
|
doubleUnion.d = std::stod(valueStr);
|
||||||
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
|
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
|
||||||
} else if (typeStr == "utf8") {
|
} else if (type == "utf8") {
|
||||||
QByteArray byteArray = QString::fromStdString(valueStrStd).toUtf8();
|
std::vector<unsigned char> byteArray =
|
||||||
byteArray.append('\0');
|
std::vector<unsigned char>(valueStr.begin(), valueStr.end());
|
||||||
|
byteArray.push_back('\0');
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
for (unsigned char c : byteArray) {
|
for (unsigned char c : byteArray) {
|
||||||
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
|
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
|
||||||
}
|
}
|
||||||
result = QString::fromStdString(ss.str());
|
result = ss.str();
|
||||||
} else if (typeStr == "utf16") {
|
} else if (type == "utf16") {
|
||||||
QByteArray byteArray(
|
std::wstring wide_str(valueStr.size(), L'\0');
|
||||||
reinterpret_cast<const char*>(QString::fromStdString(valueStrStd).utf16()),
|
std::mbstowcs(&wide_str[0], valueStr.c_str(), valueStr.size());
|
||||||
QString::fromStdString(valueStrStd).size() * 2);
|
wide_str.resize(std::wcslen(wide_str.c_str()));
|
||||||
byteArray.append('\0');
|
|
||||||
byteArray.append('\0');
|
std::u16string valueStringU16;
|
||||||
std::stringstream ss;
|
|
||||||
for (unsigned char c : byteArray) {
|
for (wchar_t wc : wide_str) {
|
||||||
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c);
|
if (wc <= 0xFFFF) {
|
||||||
|
valueStringU16.push_back(static_cast<char16_t>(wc));
|
||||||
|
} else {
|
||||||
|
wc -= 0x10000;
|
||||||
|
valueStringU16.push_back(static_cast<char16_t>(0xD800 | (wc >> 10)));
|
||||||
|
valueStringU16.push_back(static_cast<char16_t>(0xDC00 | (wc & 0x3FF)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = QString::fromStdString(ss.str());
|
|
||||||
} else if (typeStr == "bytes") {
|
std::vector<unsigned char> byteArray;
|
||||||
|
// convert to little endian
|
||||||
|
for (char16_t ch : valueStringU16) {
|
||||||
|
unsigned char low_byte = static_cast<unsigned char>(ch & 0x00FF);
|
||||||
|
unsigned char high_byte = static_cast<unsigned char>((ch >> 8) & 0x00FF);
|
||||||
|
|
||||||
|
byteArray.push_back(low_byte);
|
||||||
|
byteArray.push_back(high_byte);
|
||||||
|
}
|
||||||
|
byteArray.push_back('\0');
|
||||||
|
byteArray.push_back('\0');
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
for (unsigned char ch : byteArray) {
|
||||||
|
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(ch);
|
||||||
|
}
|
||||||
|
result = ss.str();
|
||||||
|
} else if (type == "bytes") {
|
||||||
result = valueStr;
|
result = valueStr;
|
||||||
} else if (typeStr == "mask" || typeStr == "mask_jump32") {
|
} else if (type == "mask" || type == "mask_jump32") {
|
||||||
result = valueStr;
|
result = valueStr;
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO(Loader, "Error applying Patch, unknown type: {}", typeStr);
|
LOG_INFO(Loader, "Error applying Patch, unknown type: {}", type);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnGameLoaded() {
|
void OnGameLoaded() {
|
||||||
|
|
||||||
|
if (!patchFile.empty()) {
|
||||||
|
std::string patchDir = Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string();
|
||||||
|
|
||||||
|
std::string filePath = patchDir + "/" + patchFile;
|
||||||
|
|
||||||
|
pugi::xml_document doc;
|
||||||
|
pugi::xml_parse_result result = doc.load_file(filePath.c_str());
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
auto patchXML = doc.child("Patch");
|
||||||
|
for (pugi::xml_node_iterator it = patchXML.children().begin();
|
||||||
|
it != patchXML.children().end(); ++it) {
|
||||||
|
|
||||||
|
if (std::string(it->name()) == "Metadata") {
|
||||||
|
if (std::string(it->attribute("isEnabled").value()) == "true") {
|
||||||
|
auto patchList = it->first_child();
|
||||||
|
|
||||||
|
std::string currentPatchName = it->attribute("Name").value();
|
||||||
|
|
||||||
|
for (pugi::xml_node_iterator patchLineIt = patchList.children().begin();
|
||||||
|
patchLineIt != patchList.children().end(); ++patchLineIt) {
|
||||||
|
|
||||||
|
std::string type = patchLineIt->attribute("Type").value();
|
||||||
|
std::string address = patchLineIt->attribute("Address").value();
|
||||||
|
std::string patchValue = patchLineIt->attribute("Value").value();
|
||||||
|
std::string maskOffsetStr = patchLineIt->attribute("type").value();
|
||||||
|
|
||||||
|
patchValue = convertValueToHex(type, patchValue);
|
||||||
|
|
||||||
|
bool littleEndian = false;
|
||||||
|
|
||||||
|
if (type == "bytes16") {
|
||||||
|
littleEndian = true;
|
||||||
|
} else if (type == "bytes32") {
|
||||||
|
littleEndian = true;
|
||||||
|
} else if (type == "bytes64") {
|
||||||
|
littleEndian = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryPatcher::PatchMask patchMask = MemoryPatcher::PatchMask::None;
|
||||||
|
int maskOffsetValue = 0;
|
||||||
|
|
||||||
|
if (type == "mask") {
|
||||||
|
patchMask = MemoryPatcher::PatchMask::Mask;
|
||||||
|
|
||||||
|
// im not sure if this works, there is no games to test the mask
|
||||||
|
// offset on yet
|
||||||
|
if (!maskOffsetStr.empty())
|
||||||
|
maskOffsetValue = std::stoi(maskOffsetStr, 0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "mask_jump32")
|
||||||
|
patchMask = MemoryPatcher::PatchMask::Mask_Jump32;
|
||||||
|
|
||||||
|
MemoryPatcher::PatchMemory(currentPatchName, address, patchValue, false,
|
||||||
|
littleEndian, patchMask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
LOG_ERROR(Loader, "couldnt patch parse xml : {}", result.description());
|
||||||
|
|
||||||
|
ApplyPendingPatches();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_QT_GUI
|
||||||
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
|
// We use the QT headers for the xml and json parsing, this define is only true on QT builds
|
||||||
QString patchDir =
|
QString patchDir =
|
||||||
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string());
|
QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::PatchesDir).string());
|
||||||
|
@ -190,7 +287,8 @@ void OnGameLoaded() {
|
||||||
QString patchValue = lineObject["Value"].toString();
|
QString patchValue = lineObject["Value"].toString();
|
||||||
QString maskOffsetStr = lineObject["Offset"].toString();
|
QString maskOffsetStr = lineObject["Offset"].toString();
|
||||||
|
|
||||||
patchValue = convertValueToHex(type, patchValue);
|
patchValue = QString::fromStdString(
|
||||||
|
convertValueToHex(type.toStdString(), patchValue.toStdString()));
|
||||||
|
|
||||||
bool littleEndian = false;
|
bool littleEndian = false;
|
||||||
|
|
||||||
|
@ -233,6 +331,7 @@ void OnGameLoaded() {
|
||||||
}
|
}
|
||||||
ApplyPendingPatches();
|
ApplyPendingPatches();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddPatchToQueue(patchInfo patchToAdd) {
|
void AddPatchToQueue(patchInfo patchToAdd) {
|
|
@ -5,13 +5,13 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace MemoryPatcher {
|
namespace MemoryPatcher {
|
||||||
|
|
||||||
extern uintptr_t g_eboot_address;
|
extern uintptr_t g_eboot_address;
|
||||||
extern u64 g_eboot_image_size;
|
extern uint64_t g_eboot_image_size;
|
||||||
extern std::string g_game_serial;
|
extern std::string g_game_serial;
|
||||||
|
extern std::string patchFile;
|
||||||
|
|
||||||
enum PatchMask : uint8_t {
|
enum PatchMask : uint8_t {
|
||||||
None,
|
None,
|
||||||
|
@ -32,7 +32,7 @@ struct patchInfo {
|
||||||
|
|
||||||
extern std::vector<patchInfo> pending_patches;
|
extern std::vector<patchInfo> pending_patches;
|
||||||
|
|
||||||
QString convertValueToHex(const QString& type, const QString& valueStr);
|
std::string convertValueToHex(const std::string type, const std::string valueStr);
|
||||||
|
|
||||||
void OnGameLoaded();
|
void OnGameLoaded();
|
||||||
void AddPatchToQueue(patchInfo patchToAdd);
|
void AddPatchToQueue(patchInfo patchToAdd);
|
|
@ -6,9 +6,7 @@
|
||||||
#include "common/arch.h"
|
#include "common/arch.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#ifdef ENABLE_QT_GUI
|
#include "common/memory_patcher.h"
|
||||||
#include "qt_gui/memory_patcher.h"
|
|
||||||
#endif
|
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/aerolib/aerolib.h"
|
#include "core/aerolib/aerolib.h"
|
||||||
#include "core/cpu_patches.h"
|
#include "core/cpu_patches.h"
|
||||||
|
@ -199,7 +197,6 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
|
||||||
const VAddr entry_addr = base_virtual_addr + elf.GetElfEntry();
|
const VAddr entry_addr = base_virtual_addr + elf.GetElfEntry();
|
||||||
LOG_INFO(Core_Linker, "program entry addr ..........: {:#018x}", entry_addr);
|
LOG_INFO(Core_Linker, "program entry addr ..........: {:#018x}", entry_addr);
|
||||||
|
|
||||||
#ifdef ENABLE_QT_GUI
|
|
||||||
if (MemoryPatcher::g_eboot_address == 0) {
|
if (MemoryPatcher::g_eboot_address == 0) {
|
||||||
if (name == "eboot") {
|
if (name == "eboot") {
|
||||||
MemoryPatcher::g_eboot_address = base_virtual_addr;
|
MemoryPatcher::g_eboot_address = base_virtual_addr;
|
||||||
|
@ -207,7 +204,6 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
|
||||||
MemoryPatcher::OnGameLoaded();
|
MemoryPatcher::OnGameLoaded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Module::LoadDynamicInfo() {
|
void Module::LoadDynamicInfo() {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#ifdef ENABLE_QT_GUI
|
#ifdef ENABLE_QT_GUI
|
||||||
#include "qt_gui/memory_patcher.h"
|
#include "common/memory_patcher.h"
|
||||||
#endif
|
#endif
|
||||||
#include "common/ntapi.h"
|
#include "common/ntapi.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
|
|
10
src/main.cpp
10
src/main.cpp
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
#include "common/memory_patcher.h"
|
||||||
#include "emulator.h"
|
#include "emulator.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
@ -10,7 +11,16 @@ int main(int argc, char* argv[]) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
std::string curArg = argv[i];
|
||||||
|
if (curArg == "-p") {
|
||||||
|
std::string patchFile = argv[i + 1];
|
||||||
|
MemoryPatcher::patchFile = patchFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Core::Emulator emulator;
|
Core::Emulator emulator;
|
||||||
emulator.Run(argv[1]);
|
emulator.Run(argv[1]);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@
|
||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
#include <common/logging/log.h>
|
#include <common/logging/log.h>
|
||||||
#include "cheats_patches.h"
|
#include "cheats_patches.h"
|
||||||
|
#include "common/memory_patcher.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "core/module.h"
|
#include "core/module.h"
|
||||||
#include "qt_gui/memory_patcher.h"
|
|
||||||
|
|
||||||
using namespace Common::FS;
|
using namespace Common::FS;
|
||||||
|
|
||||||
|
@ -1178,7 +1178,8 @@ void CheatsPatches::applyPatch(const QString& patchName, bool enabled) {
|
||||||
QString patchValue = lineObject["Value"].toString();
|
QString patchValue = lineObject["Value"].toString();
|
||||||
QString maskOffsetStr = lineObject["Offset"].toString();
|
QString maskOffsetStr = lineObject["Offset"].toString();
|
||||||
|
|
||||||
patchValue = MemoryPatcher::convertValueToHex(type, patchValue);
|
patchValue = QString::fromStdString(
|
||||||
|
MemoryPatcher::convertValueToHex(type.toStdString(), patchValue.toStdString()));
|
||||||
|
|
||||||
bool littleEndian = false;
|
bool littleEndian = false;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
|
#include "common/memory_patcher.h"
|
||||||
#include "core/file_sys/fs.h"
|
#include "core/file_sys/fs.h"
|
||||||
#include "emulator.h"
|
#include "emulator.h"
|
||||||
#include "game_install_dialog.h"
|
#include "game_install_dialog.h"
|
||||||
|
@ -36,6 +37,13 @@ int main(int argc, char* argv[]) {
|
||||||
// Check for command line arguments
|
// Check for command line arguments
|
||||||
if (has_command_line_argument) {
|
if (has_command_line_argument) {
|
||||||
Core::Emulator emulator;
|
Core::Emulator emulator;
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
std::string curArg = argv[i];
|
||||||
|
if (curArg == "-p") {
|
||||||
|
std::string patchFile = argv[i + 1];
|
||||||
|
MemoryPatcher::patchFile = patchFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
emulator.Run(argv[1]);
|
emulator.Run(argv[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue