mirror of
https://github.com/mon/ifs_layeredfs.git
synced 2026-04-26 01:20:28 -05:00
Add testing framework and some regression tests, fix string casing regression in devmode, allow data_mods folder to be changed
This commit is contained in:
parent
8bfa283a73
commit
4c9d6619f4
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
*.zip
|
||||
*.dll
|
||||
build32/
|
||||
build64/
|
||||
dist/
|
||||
subprojects/googletest*
|
||||
subprojects/packagecache
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ folder.
|
|||
Folders cannot have "," in their name if using allow/blocklist
|
||||
--layered-logfile=filename.log
|
||||
Use a custom, separate logfile instead of the game's log.
|
||||
--layered-data-mods-folder=./some_folder
|
||||
Use a custom mods folder instead of the default ./data_mods
|
||||
MUST start with "./" to avoid path weirdness.
|
||||
```
|
||||
|
||||
# Logs
|
||||
|
|
|
|||
BIN
avs2-core.dll
Normal file
BIN
avs2-core.dll
Normal file
Binary file not shown.
|
|
@ -9,3 +9,6 @@ system = 'windows'
|
|||
cpu_family = 'x86'
|
||||
cpu = 'i686'
|
||||
endian = 'little'
|
||||
|
||||
[properties]
|
||||
needs_exe_wrapper = false
|
||||
|
|
|
|||
|
|
@ -9,3 +9,6 @@ system = 'windows'
|
|||
cpu_family = 'x86_64'
|
||||
cpu = 'x86_64'
|
||||
endian = 'little'
|
||||
|
||||
[properties]
|
||||
needs_exe_wrapper = false
|
||||
|
|
|
|||
89
ensure_xp_compatible.py
Normal file
89
ensure_xp_compatible.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import argparse
|
||||
import os
|
||||
from glob import glob
|
||||
from typing import TypeAlias
|
||||
|
||||
import pefile
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("dll_path")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
Imports: TypeAlias = dict[bytes, list[tuple[bytes | None, int | None]]]
|
||||
Exports: TypeAlias = list[tuple[bytes | None, int | None]]
|
||||
|
||||
|
||||
class MissingDLL(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MissingFunction(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def load_imports(path: str) -> Imports:
|
||||
print(f"Loading imports from {path}")
|
||||
pe = pefile.PE(path)
|
||||
imports: Imports = {}
|
||||
total = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
these = []
|
||||
for imp in entry.imports:
|
||||
these.append((imp.name, imp.ordinal))
|
||||
|
||||
imports[entry.dll.lower()] = these
|
||||
total += len(these)
|
||||
|
||||
print(f" ...{total} imports from {len(imports)} DLLs")
|
||||
|
||||
return imports
|
||||
|
||||
|
||||
def load_exports(path: str) -> Exports:
|
||||
print(f"Loading exports from {path}...")
|
||||
pe = pefile.PE(path)
|
||||
exports: Exports = []
|
||||
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
|
||||
exports.append((exp.name, exp.ordinal))
|
||||
|
||||
print(f" ...{len(exports)} exports")
|
||||
|
||||
return exports
|
||||
|
||||
|
||||
available_functions: dict[bytes, Exports] = {}
|
||||
for dll in glob("./xp_dlls/*.dll"):
|
||||
available_functions[os.path.basename(dll).lower().encode()] = load_exports(dll)
|
||||
|
||||
needed_functions = load_imports(args.dll_path)
|
||||
|
||||
# https://www.geoffchappell.com/studies/windows/win32/kernel32/api/index.htm
|
||||
# my test .dll is 32-bit and these are only in 64
|
||||
ignore_functions = [
|
||||
b"RtlLookupFunctionEntry",
|
||||
b"RtlUnwindEx",
|
||||
b"RtlVirtualUnwind",
|
||||
b"__C_specific_handler",
|
||||
]
|
||||
|
||||
for dll_name, imports in needed_functions.items():
|
||||
if (dll := available_functions.get(dll_name)) is None:
|
||||
raise MissingDLL(f"Need {dll_name} but it's not in the XP DLLs folder")
|
||||
|
||||
for name, ordinal in imports:
|
||||
if name is not None:
|
||||
if name in ignore_functions:
|
||||
continue
|
||||
|
||||
if next((fn for fn in dll if fn[0] == name), None) is None:
|
||||
raise MissingFunction(f'Function "{name}" not present in XP {dll_name}')
|
||||
elif ordinal is not None:
|
||||
if next((fn for fn in dll if fn[1] == ordinal), None) is None:
|
||||
raise MissingFunction(
|
||||
f"Function with ordinal {ordinal} not present in XP {dll_name}"
|
||||
)
|
||||
else:
|
||||
raise RuntimeError("Import with no name or ordinal???")
|
||||
|
||||
print("All functions present!")
|
||||
56
meson.build
56
meson.build
|
|
@ -1,6 +1,6 @@
|
|||
project('layeredfs', 'c', 'cpp', version: '3.4',
|
||||
default_options: [
|
||||
'cpp_std=c++17',
|
||||
'cpp_std=c++20',
|
||||
'buildtype=release',
|
||||
'strip=true',
|
||||
'werror=true',
|
||||
|
|
@ -66,10 +66,14 @@ layeredfs_cfg_dep = declare_dependency(
|
|||
]
|
||||
)
|
||||
|
||||
avs_standalone_lib = static_library('avs_standalone',
|
||||
sources: 'src/avs_standalone.cpp'
|
||||
)
|
||||
|
||||
executable('playpen',
|
||||
sources: 'src/playpen.cpp',
|
||||
build_by_default: false,
|
||||
link_with: [layeredfs_lib, texbin_verbose_lib],
|
||||
link_with: [layeredfs_lib, texbin_verbose_lib, avs_standalone_lib],
|
||||
dependencies: layeredfs_cfg_dep,
|
||||
)
|
||||
|
||||
|
|
@ -80,17 +84,10 @@ executable('texbin_debug',
|
|||
dependencies: layeredfs_cfg_dep,
|
||||
)
|
||||
|
||||
# "normal" hook
|
||||
shared_library('ifs_hook',
|
||||
link_with: [layeredfs_lib, texbin_lib],
|
||||
dependencies: layeredfs_cfg_dep,
|
||||
name_prefix: '',
|
||||
install_dir: '/',
|
||||
install: true,
|
||||
)
|
||||
|
||||
# pre-configured DLLs if you don't know how to (or can't) add cmdline args
|
||||
special_cfgs = [
|
||||
# "normal" hook
|
||||
['', []],
|
||||
['always_verbose', ['-DCFG_VERBOSE']],
|
||||
['always_logs_to_file', ['-DCFG_LOGFILE']],
|
||||
['always_verbose_and_logs_to_file', ['-DCFG_VERBOSE','-DCFG_LOGFILE']],
|
||||
|
|
@ -113,6 +110,8 @@ if host_machine.cpu_family() == 'x86'
|
|||
)
|
||||
endif
|
||||
|
||||
python = import('python').find_installation()
|
||||
|
||||
foreach cfg : special_cfgs
|
||||
folder_name = cfg[0]
|
||||
defines = cfg[1]
|
||||
|
|
@ -121,14 +120,32 @@ foreach cfg : special_cfgs
|
|||
continue
|
||||
endif
|
||||
|
||||
shared_library('ifs_hook_' + folder_name,
|
||||
lib_name = 'ifs_hook_' + folder_name
|
||||
special_defines = []
|
||||
install_dir = '/special_builds' / folder_name
|
||||
if folder_name == ''
|
||||
lib_name = 'ifs_hook'
|
||||
install_dir = '/'
|
||||
else
|
||||
special_defines = [f'-DSPECIAL_VER="@folder_name@"']
|
||||
endif
|
||||
|
||||
ifs_dll = shared_library(lib_name,
|
||||
link_with: [layeredfs_lib, texbin_lib],
|
||||
dependencies: layeredfs_cfg_dep,
|
||||
cpp_args: [defines, f'-DSPECIAL_VER="@folder_name@"'],
|
||||
cpp_args: [defines, special_defines],
|
||||
name_prefix: '',
|
||||
install_dir: '/special_builds' / folder_name,
|
||||
install: true,
|
||||
)
|
||||
test(lib_name + '_is_xp',
|
||||
python,
|
||||
args: [
|
||||
files('ensure_xp_compatible.py'),
|
||||
ifs_dll,
|
||||
],
|
||||
workdir: meson.current_source_dir(),
|
||||
)
|
||||
endforeach
|
||||
|
||||
injector_cfgs = [
|
||||
|
|
@ -165,3 +182,16 @@ foreach cfg : injector_cfgs
|
|||
install: true,
|
||||
)
|
||||
endforeach
|
||||
|
||||
gtest_proj = subproject('gtest', required: false, default_options: {'default_library': 'static'})
|
||||
gtest_main_dep = gtest_proj.get_variable('gtest_main_dep')
|
||||
gmock_dep = gtest_proj.get_variable('gmock_dep')
|
||||
|
||||
test('tests', executable('tests_bin',
|
||||
sources: 'src/tests.cpp',
|
||||
link_with: [layeredfs_lib, texbin_lib, avs_standalone_lib],
|
||||
dependencies: [layeredfs_cfg_dep, gtest_main_dep, gmock_dep],
|
||||
build_by_default: false,
|
||||
),
|
||||
workdir: meson.current_source_dir(),
|
||||
)
|
||||
|
|
|
|||
163
src/avs_standalone.cpp
Normal file
163
src/avs_standalone.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#include "avs_standalone.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "avs.h"
|
||||
#include "hook.h"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace avs_standalone
|
||||
{
|
||||
|
||||
typedef void (*avs_log_writer_t)(const char *chars, uint32_t nchars, void *ctx);
|
||||
|
||||
static LONG WINAPI exc_handler(_EXCEPTION_POINTERS *ExceptionInfo);
|
||||
static size_t read_str(int32_t context, void *dst_buf, size_t count);
|
||||
static void log_writer(const char *chars, uint32_t nchars, void *ctx);
|
||||
|
||||
#define FOREACH_EXTRA_FUNC(X) \
|
||||
X("XCgsqzn0000129", void, avs_boot, node_t config, void *com_heap, size_t sz_com_heap, void *reserved, avs_log_writer_t log_writer, void *log_context) \
|
||||
X("XCgsqzn000012a", void, avs_shutdown) \
|
||||
X("XCgsqzn00000a1", node_t, property_search, property_t prop, node_t node, const char *path) \
|
||||
X("XCgsqzn0000048", int, avs_fs_addfs, void *filesys) \
|
||||
X("XCgsqzn0000159", void *, avs_filesys_ramfs)
|
||||
|
||||
#define AVS_FUNC_PTR(obfus_name, ret_type, name, ...) ret_type (*name)(__VA_ARGS__);
|
||||
FOREACH_EXTRA_FUNC(AVS_FUNC_PTR)
|
||||
|
||||
static bool g_print_logs = true;
|
||||
static size_t g_boot_cfg_offset;
|
||||
|
||||
#define DEFAULT_HEAP_SIZE 16777216
|
||||
|
||||
bool boot(bool _print_logs) {
|
||||
AddVectoredExceptionHandler(1, exc_handler);
|
||||
|
||||
log_to_stdout();
|
||||
|
||||
if(!load_dll()) {
|
||||
log_fatal("DLL load failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
init_avs(); // fails because of Minhook not being initialised, don't care
|
||||
|
||||
auto avs_heap = malloc(DEFAULT_HEAP_SIZE);
|
||||
|
||||
g_boot_cfg_offset = 0;
|
||||
int prop_len = property_read_query_memsize(read_str, 0, 0, 0);
|
||||
if (prop_len <= 0) {
|
||||
log_fatal("error reading config (size <= 0)");
|
||||
return false;
|
||||
}
|
||||
auto buffer = malloc(prop_len);
|
||||
auto avs_config = property_create(PROP_READ | PROP_WRITE | PROP_CREATE | PROP_APPEND, buffer, prop_len);
|
||||
if (!avs_config) {
|
||||
log_fatal("cannot create property");
|
||||
return false;
|
||||
}
|
||||
g_boot_cfg_offset = 0;
|
||||
if (!property_insert_read(avs_config, 0, read_str, 0)) {
|
||||
log_fatal("avs-core", "cannot read property");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto avs_config_root = property_search(avs_config, 0, "/config");
|
||||
if(!avs_config_root) {
|
||||
log_fatal("no root config node");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool was_print = g_print_logs;
|
||||
g_print_logs = _print_logs;
|
||||
avs_boot(avs_config_root, avs_heap, DEFAULT_HEAP_SIZE, NULL, log_writer, NULL);
|
||||
g_print_logs = was_print;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown(void) {
|
||||
avs_shutdown();
|
||||
}
|
||||
|
||||
#define LOAD_FUNC(obfus_name, ret_type, name, ...) \
|
||||
if (!(name = (decltype(name))GetProcAddress(avs, obfus_name))) \
|
||||
{ \
|
||||
log_fatal("avs_standalone: couldn't get " #name); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
bool load_dll(void)
|
||||
{
|
||||
auto avs = LoadLibraryA("avs2-core.dll");
|
||||
if (!avs)
|
||||
{
|
||||
log_fatal("Playpen: Couldn't load avs dll");
|
||||
return false;
|
||||
}
|
||||
|
||||
FOREACH_EXTRA_FUNC(LOAD_FUNC);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void log_writer(const char *chars, uint32_t nchars, void *ctx)
|
||||
{
|
||||
// don't print noisy shutdown logs
|
||||
auto prefix = "[----/--/-- --:--:--] ";
|
||||
auto len = strlen(prefix);
|
||||
if (strncmp(chars, prefix, len) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_print_logs)
|
||||
{
|
||||
fprintf(stderr, "%.*s", nchars, chars);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *boot_cfg = R"(<?xml version="1.0" encoding="SHIFT_JIS"?>
|
||||
<config>
|
||||
<fs>
|
||||
<nr_filesys __type="u16">16</nr_filesys>
|
||||
<nr_mountpoint __type="u16">1024</nr_mountpoint>
|
||||
<nr_mounttable __type="u16">32</nr_mounttable>
|
||||
<nr_filedesc __type="u16">4096</nr_filedesc>
|
||||
<link_limit __type="u16">8</link_limit>
|
||||
<root>
|
||||
<device>.</device>
|
||||
<!--<option>posix=1</option>-->
|
||||
</root>
|
||||
<mounttable>
|
||||
<vfs name="boot" fstype="fs" src="dev/raw" dst="/dev/raw" opt="vf=1,posix=1"/>
|
||||
<vfs name="boot" fstype="fs" src="dev/nvram" dst="/dev/nvram" opt="vf=0,posix=1"/>
|
||||
</mounttable>
|
||||
</fs>
|
||||
<log><level>misc</level></log>
|
||||
<sntp>
|
||||
<ea_on __type="bool">0</ea_on>
|
||||
<servers></servers>
|
||||
</sntp>
|
||||
</config>
|
||||
)";
|
||||
|
||||
static size_t read_str(int32_t context, void *dst_buf, size_t count)
|
||||
{
|
||||
memcpy(dst_buf, &boot_cfg[g_boot_cfg_offset], count);
|
||||
g_boot_cfg_offset += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
LONG WINAPI exc_handler(_EXCEPTION_POINTERS *ExceptionInfo) {
|
||||
switch(ExceptionInfo->ExceptionRecord->ExceptionCode) {
|
||||
case DBG_PRINTEXCEPTION_C:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unhandled exception %lX\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
|
||||
break;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
}
|
||||
7
src/avs_standalone.hpp
Normal file
7
src/avs_standalone.hpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace avs_standalone
|
||||
{
|
||||
|
||||
bool boot(bool print_logs);
|
||||
void shutdown(void);
|
||||
bool load_dll(void);
|
||||
}
|
||||
|
|
@ -6,12 +6,13 @@
|
|||
#include "log.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
#define VERBOSE_FLAG "--layered-verbose"
|
||||
#define DEVMODE_FLAG "--layered-devmode"
|
||||
#define DISABLE_FLAG "--layered-disable"
|
||||
#define ALLOWLIST_FLAG "--layered-allowlist"
|
||||
#define BLOCKLIST_FLAG "--layered-blocklist"
|
||||
#define LOGFILE_FLAG "--layered-logfile"
|
||||
#define VERBOSE_FLAG "--layered-verbose"
|
||||
#define DEVMODE_FLAG "--layered-devmode"
|
||||
#define DISABLE_FLAG "--layered-disable"
|
||||
#define ALLOWLIST_FLAG "--layered-allowlist"
|
||||
#define BLOCKLIST_FLAG "--layered-blocklist"
|
||||
#define LOGFILE_FLAG "--layered-logfile"
|
||||
#define MOD_FOLDER_FLAG "--layered-data-mods-folder"
|
||||
|
||||
config_t config;
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ void load_config(void) {
|
|||
config.disable = false;
|
||||
config.allowlist.clear();
|
||||
config.blocklist.clear();
|
||||
config.mod_folder = DEFAULT_MOD_FOLDER;
|
||||
|
||||
#ifdef CFG_VERBOSE
|
||||
config.verbose_logs = true;
|
||||
|
|
@ -91,16 +93,24 @@ void load_config(void) {
|
|||
config.logfile = &path[1];
|
||||
}
|
||||
}
|
||||
else if (strncmp(__argv[i], MOD_FOLDER_FLAG, strlen(MOD_FOLDER_FLAG)) == 0) {
|
||||
std::string_view path = &__argv[i][strlen(MOD_FOLDER_FLAG)];
|
||||
// correct format: --layered-data-mods-folder=./my_mods
|
||||
if(path.starts_with("=./")) {
|
||||
config.mod_folder = path.substr(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_config(void) {
|
||||
log_info("Options: %s=%d %s=%d %s=%d %s=%s %s=%s %s=%s",
|
||||
log_info("Options: %s=%d %s=%d %s=%d %s=%s %s=%s %s=%s %s=%s",
|
||||
VERBOSE_FLAG, config.verbose_logs,
|
||||
DEVMODE_FLAG, config.developer_mode,
|
||||
DISABLE_FLAG, config.disable,
|
||||
LOGFILE_FLAG, config.logfile,
|
||||
ALLOWLIST_FLAG, allowlist,
|
||||
BLOCKLIST_FLAG, blocklist
|
||||
BLOCKLIST_FLAG, blocklist,
|
||||
MOD_FOLDER_FLAG, config.mod_folder.c_str()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,13 @@ typedef struct {
|
|||
const char *logfile;
|
||||
std::set<std::string, CaseInsensitiveCompare> allowlist;
|
||||
std::set<std::string, CaseInsensitiveCompare> blocklist;
|
||||
std::string mod_folder;
|
||||
} config_t;
|
||||
|
||||
#define DEFAULT_LOGFILE "ifs_hook.log"
|
||||
#define DEFAULT_MOD_FOLDER "./data_mods"
|
||||
|
||||
#define CACHE_FOLDER (config.mod_folder + "/_cache")
|
||||
|
||||
extern config_t config;
|
||||
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ void handle_texbin(HookFile &file) {
|
|||
}
|
||||
|
||||
auto starting = file.get_path_to_open();
|
||||
string out = CACHE_FOLDER "/" + file.norm_path;
|
||||
string out = CACHE_FOLDER + "/" + file.norm_path;
|
||||
auto out_hashed = out + ".hashed";
|
||||
auto cache_hasher = CacheHasher(out_hashed);
|
||||
|
||||
|
|
@ -292,15 +292,15 @@ uint32_t handle_file_open(HookFile &file) {
|
|||
file.mod_path = find_first_modfile(norm_copy);
|
||||
}
|
||||
|
||||
if(string_ends_with(file.path, ".xml")) {
|
||||
if(file.path.ends_with(".xml")) {
|
||||
merge_xmls(file);
|
||||
}
|
||||
|
||||
if(string_ends_with(file.path, ".bin")) {
|
||||
if(file.path.ends_with(".bin")) {
|
||||
handle_texbin(file);
|
||||
}
|
||||
|
||||
if (string_ends_with(file.path, "texturelist.xml")) {
|
||||
if (file.path.ends_with("texturelist.xml")) {
|
||||
parse_texturelist(file);
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ typedef struct image {
|
|||
string ifs_mod_path;
|
||||
int width;
|
||||
int height;
|
||||
const string cache_folder() { return CACHE_FOLDER "/" + ifs_mod_path; }
|
||||
const string cache_folder() { return CACHE_FOLDER + "/" + ifs_mod_path; }
|
||||
const string cache_file() { return cache_folder() + "/" + name_md5; };
|
||||
} image_t;
|
||||
|
||||
|
|
@ -296,7 +296,7 @@ void parse_texturelist(HookFile &file) {
|
|||
}
|
||||
|
||||
if (prop_was_rewritten) {
|
||||
string outfolder = CACHE_FOLDER "/" + ifs_mod_path;
|
||||
string outfolder = CACHE_FOLDER + "/" + ifs_mod_path;
|
||||
if (!mkdir_p(outfolder)) {
|
||||
log_warning("Couldn't create cache folder");
|
||||
}
|
||||
|
|
@ -459,7 +459,7 @@ void merge_xmls(HookFile &file) {
|
|||
return;
|
||||
|
||||
auto starting = file.get_path_to_open();
|
||||
out = CACHE_FOLDER "/" + file.norm_path;
|
||||
out = CACHE_FOLDER + "/" + file.norm_path;
|
||||
auto out_hashed = out + ".hashed";
|
||||
auto cache_hasher = CacheHasher(out_hashed);
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ optional<string> normalise_path(const string &_path) {
|
|||
|
||||
vector<string> available_mods() {
|
||||
vector<string> ret;
|
||||
string mod_root = MOD_FOLDER "/";
|
||||
string mod_root = config.mod_folder + "/";
|
||||
|
||||
// just pretend we have no mods at all
|
||||
if (config.disable) {
|
||||
|
|
@ -134,7 +134,7 @@ vector<string> available_mods() {
|
|||
|
||||
if (config.developer_mode) {
|
||||
static bool first_search = true;
|
||||
for (auto folder : folders_in_folder(MOD_FOLDER)) {
|
||||
for (auto folder : folders_in_folder(config.mod_folder.c_str())) {
|
||||
if (!strcasecmp(folder.c_str(), "_cache")) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ optional<string> find_first_modfile(const string &norm_path) {
|
|||
for (auto &dir : available_mods()) {
|
||||
auto mod_path = dir + "/" + norm_path;
|
||||
if (file_exists(mod_path.c_str())) {
|
||||
return mod_path;
|
||||
return path_to_actual_case(mod_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ optional<string> find_first_modfolder(const string &norm_path) {
|
|||
for (auto &dir : available_mods()) {
|
||||
auto mod_path = dir + "/" + norm_path;
|
||||
if (folder_exists(mod_path.c_str())) {
|
||||
return mod_path;
|
||||
return path_to_actual_case(mod_path) + "/";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ using std::optional;
|
|||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
#define MOD_FOLDER "./data_mods"
|
||||
#define CACHE_FOLDER MOD_FOLDER "/_cache"
|
||||
|
||||
void init_modpath_handler(void);
|
||||
void cache_mods(void);
|
||||
vector<string> available_mods();
|
||||
|
|
|
|||
138
src/playpen.cpp
138
src/playpen.cpp
|
|
@ -9,27 +9,8 @@
|
|||
|
||||
#include <fstream>
|
||||
|
||||
void boot_avs(void);
|
||||
bool load_dll(void);
|
||||
LONG WINAPI exc_handler(_EXCEPTION_POINTERS *ExceptionInfo);
|
||||
|
||||
typedef void (*avs_log_writer_t)(const char *chars, uint32_t nchars, void *ctx);
|
||||
|
||||
#define FOREACH_EXTRA_FUNC(X) \
|
||||
X("XCgsqzn0000129", void, avs_boot, node_t config, void *com_heap, size_t sz_com_heap, void *reserved, avs_log_writer_t log_writer, void *log_context) \
|
||||
X("XCgsqzn000012a", void, avs_shutdown) \
|
||||
X("XCgsqzn00000a1", node_t, property_search, property_t prop, node_t node, const char *path) \
|
||||
X("XCgsqzn0000048", int, avs_fs_addfs, void* filesys) \
|
||||
X("XCgsqzn0000159", void*, avs_filesys_ramfs) \
|
||||
|
||||
#define AVS_FUNC_PTR(obfus_name, ret_type, name, ...) ret_type (* name )( __VA_ARGS__ );
|
||||
FOREACH_EXTRA_FUNC(AVS_FUNC_PTR)
|
||||
|
||||
static bool print_logs = true;
|
||||
|
||||
#define QUIET_BOOT
|
||||
|
||||
#include "texbin.hpp"
|
||||
#include "avs_standalone.hpp"
|
||||
|
||||
#define log_assert(cond) if(!(cond)) {log_fatal("Assertion failed:" #cond);}
|
||||
|
||||
|
|
@ -219,7 +200,7 @@ FAIL:
|
|||
if (prop_buffer)
|
||||
free(prop_buffer);*/
|
||||
|
||||
/*auto d = avs_fs_opendir(MOD_FOLDER);
|
||||
/*auto d = avs_fs_opendir(config.mod_folder.c_str());
|
||||
if (!d) {
|
||||
log_warning("couldn't d");
|
||||
return;
|
||||
|
|
@ -296,125 +277,16 @@ void textypes() {
|
|||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// textypes();
|
||||
AddVectoredExceptionHandler(1, exc_handler);
|
||||
log_to_stdout();
|
||||
if(!load_dll()) {
|
||||
log_fatal("DLL load failed");
|
||||
if(!avs_standalone::boot(false)) {
|
||||
log_fatal("avs_standalone boot failed");
|
||||
return 1;
|
||||
}
|
||||
init_avs(); // fails because of Minhook not being initialised, don't care
|
||||
#ifdef QUIET_BOOT
|
||||
print_logs = false;
|
||||
boot_avs();
|
||||
print_logs = true;
|
||||
#else
|
||||
boot_avs();
|
||||
#endif
|
||||
|
||||
init(); // this double-hooks some AVS funcs, don't care
|
||||
|
||||
avs_playpen();
|
||||
|
||||
avs_shutdown();
|
||||
avs_standalone::shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DEFAULT_HEAP_SIZE 16777216
|
||||
|
||||
void log_writer(const char *chars, uint32_t nchars, void *ctx) {
|
||||
// don't print noisy shutdown logs
|
||||
auto prefix = "[----/--/-- --:--:--] ";
|
||||
auto len = strlen(prefix);
|
||||
if(strncmp(chars, prefix, len) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(print_logs) {
|
||||
fprintf(stderr, "%.*s", nchars, chars);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *boot_cfg = R"(<?xml version="1.0" encoding="SHIFT_JIS"?>
|
||||
<config>
|
||||
<fs>
|
||||
<nr_filesys __type="u16">16</nr_filesys>
|
||||
<nr_mountpoint __type="u16">1024</nr_mountpoint>
|
||||
<nr_mounttable __type="u16">32</nr_mounttable>
|
||||
<nr_filedesc __type="u16">4096</nr_filedesc>
|
||||
<link_limit __type="u16">8</link_limit>
|
||||
<root><device>.</device></root>
|
||||
<mounttable>
|
||||
<vfs name="boot" fstype="fs" src="dev/raw" dst="/dev/raw" opt="vf=1,posix=1"/>
|
||||
<vfs name="boot" fstype="fs" src="dev/nvram" dst="/dev/nvram" opt="vf=0,posix=1"/>
|
||||
</mounttable>
|
||||
</fs>
|
||||
<log><level>misc</level></log>
|
||||
<sntp>
|
||||
<ea_on __type="bool">0</ea_on>
|
||||
<servers></servers>
|
||||
</sntp>
|
||||
</config>
|
||||
)";
|
||||
|
||||
static size_t off;
|
||||
static size_t read_str(int32_t context, void *dst_buf, size_t count) {
|
||||
memcpy(dst_buf, &boot_cfg[off], count);
|
||||
off += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
#define LOAD_FUNC(obfus_name, ret_type, name, ...) \
|
||||
if(!(name = (decltype(name))GetProcAddress(avs, obfus_name))) {\
|
||||
log_fatal("Playpen: couldn't get " #name); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
bool load_dll(void) {
|
||||
auto avs = LoadLibraryA("avs2-core.dll");
|
||||
if(!avs) {
|
||||
log_fatal("Playpen: Couldn't load avs dll");
|
||||
return false;
|
||||
}
|
||||
|
||||
FOREACH_EXTRA_FUNC(LOAD_FUNC);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void boot_avs(void) {
|
||||
auto avs_heap = malloc(DEFAULT_HEAP_SIZE);
|
||||
|
||||
off = 0;
|
||||
int prop_len = property_read_query_memsize(read_str, 0, 0, 0);
|
||||
if (prop_len <= 0) {
|
||||
log_fatal("error reading config (size <= 0)");
|
||||
return;
|
||||
}
|
||||
auto buffer = malloc(prop_len);
|
||||
auto avs_config = property_create(PROP_READ | PROP_WRITE | PROP_CREATE | PROP_APPEND, buffer, prop_len);
|
||||
if (!avs_config) {
|
||||
log_fatal("cannot create property");
|
||||
return;
|
||||
}
|
||||
off = 0;
|
||||
if (!property_insert_read(avs_config, 0, read_str, 0)) {
|
||||
log_fatal("avs-core", "cannot read property");
|
||||
return;
|
||||
}
|
||||
|
||||
auto avs_config_root = property_search(avs_config, 0, "/config");
|
||||
if(!avs_config_root) {
|
||||
log_fatal("no root config node");
|
||||
return;
|
||||
}
|
||||
|
||||
avs_boot(avs_config_root, avs_heap, DEFAULT_HEAP_SIZE, NULL, log_writer, NULL);
|
||||
}
|
||||
|
||||
LONG WINAPI exc_handler(_EXCEPTION_POINTERS *ExceptionInfo) {
|
||||
fprintf(stderr, "Unhandled exception %lX\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ static CriticalSectionLock mangling_mtx;
|
|||
static void ramfs_demangler_demangle_if_possible_nolock(std::string& raw_path);
|
||||
|
||||
void ramfs_demangler_on_fs_open(const std::string& path, AVS_FILE open_result) {
|
||||
if (open_result < 0 || !string_ends_with(path.c_str(), ".ifs")) {
|
||||
if (open_result < 0 || !path.ends_with(".ifs")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -162,12 +162,11 @@ void ramfs_demangler_on_fs_mount(const char* mountpoint, const char* fsroot, con
|
|||
cleanup->second.mounted_path = mountpoint;
|
||||
}
|
||||
}
|
||||
else if(string_ends_with(fsroot, ".ifs")) {
|
||||
else if(string root = fsroot; root.ends_with(".ifs")) {
|
||||
// this fixes ifs-inside-ifs by demangling the root location too
|
||||
string root = (string)fsroot;
|
||||
ramfs_demangler_demangle_if_possible_nolock(root);
|
||||
log_verbose("imagefs mount mapped to %s", root.c_str());
|
||||
mangling_map[mountpoint] = root;
|
||||
mangling_map[mountpoint] = root;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
60
src/tests.cpp
Normal file
60
src/tests.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "hook.h"
|
||||
#include "avs_standalone.hpp"
|
||||
#include "modpath_handler.h"
|
||||
|
||||
using ::testing::Contains;
|
||||
using ::testing::Optional;
|
||||
|
||||
class Environment : public ::testing::Environment {
|
||||
public:
|
||||
~Environment() override {}
|
||||
|
||||
// Override this to define how to set up the environment.
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(avs_standalone::boot(false));
|
||||
ASSERT_EQ(init(), 0);
|
||||
|
||||
config.mod_folder = "./testcases_data_mods";
|
||||
print_config();
|
||||
cache_mods();
|
||||
|
||||
ASSERT_THAT(available_mods(), Contains(config.mod_folder + "/empty"));
|
||||
}
|
||||
|
||||
// Override this to define how to tear down the environment.
|
||||
void TearDown() override {
|
||||
avs_standalone::shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
testing::Environment* const foo_env =
|
||||
testing::AddGlobalTestEnvironment(new Environment);
|
||||
|
||||
class DevModeOnOff : public testing::TestWithParam<bool> {
|
||||
void SetUp() override {
|
||||
config.developer_mode = GetParam();
|
||||
}
|
||||
void TearDown() override {
|
||||
config.developer_mode = false;
|
||||
}
|
||||
};
|
||||
INSTANTIATE_TEST_SUITE_P(DevModeOnOffInstance, DevModeOnOff, testing::Bool());
|
||||
|
||||
|
||||
TEST_P(DevModeOnOff, MissingFileNullopt) {
|
||||
ASSERT_EQ(find_first_modfile("doesn't exist"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST_P(DevModeOnOff, CaseInsensitiveFiles) {
|
||||
EXPECT_THAT(find_first_modfile("OhNo/oWo"), Optional(config.mod_folder + "/Case_Sensitive/OhNO/oWo"));
|
||||
EXPECT_THAT(find_first_modfile("ohno/owo"), Optional(config.mod_folder + "/Case_Sensitive/OhNO/oWo"));
|
||||
}
|
||||
|
||||
TEST_P(DevModeOnOff, CaseInsensitiveFolders) {
|
||||
EXPECT_THAT(find_first_modfolder("OhNO"), Optional(config.mod_folder + "/Case_Sensitive/OhNO/"));
|
||||
EXPECT_THAT(find_first_modfolder("ohno"), Optional(config.mod_folder + "/Case_Sensitive/OhNO/"));
|
||||
}
|
||||
|
|
@ -19,19 +19,6 @@ char* snprintf_auto(const char* fmt, ...) {
|
|||
return s;
|
||||
}
|
||||
|
||||
bool string_ends_with(const char * str, const char * suffix) {
|
||||
size_t str_len = strlen(str);
|
||||
size_t suffix_len = strlen(suffix);
|
||||
|
||||
return
|
||||
(str_len >= suffix_len) &&
|
||||
(0 == _stricmp(str + (str_len - suffix_len), suffix));
|
||||
}
|
||||
|
||||
bool string_ends_with(const std::string &str, const char * suffix) {
|
||||
return string_ends_with(str.c_str(), suffix);
|
||||
}
|
||||
|
||||
void string_replace(std::string &str, const char* from, const char* to) {
|
||||
auto to_len = strlen(to);
|
||||
auto from_len = strlen(from);
|
||||
|
|
@ -119,6 +106,35 @@ bool folder_exists(const char* name) {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::string path_to_actual_case(std::string path) {
|
||||
WIN32_FIND_DATAA ffd;
|
||||
HANDLE hFind;
|
||||
size_t start = std::string::npos;
|
||||
|
||||
while((start = path.rfind('/', start)) != string::npos) {
|
||||
hFind = FindFirstFileA(path.c_str(), &ffd);
|
||||
if (hFind == INVALID_HANDLE_VALUE) {
|
||||
continue;
|
||||
}
|
||||
FindClose(hFind);
|
||||
|
||||
auto segment = &path[start+1];
|
||||
size_t segment_len = strlen(segment);
|
||||
if(strcasecmp(segment, ffd.cFileName) == 0)
|
||||
memcpy(segment, ffd.cFileName, segment_len);
|
||||
|
||||
path[start] = '\0';
|
||||
}
|
||||
|
||||
// restore slashes
|
||||
for(auto& c: path) {
|
||||
if(c == '\0')
|
||||
c = '/';
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
void str_toupper_inline(std::string& str) {
|
||||
for (size_t i = 0; i < str.length(); i++) {
|
||||
str[i] = toupper(str[i]);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@
|
|||
#define lenof(x) (sizeof(x) / sizeof(*x))
|
||||
|
||||
char* snprintf_auto(const char* fmt, ...);
|
||||
bool string_ends_with(const char * str, const char * suffix);
|
||||
bool string_ends_with(const std::string &str, const char * suffix);
|
||||
// case insensitive
|
||||
void string_replace(std::string &str, const char* from, const char* to);
|
||||
// // case insensitive
|
||||
|
|
@ -24,6 +22,11 @@ wchar_t *str_widen(const char *src);
|
|||
void str_toupper_inline(std::string &str);
|
||||
bool file_exists(const char* name);
|
||||
bool folder_exists(const char* name);
|
||||
// the given path:
|
||||
// - must be known to exist
|
||||
// - must start with "/" or "./"
|
||||
// - must not end with "/"
|
||||
std::string path_to_actual_case(std::string path);
|
||||
std::vector<std::string> folders_in_folder(const char* root);
|
||||
uint64_t file_time(const char* path);
|
||||
LONG time(void);
|
||||
|
|
|
|||
16
subprojects/gtest.wrap
Normal file
16
subprojects/gtest.wrap
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[wrap-file]
|
||||
directory = googletest-1.15.2
|
||||
source_url = https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz
|
||||
source_filename = gtest-1.15.2.tar.gz
|
||||
source_hash = 7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926
|
||||
patch_filename = gtest_1.15.2-4_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.15.2-4/get_patch
|
||||
patch_hash = a5151324b97e6a98fa7a0e8095523e6d5c4bb3431210d6ac4ad9800c345acf40
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.15.2-4/gtest-1.15.2.tar.gz
|
||||
wrapdb_version = 1.15.2-4
|
||||
|
||||
[provide]
|
||||
gtest = gtest_dep
|
||||
gtest_main = gtest_main_dep
|
||||
gmock = gmock_dep
|
||||
gmock_main = gmock_main_dep
|
||||
3
test.sh
Normal file
3
test.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
meson test -C build64 --print-errorlogs "$@"
|
||||
0
testcases_data_mods/Case_Sensitive/OhNO/oWo
Normal file
0
testcases_data_mods/Case_Sensitive/OhNO/oWo
Normal file
0
testcases_data_mods/empty/.keep
Normal file
0
testcases_data_mods/empty/.keep
Normal file
1
xp_dlls/README.md
Normal file
1
xp_dlls/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Taken from my Jubeat cab's XP image
|
||||
BIN
xp_dlls/kernel32.dll
Normal file
BIN
xp_dlls/kernel32.dll
Normal file
Binary file not shown.
BIN
xp_dlls/msvcrt.dll
Normal file
BIN
xp_dlls/msvcrt.dll
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user