fix(nid): preserve Unicode paths on Windows
This commit is contained in:
parent
d592d54e11
commit
bf6a054c52
|
|
@ -25,7 +25,7 @@ target_include_directories(nid_patcher PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/i
|
|||
target_compile_features(nid_patcher PRIVATE cxx_std_20)
|
||||
|
||||
if(MINGW)
|
||||
target_link_options(nid_patcher PRIVATE -static -static-libgcc -static-libstdc++)
|
||||
target_link_options(nid_patcher PRIVATE -municode -static -static-libgcc -static-libstdc++)
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(CMAKE_REQUIRED_FLAGS -fno-asynchronous-unwind-tables)
|
||||
check_cxx_source_compiles("#ifdef __USING_SJLJ_EXCEPTIONS__\n#error\n#endif\nint main() { return 0; }" ANYPS5_MINGW_DWARF_EXCEPTIONS)
|
||||
|
|
@ -165,6 +165,9 @@ if(BUILD_TESTING)
|
|||
if(Python3_Interpreter_FOUND)
|
||||
add_test(NAME nid_patcher_macho COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_macho_nids.py $<TARGET_FILE:nid_patcher>)
|
||||
add_test(NAME nid_patcher_native_paths COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_native_paths.py $<TARGET_FILE:nid_patcher>)
|
||||
set_tests_properties(nid_patcher_native_paths PROPERTIES TIMEOUT 20)
|
||||
endif()
|
||||
add_test_executable(patched_libraries_tests tests/PatchedLibraries.cpp)
|
||||
add_dependencies(patched_libraries_tests libs)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef NID_NATIVEPATH_HPP
|
||||
#define NID_NATIVEPATH_HPP
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace Nid::Internal {
|
||||
|
||||
inline std::filesystem::path NativePath(const std::string& path) {
|
||||
return std::filesystem::path(std::u8string(path.begin(), path.end()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
#include <nid/PeNidPatcher.hpp>
|
||||
#include <nid/MachONidPatcher.hpp>
|
||||
#include <nid/NidPatcherUtils.hpp>
|
||||
#include <nid/NativePath.hpp>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
|
@ -110,7 +111,7 @@ std::string NormalizeExportName(const std::string& name) {
|
|||
|
||||
std::unordered_set<std::string> ReadExportExclusions(const std::string& path) {
|
||||
if constexpr (std::endian::native != std::endian::little) throw std::runtime_error("export references require a little-endian host");
|
||||
std::ifstream input(path, std::ios::binary | std::ios::ate);
|
||||
std::ifstream input(Internal::NativePath(path), std::ios::binary | std::ios::ate);
|
||||
if (!input) throw std::runtime_error("cannot open export reference: " + path);
|
||||
const auto size = input.tellg();
|
||||
if (size <= 0) throw std::runtime_error("empty or unreadable export reference: " + path);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <nid/BinaryPatcherFactory.hpp>
|
||||
#include <nid/ExportExclusions.hpp>
|
||||
#include <nid/NativePath.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
|
@ -9,20 +10,20 @@
|
|||
namespace {
|
||||
|
||||
std::vector<std::uint8_t> _readFile(const std::string& path) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
std::ifstream f(Nid::Internal::NativePath(path), std::ios::binary);
|
||||
if (!f) throw std::runtime_error("cannot open: " + path);
|
||||
return {std::istreambuf_iterator<char>(f), {}};
|
||||
}
|
||||
|
||||
void _writeFile(const std::string& path, const std::vector<std::uint8_t>& data) {
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
std::ofstream f(Nid::Internal::NativePath(path), std::ios::binary);
|
||||
if (!f) throw std::runtime_error("cannot write: " + path);
|
||||
f.write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int Run(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: nid_patcher <library_name> [--preserve-exports <unpatched-library>] <file> [file2 ...]\n";
|
||||
return 1;
|
||||
|
|
@ -65,3 +66,27 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
try {
|
||||
std::vector<std::string> values;
|
||||
values.reserve(static_cast<std::size_t>(argc));
|
||||
for (int index = 0; index < argc; ++index) {
|
||||
const auto utf8 = std::filesystem::path(argv[index]).u8string();
|
||||
values.emplace_back(utf8.begin(), utf8.end());
|
||||
}
|
||||
std::vector<char*> arguments;
|
||||
arguments.reserve(values.size());
|
||||
for (auto& value : values) arguments.push_back(value.data());
|
||||
return Run(argc, arguments.data());
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << "FAIL: " << error.what() << '\n';
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
#else
|
||||
int main(int argc, char* argv[]) {
|
||||
return Run(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from test_macho_nids import build_dylib, exports_of, nid
|
||||
|
||||
PATCHER = Path(sys.argv.pop(1)).resolve()
|
||||
|
||||
|
||||
class NativePathTests(unittest.TestCase):
|
||||
def invoke(self, *arguments):
|
||||
return subprocess.run([str(PATCHER), "libtest", *map(str, arguments)],
|
||||
capture_output=True, encoding="utf-8", errors="replace", timeout=10)
|
||||
|
||||
def test_non_ascii_directory_and_filename_patch_the_intended_file(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary) / "\u6e2c\u8a66"
|
||||
root.mkdir()
|
||||
file = root / "\u043c\u043e\u0434\u0443\u043b\u044c.prx"
|
||||
file.write_bytes(build_dylib([("_puts_nid_postfix", 0, 0x100)], []))
|
||||
result = self.invoke(file)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertEqual(exports_of(file.read_bytes()), {"_" + nid("puts"): (0, 0x100)})
|
||||
self.assertIn(str(file), result.stdout)
|
||||
self.assertEqual(list(root.iterdir()), [file])
|
||||
|
||||
def test_non_ascii_export_reference_preserves_the_named_export(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
reference = root / "\u6e2c\u8a66-reference.prx"
|
||||
original = build_dylib([("_puts", 0, 0x100)], [])
|
||||
reference.write_bytes(original)
|
||||
file = root / "target.prx"
|
||||
file.write_bytes(build_dylib([("_puts_nid_postfix", 0, 0x100), ("_exit_nid_postfix", 0, 0x108)], []))
|
||||
result = self.invoke("--preserve-exports", reference, file)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertEqual(exports_of(file.read_bytes()), {"_puts_nid_postfix": (0, 0x100), "_" + nid("exit"): (0, 0x108)})
|
||||
self.assertEqual(reference.read_bytes(), original)
|
||||
|
||||
def test_missing_unicode_input_does_not_modify_another_file(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
existing = root / "existing.prx"
|
||||
existing.write_bytes(b"unchanged")
|
||||
missing = root / "\u6e2c\u8a66-missing.prx"
|
||||
result = self.invoke(missing)
|
||||
self.assertEqual(result.returncode, 2)
|
||||
self.assertIn(str(missing), result.stderr)
|
||||
self.assertFalse(missing.exists())
|
||||
self.assertEqual(existing.read_bytes(), b"unchanged")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue