Skip to content

Commit

Permalink
patch: Add support for patch files (Vita3K#3419)
Browse files Browse the repository at this point in the history
* feat: poc patch loading

* fix: remove some logging, tweak filename requirements

* fix: clang format

* fix: loop -> memcpy

* fix: patching the wrong file

* fix: patch path in shared path on linux

* fix: newline

* fix: change to txt

* fix: explicit std

* fix: conditionally load patches

* fix: stroull -> stoull
  • Loading branch information
SpikeHD authored Nov 15, 2024
1 parent ef0dbb2 commit 0ebcc09
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 6 deletions.
3 changes: 2 additions & 1 deletion vita3k/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ add_subdirectory(http)
add_subdirectory(io)
add_subdirectory(kernel)
add_subdirectory(mem)
add_subdirectory(patch)
add_subdirectory(module)
add_subdirectory(modules)
add_subdirectory(motion)
Expand All @@ -136,7 +137,7 @@ if(WIN32)
target_sources(vita3k PRIVATE util/src/vc_runtime_checker.cpp)
endif()

target_link_libraries(vita3k PRIVATE app config cppcommon ctrl display gdbstub gui gxm io miniz modules packages renderer shader touch util)
target_link_libraries(vita3k PRIVATE app config cppcommon ctrl display gdbstub gui gxm io miniz modules packages patch renderer shader touch util)
if(USE_DISCORD_RICH_PRESENCE)
target_link_libraries(vita3k PRIVATE discord-rpc)
endif()
Expand Down
7 changes: 7 additions & 0 deletions vita3k/app/src/app_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ void init_paths(Root &root_paths) {
root_paths.set_config_path(portable_path);
root_paths.set_shared_path(portable_path);
root_paths.set_cache_path(portable_path / "cache" / "");
root_paths.set_patch_path(portable_path / "patch" / "");
} else {
// SDL_GetPrefPath is deferred as it creates the directory.
// When using a portable directory, it is not needed.
Expand Down Expand Up @@ -157,6 +158,7 @@ void init_paths(Root &root_paths) {
root_paths.set_config_path(base_path);
root_paths.set_shared_path(base_path);
root_paths.set_cache_path(base_path / "cache" / "");
root_paths.set_patch_path(base_path / "patch" / "");

#if defined(__linux__) && !defined(__ANDROID__) && !defined(__APPLE__)
// XDG Data Dirs.
Expand Down Expand Up @@ -221,6 +223,9 @@ void init_paths(Root &root_paths) {
} else if (XDG_DATA_HOME != NULL) {
root_paths.set_shared_path(fs::path(XDG_DATA_HOME) / app_name / "");
}

// patch path should be in shared path
root_paths.set_patch_path(root_paths.get_shared_path() / "patch" / "");
#endif
}

Expand All @@ -229,6 +234,7 @@ void init_paths(Root &root_paths) {
fs::create_directories(root_paths.get_cache_path());
fs::create_directories(root_paths.get_log_path() / "shaderlog");
fs::create_directories(root_paths.get_log_path() / "texturelog");
fs::create_directories(root_paths.get_patch_path());
}

bool init(EmuEnvState &state, Config &cfg, const Root &root_paths) {
Expand All @@ -241,6 +247,7 @@ bool init(EmuEnvState &state, Config &cfg, const Root &root_paths) {
state.cache_path = root_paths.get_cache_path();
state.shared_path = root_paths.get_shared_path();
state.static_assets_path = root_paths.get_static_assets_path();
state.patch_path = root_paths.get_patch_path();

// If configuration does not provide a preference path, use SDL's default
if (state.cfg.pref_path == root_paths.get_pref_path() || state.cfg.pref_path.empty())
Expand Down
1 change: 1 addition & 0 deletions vita3k/emuenv/include/emuenv/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ struct EmuEnvState {
fs::path pref_path{};
fs::path static_assets_path{};
fs::path shared_path{};
fs::path patch_path{};
bool load_exec{};
std::string load_app_path{};
std::string load_exec_argv{};
Expand Down
2 changes: 1 addition & 1 deletion vita3k/kernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ add_library(

target_include_directories(kernel PUBLIC include)
target_link_libraries(kernel PUBLIC rtc cpu mem util nids)
target_link_libraries(kernel PRIVATE sdl2 miniz vita-toolchain)
target_link_libraries(kernel PRIVATE patch sdl2 miniz vita-toolchain)
if(TRACY_ENABLE_ON_CORE_COMPONENTS)
target_link_libraries(kernel PRIVATE tracy)
endif()
Expand Down
3 changes: 2 additions & 1 deletion vita3k/kernel/include/kernel/load_self.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#pragma once

#include <patch/patch.h>
#include <util/fs.h>
#include <util/types.h>

Expand All @@ -26,5 +27,5 @@ struct KernelState;
struct MemState;
struct KernelModule;

SceUID load_self(KernelState &kernel, MemState &mem, const void *self, const std::string &self_path, const fs::path &log_path);
SceUID load_self(KernelState &kernel, MemState &mem, const void *self, const std::string &self_path, const fs::path &log_path, const std::vector<Patch> &patches);
int unload_self(KernelState &kernel, MemState &mem, KernelModule &module);
10 changes: 9 additions & 1 deletion vita3k/kernel/src/load_self.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ static bool load_exports(SceKernelModuleInfo *kernel_module_info, const sce_modu
/**
* \return Negative on failure
*/
SceUID load_self(KernelState &kernel, MemState &mem, const void *self, const std::string &self_path, const fs::path &log_path) {
SceUID load_self(KernelState &kernel, MemState &mem, const void *self, const std::string &self_path, const fs::path &log_path, const std::vector<Patch> &patches) {
// TODO: use raw I/O from path when io becomes less bad
const uint8_t *const self_bytes = static_cast<const uint8_t *>(self);
const SCE_header &self_header = *static_cast<const SCE_header *>(self);
Expand Down Expand Up @@ -633,6 +633,14 @@ SceUID load_self(KernelState &kernel, MemState &mem, const void *self, const std
memcpy(seg_ptr.get(mem), seg_bytes, seg_header.p_filesz);
}

for (auto &patch : patches) {
// TODO patches should maybe be able to specify the path/file to patch?
if (seg_index == patch.seg && self_path.find("eboot.bin") != std::string::npos) {
LOG_INFO("Patching segment {} at offset 0x{:X} with {} values", seg_index, patch.offset, patch.values.size());
memcpy(seg_ptr.get(mem) + patch.offset, patch.values.data(), patch.values.size());
}
}

segment_reloc_info[seg_index] = { segment_address, seg_header.p_vaddr, seg_header.p_memsz };
}
} else if (seg_header.p_type == PT_SCE_RELA) {
Expand Down
2 changes: 1 addition & 1 deletion vita3k/modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,6 @@ set(SOURCE_LIST

add_library(modules STATIC ${SOURCE_LIST})
target_include_directories(modules PUBLIC include)
target_link_libraries(modules PRIVATE audio codec ctrl dialog display dlmalloc gui gxm kernel mem motion net ngs np ssl packages printf renderer rtc sdl2 touch xxHash::xxhash)
target_link_libraries(modules PRIVATE audio codec ctrl dialog display dlmalloc gui gxm kernel mem motion net ngs np ssl packages patch printf renderer rtc sdl2 touch xxHash::xxhash)
target_link_libraries(modules PUBLIC module)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_LIST})
8 changes: 7 additions & 1 deletion vita3k/modules/module_parent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <kernel/state.h>
#include <module/load_module.h>
#include <nids/functions.h>
#include <patch/patch.h>
#include <util/arm.h>
#include <util/find.h>
#include <util/lock_and_find.h>
Expand Down Expand Up @@ -241,7 +242,12 @@ SceUID load_module(EmuEnvState &emuenv, const std::string &module_path) {
LOG_ERROR("Failed to read module file {}", module_path);
return SCE_ERROR_ERRNO_ENOENT;
}
SceUID module_id = load_self(emuenv.kernel, emuenv.mem, module_buffer.data(), module_path, emuenv.log_path);

// 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>();

SceUID module_id = load_self(emuenv.kernel, emuenv.mem, module_buffer.data(), module_path, emuenv.log_path, patches);

if (module_id >= 0) {
const auto module = lock_and_find(module_id, emuenv.kernel.loaded_modules, emuenv.kernel.mutex);
LOG_INFO("Module {} (at \"{}\") loaded", module->info.module_name, module_path);
Expand Down
9 changes: 9 additions & 0 deletions vita3k/patch/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_library(
patch
STATIC
include/patch/patch.h
src/patch.cpp
)

target_include_directories(patch PUBLIC include)
target_link_libraries(patch PRIVATE util)
32 changes: 32 additions & 0 deletions vita3k/patch/include/patch/patch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 <string>
#include <util/fs.h>
#include <util/types.h>
#include <vector>

struct Patch {
uint8_t seg;
uint32_t offset;
std::vector<uint8_t> values;
};

std::vector<Patch> get_patches(fs::path &path, const std::string &titleid);
Patch parse_patch(const std::string &patch);
81 changes: 81 additions & 0 deletions vita3k/patch/src/patch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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/patch.h"

#include <util/log.h>

std::vector<Patch> get_patches(fs::path &path, const std::string &titleid) {
// Find a file in the path with the titleid
std::vector<Patch> patches;

LOG_INFO("Looking for patches for titleid {}", titleid);

for (auto &entry : fs::directory_iterator(path)) {
auto filename = entry.path().filename().string();
// Just in case users decide to use lowercase filenames
auto upper_filename = std::transform(filename.begin(), filename.end(), filename.begin(), ::toupper);

if (filename.find(titleid) != std::string::npos && filename.ends_with(".TXT")) {
// Read the file
std::ifstream file(entry.path().c_str());

// Parse the file
while (file.good()) {
std::string line;
std::getline(file, line);

// If line is a comment, skip it
if (line[0] == '#')
continue;

patches.push_back(parse_patch(line));
}
}
}

LOG_INFO("Found {} patches for titleid {}", patches.size(), titleid);

return patches;
}

Patch parse_patch(const std::string &patch) {
// FORMAT: <seg>:<offset> <values>
// Example, equivalent to `t1_mov(0, 1)`:
// 0:0xA994 0x01 0x20
// Keep in mind that we are in little endian
uint8_t seg = std::stoi(patch.substr(0, patch.find(':')));

// Everything after the first colon, and before the first space, is the offset
uint32_t offset = std::stoull(patch.substr(patch.find(':') + 1, patch.find(' ') - patch.find(':') - 1), nullptr, 16);

// All following values (separated by spaces) are the values to be written
std::string values = patch.substr(patch.find(' ') + 1);
std::vector<uint8_t> values_vec;

// 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);
}

values_vec.push_back(std::stoull(values, nullptr, 16));

return Patch{ seg, offset, values_vec };
}
8 changes: 8 additions & 0 deletions vita3k/util/include/util/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ struct fmt::formatter<boost::filesystem::path> : ostream_formatter {};
class Root {
fs::path base_path;
fs::path pref_path;
fs::path patch_path;
fs::path log_path;
fs::path config_path;
fs::path shared_path;
Expand All @@ -101,6 +102,13 @@ class Root {
return pref_path;
}

void set_patch_path(const fs::path &p) {
patch_path = p;
}
fs::path get_patch_path() const {
return patch_path;
}

void set_log_path(const fs::path &p) {
log_path = p;
}
Expand Down

0 comments on commit 0ebcc09

Please sign in to comment.