vita3k: big game patch improvements (#3479)

This commit is contained in:
SpikeHD 2026-01-05 17:29:47 -08:00 committed by GitHub
parent fd6d0072d4
commit 789b78d8ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 366 additions and 13 deletions

View File

@ -247,8 +247,7 @@ SceUID load_module(EmuEnvState &emuenv, const std::string &module_path) {
return SCE_ERROR_ERRNO_ENOENT;
}
// Only load patches for eboot.bin modules
const std::vector<Patch> patches = module_path.find("eboot.bin") != std::string::npos ? get_patches(emuenv.patch_path, emuenv.io.title_id) : std::vector<Patch>();
const std::vector<Patch> patches = get_patches(emuenv.patch_path, emuenv.io.title_id, module_path);
SceUID module_id = load_self(emuenv.kernel, emuenv.mem, module_buffer.data(), module_path, emuenv.log_path, patches);

View File

@ -2,7 +2,11 @@ add_library(
patch
STATIC
include/patch/patch.h
include/patch/instructions.h
include/patch/util.h
src/patch.cpp
src/instructions.cpp
src/util.cpp
)
target_include_directories(patch PUBLIC include)

View File

@ -0,0 +1,56 @@
// Vita3K emulator project
// Copyright (C) 2024 Vita3K team
//
// This program 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 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <array>
#include <map>
#include <string>
#include <util/types.h>
#include <vector>
using TranslateFn = uint32_t (*)(std::vector<uint32_t> &args);
enum class Instruction {
NOP,
T1_MOV,
// All-encompassing "this is not an instruction" value
INVALID,
};
struct Op {
Instruction instruction;
TranslateFn translate;
};
/**
* Special instructions
*/
uint32_t nop(std::vector<uint32_t> &args);
/**
* MOV instructions
*
* https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOV--immediate-?lang=en
*/
uint32_t t1_mov(std::vector<uint32_t> &args);
static const std::map<std::string, Op> instruction_funcs = {
{ "nop", { Instruction::NOP, nop } },
{ "t1_mov", { Instruction::T1_MOV, t1_mov } },
};

View File

@ -27,5 +27,10 @@ struct Patch {
std::vector<uint8_t> values;
};
std::vector<Patch> get_patches(fs::path &path, const std::string &titleid);
struct PatchHeader {
std::string titleid;
std::string bin;
};
std::vector<Patch> get_patches(fs::path &path, const std::string &titleid, const std::string &bin);
Patch parse_patch(const std::string &patch);

View File

@ -0,0 +1,41 @@
// Vita3K emulator project
// Copyright (C) 2024 Vita3K team
//
// This program 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 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <array>
#include <string>
#include <util/types.h>
#include <vector>
#include "patch/instructions.h"
#include "patch/patch.h"
PatchHeader read_header(std::string &header, bool isPatchlist);
std::vector<uint8_t> to_bytes(unsigned long long value, uint8_t count);
void strip_arg_spaces(std::string &line);
void strip_arg_spaces(std::string &line, char open, char close);
Instruction to_instruction(const std::string &inst);
bool is_valid_instruction(std::string &inst);
std::string strip_args(std::string inst);
std::vector<std::string> get_args(std::string inst, char open, char close);
std::vector<std::string> get_args(std::string inst);
uint32_t translate(std::string &inst, std::vector<uint32_t> &args);

View File

@ -0,0 +1,31 @@
// Vita3K emulator project
// Copyright (C) 2024 Vita3K team
//
// This program 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 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "patch/instructions.h"
#include <util/log.h>
uint32_t nop(std::vector<uint32_t> &args) {
return 0;
}
uint32_t t1_mov(std::vector<uint32_t> &args) {
uint8_t b0 = args[1];
uint8_t b1 = 0b00100000 | (args[0] << 8);
return (b1 << 8) | b0;
}

View File

@ -16,11 +16,13 @@
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "patch/patch.h"
#include "patch/instructions.h"
#include "patch/util.h"
#include <util/fs.h>
#include <util/log.h>
std::vector<Patch> get_patches(fs::path &path, const std::string &titleid) {
std::vector<Patch> get_patches(fs::path &path, const std::string &titleid, const std::string &bin) {
// Find a file in the path with the titleid
std::vector<Patch> patches;
@ -31,20 +33,37 @@ std::vector<Patch> get_patches(fs::path &path, const std::string &titleid) {
// Just in case users decide to use lowercase filenames
std::transform(filename.begin(), filename.end(), filename.begin(), ::toupper);
if (filename.find(titleid) != std::string::npos && filename.ends_with(".TXT")) {
bool isPatchlist = filename.find("PATCHLIST.TXT") != std::string::npos;
if ((filename.find(titleid) != std::string::npos && filename.ends_with(".TXT")) || isPatchlist) {
// Read the file
std::ifstream file(entry.path().c_str());
PatchHeader patch_header = PatchHeader{
"",
"eboot.bin"
};
// Parse the file
while (file.good()) {
std::string line;
std::getline(file, line);
// If line is a comment, skip it
if (line[0] == '#')
// If this is a header, remember the binary the next patches are for
if (line[0] == '[') {
patch_header = read_header(line, isPatchlist);
continue;
}
// Ignore comments and patches for other binaries
if (line[0] == '#' || line[0] == '\n' || bin.find(patch_header.bin) == std::string::npos || (isPatchlist && patch_header.titleid != titleid))
continue;
patches.push_back(parse_patch(line));
try {
patches.push_back(parse_patch(line));
} catch (std::exception &e) {
LOG_ERROR("Failed to parse patch line: {}", line);
LOG_ERROR("Failed with: {}", e.what());
}
}
}
}
@ -71,12 +90,50 @@ Patch parse_patch(const std::string &patch) {
// Get all additional values separated by spaces
size_t pos = 0;
while ((pos = values.find(' ')) != std::string::npos) {
values_vec.push_back(std::stoull(values.substr(0, pos), nullptr, 16));
values.erase(0, pos + 1);
}
// Clean up potential instructions by removing spaces in between brackets
// Eg. t1_mov(0, 1) becomes t1_mov(0,1)
strip_arg_spaces(values);
values_vec.push_back(std::stoull(values, nullptr, 16));
// If there is only one value, set pos to the end of the string
if ((pos = values.find(' ')) == std::string::npos)
pos = values.length() - 1;
do {
pos = values.find(' ');
std::string val = values.substr(0, pos);
// Strip 0x from the value if it exists
if (val.length() > 2 && val[0] == '0' && val[1] == 'x')
val.erase(0, 2);
unsigned long long bytes;
uint8_t byte_count = 0;
std::string inst = strip_args(val);
Instruction instruction = to_instruction(inst);
if (instruction != Instruction::INVALID) {
auto args = get_args(val);
std::vector<uint32_t> arg_conv;
arg_conv.reserve(args.size());
std::transform(args.begin(), args.end(), std::back_inserter(arg_conv), [](std::string &s) { return std::stoull(s, nullptr, 16); });
bytes = translate(inst, arg_conv);
LOG_INFO("Translated {} to 0x{:X}", val, bytes);
} else {
bytes = std::stoull(values.substr(0, pos), nullptr, 16);
// We need to count this, as patches may have bytes of zeros that we don't want to just ignore by passing 0 to toBytes
byte_count = val.length() % 2 == 0 ? val.length() / 2 : val.length() / 2 + 1;
}
auto byte_vec = to_bytes(bytes, byte_count);
values_vec.reserve(values_vec.size() + byte_vec.size());
values_vec.insert(values_vec.end(), byte_vec.begin(), byte_vec.end());
values.erase(0, pos + 1);
} while (pos != std::string::npos);
return Patch{ seg, offset, values_vec };
}

160
vita3k/patch/src/util.cpp Normal file
View File

@ -0,0 +1,160 @@
// Vita3K emulator project
// Copyright (C) 2024 Vita3K team
//
// This program 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 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "patch/util.h"
#include <map>
#include <util/log.h>
// This function will return a PatchHeader struct, which contains the titleid and the binary name (if provided)
PatchHeader read_header(std::string &header, bool is_patchlist) {
PatchHeader patch_header;
strip_arg_spaces(header, '[', ']');
auto args = get_args(header, '[', ']');
// When this is in a patchlist file, the possible values are [titleid, bin] and [titleid]
// When this is in a title-specific patch file, the possible values are just [bin] (because the titleid is already known)
if (is_patchlist) {
patch_header.titleid = args[0];
if (args.size() > 1)
patch_header.bin = args[1];
} else {
patch_header.titleid = "";
// If we have more than one argument, user probably made a mistake, so we can correct it
if (args.size() > 1) {
LOG_WARN("Found more than one argument in a title-specific patch file. You only need to specify the binary name because the TitleID is already known.");
patch_header.bin = args[1];
return patch_header;
}
patch_header.bin = args[0];
}
return patch_header;
}
std::vector<uint8_t> to_bytes(unsigned long long value, uint8_t count) {
std::vector<uint8_t> bytes;
// If count is 0, go until we see a byte of all 0s
if (count == 0) {
while (value != 0) {
bytes.push_back(value & 0xFF);
value >>= 8;
}
return bytes;
}
// Otherwise, just go as much as count tells us
for (uint8_t i = 0; i < count; i++) {
bytes.push_back((value >> ((count - 1 - i) * 8)) & 0xFF);
}
return bytes;
}
void strip_arg_spaces(std::string &line, char open, char close) {
bool in_brackets = false;
for (size_t i = 0; i < line.size(); ++i) {
if (line[i] == open) {
in_brackets = true;
} else if (line[i] == close) {
in_brackets = false;
}
if (in_brackets && line[i] == ' ') {
line.erase(i, 1);
--i;
}
}
}
void strip_arg_spaces(std::string &line) {
return strip_arg_spaces(line, '(', ')');
}
Instruction to_instruction(const std::string &inst) {
auto it = instruction_funcs.find(inst);
if (it != instruction_funcs.end())
return it->second.instruction;
return Instruction::INVALID;
}
bool is_valid_instruction(std::string &inst) {
return to_instruction(strip_args(inst)) != Instruction::INVALID;
}
std::string strip_args(std::string inst) {
auto open = inst.find('(');
auto close = inst.find(')');
if (open == std::string::npos || close == std::string::npos)
return inst;
inst.erase(open, close - open + 1);
return inst;
}
std::vector<std::string> get_args(std::string inst, char open, char close) {
auto open_pos = inst.find(open);
auto close_pos = inst.find(close);
std::vector<std::string> args;
if (open_pos == std::string::npos || close_pos == std::string::npos)
return args;
inst = inst.substr(open_pos + 1, close_pos - open_pos - 1);
// If there is only one value, set pos to the end of the string
if ((open_pos = inst.find(',')) == std::string::npos)
open_pos = inst.length() - 1;
do {
open_pos = inst.find(',');
std::string val = inst.substr(0, open_pos);
args.push_back(val);
inst.erase(0, open_pos + 1);
} while (open_pos != std::string::npos);
return args;
}
std::vector<std::string> get_args(std::string inst) {
return get_args(inst, '(', ')');
}
uint32_t translate(std::string &inst, std::vector<uint32_t> &args) {
auto it = instruction_funcs.find(inst);
if (it != instruction_funcs.end())
return it->second.translate(args);
LOG_WARN("Instruction {} could not be translated! It will be replaced with NOP", inst);
return 0;
}