Skip to content
This repository was archived by the owner on Jul 19, 2024. It is now read-only.

Commit 4955d59

Browse files
authored
Merge pull request #166 from vswarte/feat/wwise-file-location-resolver-hook
Feat/wwise file location resolver hook
2 parents 0924ab3 + b34fa30 commit 4955d59

File tree

7 files changed

+141
-5
lines changed

7 files changed

+141
-5
lines changed

Diff for: .github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,4 @@ jobs:
9999
with:
100100
name: 'modengine-${{ github.sha }}'
101101
path: '${{ env.CMAKE_BUILD_DIR }}/ModEngine-*-win64.*' # FIXME gtierney: run-cmake isn't respecting CMAKE_INSTALL_PREFIX
102-
if: ${{ github.event_name == 'push' }}
102+
if: ${{ github.event_name == 'push' }}

Diff for: src/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ add_library(modengine2 SHARED
5050
modengine/ext/debug_menu/ds3/_DS3GameProperties.asm
5151
modengine/ext/mod_loader/archive_file_overrides.cpp
5252
modengine/ext/mod_loader/mod_loader_extension.cpp
53+
modengine/ext/mod_loader/wwise_file_overrides.cpp
5354
modengine/ext/profiling/profiling_extension.cpp
5455
modengine/ext/profiling/profiler_trampoline.asm
5556
modengine/ext/profiling/main_loop.cpp

Diff for: src/modengine/ext/mod_loader/archive_file_overrides.cpp

+1-3
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ namespace modengine::ext {
4242
// the easiest and most robust way to do this. It also has the bonus of allowing files that don't go through the asset system
4343
// (like the data archives and some of the sounds) to be overridable.
4444

45-
namespace fs = std::filesystem;
46-
4745
concurrency::concurrent_unordered_map<std::wstring, std::optional<std::filesystem::path>> archive_override_paths;
4846
concurrency::concurrent_unordered_map<std::wstring, std::optional<std::filesystem::path>> file_override_paths;
4947

@@ -241,4 +239,4 @@ void* __cdecl virtual_to_archive_path_eldenring(DLString<modengine::GameType::SE
241239
return res;
242240
}
243241

244-
}
242+
}

Diff for: src/modengine/ext/mod_loader/archive_file_overrides.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
#include "gametypes/dantelion/dlstring.h"
55

66
#include <set>
7+
#include <filesystem>
78
#include <concurrent_vector.h>
89

910
namespace modengine::ext {
1011

12+
namespace fs = std::filesystem;
13+
1114
// String type used in DS2/DS3
1215
typedef struct
1316
{
@@ -43,4 +46,6 @@ HANDLE WINAPI tCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSh
4346
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
4447
HANDLE hTemplateFile);
4548

46-
}
49+
std::optional<fs::path> find_override_file(const fs::path& game_path);
50+
51+
}

Diff for: src/modengine/ext/mod_loader/mod_loader_extension.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "mod_loader_extension.h"
22
#include "archive_file_overrides.h"
3+
#include "wwise_file_overrides.h"
34

45
#include "modengine/util/hex_string.h"
56
#include "modengine/util/platform.h"
@@ -18,6 +19,8 @@ auto loose_params_aob_2 = util::hex_string("0F 85 C5 00 00 00 48 8D 4C 24 28");
1819
auto virtual_to_archive_path_er_aob = util::hex_aob("e8 ?? ?? ?? ?? 48 83 7b 20 08 48 8d 4b 08 72 03 48 8b 09 4c 8b 4b 18 41 b8 05 00 00 00 4d 3b c8");
1920
auto virtual_to_archive_path_ac6_aob = util::hex_aob("cf e8 ?? ?? ?? ?? 48 83 7b 20 08 48 8d 4b 08 72 03 48 8b 09 4c 8b 4b 18 41 b8 05 00 00 00 4d 3b c8");
2021

22+
auto ak_file_location_resolver_open_aob = util::hex_aob("4c 89 74 24 28 48 8b 84 24 90 00 00 00 48 89 44 24 20 4c 8b ce 45 8b c4 49 8b d7 48 8b cd e8 ?? ?? ?? ?? 8b d8");
23+
2124
static fs::path primary_mod_path(const Settings& settings)
2225
{
2326
return settings.modengine_local_path();
@@ -60,6 +63,8 @@ void ModLoaderExtension::on_attach()
6063
register_hook(DS3, &hooked_virtual_to_archive_path_ds3, util::rva2addr(0x7d660), virtual_to_archive_path_ds3);
6164
register_hook(ELDEN_RING, &hooked_virtual_to_archive_path_eldenring, virtual_to_archive_path_er_aob, 0x0, virtual_to_archive_path_eldenring, SCAN_CALL_INST);
6265
register_hook(ARMORED_CORE_6, &hooked_virtual_to_archive_path_eldenring, virtual_to_archive_path_ac6_aob, 0x1, virtual_to_archive_path_eldenring, SCAN_CALL_INST);
66+
register_hook(ELDEN_RING, &hooked_ak_file_location_resolver_open, ak_file_location_resolver_open_aob, 0x1E, ak_file_location_resolver_open, SCAN_CALL_INST);
67+
register_hook(ARMORED_CORE_6, &hooked_ak_file_location_resolver_open, ak_file_location_resolver_open_aob, 0x1E, ak_file_location_resolver_open, SCAN_CALL_INST);
6368

6469
auto config = get_config<ModLoaderConfig>();
6570
for (const auto& mod : config.mods) {
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#include <filesystem>
2+
3+
#include "wwise_file_overrides.h"
4+
#include "archive_file_overrides.h"
5+
6+
namespace modengine::ext {
7+
8+
// This hook the IAkFileLocationResolver::open() method. It takes in a path
9+
// and a so-called openMode. This openMode parameter is of type AkOpenMode
10+
// which is an enum with 4 states: Read, Write, WriteOverwrite and ReadWrite.
11+
// Passing in any of these 4 invariants will cause this function to yield a
12+
// FileOperator used for sourcing the file bytes. FromSoftware added another
13+
// possible state to AKOpenMode, decimal 9. which will make this fn yield an
14+
// EBLFileOperator, this implementation does all its fetching from the BDTs.
15+
// EBLFileOperator does not use the usual virtual path lookup that ME2 already
16+
// hooks. Hence we need this hook here.
17+
//
18+
// In order to selectively make it read from disk again this hook checks if an
19+
// override file exists and sets the openMode back from 9 to Read and replaces
20+
// the virtual path parameter string with an absolute path.
21+
//
22+
// This is a messy one though:
23+
// Figuring out if there is an override isn't straight forward. The hooked
24+
// function gets invoked with a virtual path string ex: `sd:/50846376.wem`.
25+
// Wwise uses subdirectories for localized content, meaning that the "simple"
26+
// WEM example makes Wwise look in `/50846376.wem` but also in
27+
// `enus/50846376.wem` (or `ja/50846376.wem` for AC6 which has `ja` as an extra
28+
// locale for some audio and BNKs). To make matters even worse, Elden Ring
29+
// specifically sorts the WEMs into a subdir (`wem/`) and then another subdir
30+
// based on the first two digits of the WEM. So above example will spawn
31+
// lookups in `/wem/50/50846376.wem` and `enus/wem/50/50846376.wem`. In order
32+
// to figure out if there is an override we will need to look in multiple
33+
// directories per request. Luckily, aside from boot, this routine is called
34+
// quite infrequently.
35+
//
36+
// Also, worth pointing out that not all paths passed to this hook will have
37+
// the `sd:/` prefix. So we cannot get away with the usual prefix / rewrite
38+
// trick and will have to allocate a completely new string.
39+
40+
namespace fs = std::filesystem;
41+
42+
using namespace spdlog;
43+
44+
const wchar_t* prefixes[3] = {
45+
L"sd/",
46+
L"sd/enus/",
47+
L"sd/ja/",
48+
};
49+
50+
std::optional<fs::path> check_paths(const std::wstring filename) {
51+
for (auto prefix: prefixes) {
52+
if (auto override = find_override_file(prefix+filename)) {
53+
return override;
54+
}
55+
}
56+
57+
return {};
58+
}
59+
60+
std::optional<fs::path> find_override(const std::wstring filename)
61+
{
62+
// Check wem/<first to digits of filename>/<filename> too since ER uses
63+
// this format
64+
if (filename.ends_with(L".wem")) {
65+
auto wem_path = L"wem/" + filename.substr(0,2) + L"/" + filename;
66+
auto wem_path_result = check_paths(wem_path);
67+
68+
if (wem_path_result.has_value()) {
69+
return wem_path_result;
70+
}
71+
}
72+
73+
return check_paths(filename);
74+
}
75+
76+
std::optional<std::wstring> normalize_filename(const std::wstring path) {
77+
if (path.starts_with(L"sd:/")) {
78+
return std::wstring(path.substr(4));
79+
}
80+
81+
return {};
82+
}
83+
84+
ScannedHook<decltype(&ak_file_location_resolver_open)> hooked_ak_file_location_resolver_open;
85+
86+
void* __cdecl ak_file_location_resolver_open(UINT64 p1, wchar_t* path, AKOpenMode openMode, UINT64 p4, UINT64 p5, UINT64 p6)
87+
{
88+
std::wstring lookup(path);
89+
debug(L"sd.bhd entry requested: {}", lookup);
90+
91+
auto normalized = normalize_filename(lookup);
92+
if (!normalized.has_value()) {
93+
return hooked_ak_file_location_resolver_open.original(p1, path, openMode, p4, p5, p6);
94+
}
95+
96+
auto filename = normalized.value();
97+
auto override = find_override(filename);
98+
if (!override.has_value()) {
99+
return hooked_ak_file_location_resolver_open.original(p1, path, openMode, p4, p5, p6);
100+
}
101+
102+
auto override_path_string = override.value().wstring();
103+
return hooked_ak_file_location_resolver_open.original(p1, override_path_string.data(), AKOpenMode::READ, p4, p5, p6);
104+
}
105+
106+
}

Diff for: src/modengine/ext/mod_loader/wwise_file_overrides.h

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include "modengine/hook_set.h"
4+
5+
namespace modengine::ext {
6+
7+
enum AKOpenMode : uint32_t {
8+
READ = 0,
9+
WRITE = 1,
10+
WRITE_OVERWRITE = 2,
11+
READ_WRITE = 3,
12+
13+
// Custom mode specific to From Software's implementation
14+
READ_EBL = 9,
15+
};
16+
17+
void* __cdecl ak_file_location_resolver_open(UINT64 p1, wchar_t* path, AKOpenMode openMode, UINT64 p4, UINT64 p5, UINT64 p6);
18+
19+
extern ScannedHook<decltype(&ak_file_location_resolver_open)> hooked_ak_file_location_resolver_open;
20+
21+
}

0 commit comments

Comments
 (0)