mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-01-09 06:34:07 +08:00
vita3k: big game patch improvements (#3479)
This commit is contained in:
parent
fd6d0072d4
commit
789b78d8ac
@ -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);
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
56
vita3k/patch/include/patch/instructions.h
Normal file
56
vita3k/patch/include/patch/instructions.h
Normal 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 } },
|
||||
};
|
||||
@ -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);
|
||||
|
||||
41
vita3k/patch/include/patch/util.h
Normal file
41
vita3k/patch/include/patch/util.h
Normal 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);
|
||||
31
vita3k/patch/src/instructions.cpp
Normal file
31
vita3k/patch/src/instructions.cpp
Normal 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;
|
||||
}
|
||||
@ -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
160
vita3k/patch/src/util.cpp
Normal 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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user