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

Feat/wwise file location resolver hook #166

Merged
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ jobs:
with:
name: 'modengine-${{ github.sha }}'
path: '${{ env.CMAKE_BUILD_DIR }}/ModEngine-*-win64.*' # FIXME gtierney: run-cmake isn't respecting CMAKE_INSTALL_PREFIX
if: ${{ github.event_name == 'push' }}
if: ${{ github.event_name == 'push' }}
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ add_library(modengine2 SHARED
modengine/ext/debug_menu/ds3/_DS3GameProperties.asm
modengine/ext/mod_loader/archive_file_overrides.cpp
modengine/ext/mod_loader/mod_loader_extension.cpp
modengine/ext/mod_loader/wwise_file_overrides.cpp
modengine/ext/profiling/profiling_extension.cpp
modengine/ext/profiling/profiler_trampoline.asm
modengine/ext/profiling/main_loop.cpp
Expand Down
5 changes: 5 additions & 0 deletions src/modengine/ext/mod_loader/mod_loader_extension.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "mod_loader_extension.h"
#include "archive_file_overrides.h"
#include "wwise_file_overrides.h"

#include "modengine/util/hex_string.h"
#include "modengine/util/platform.h"
Expand All @@ -18,6 +19,8 @@ auto loose_params_aob_2 = util::hex_string("0F 85 C5 00 00 00 48 8D 4C 24 28");
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");
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");

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");

static fs::path primary_mod_path(const Settings& settings)
{
return settings.modengine_local_path();
Expand Down Expand Up @@ -60,6 +63,8 @@ void ModLoaderExtension::on_attach()
register_hook(DS3, &hooked_virtual_to_archive_path_ds3, util::rva2addr(0x7d660), virtual_to_archive_path_ds3);
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);
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);
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);
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);

auto config = get_config<ModLoaderConfig>();
for (const auto& mod : config.mods) {
Expand Down
120 changes: 120 additions & 0 deletions src/modengine/ext/mod_loader/wwise_file_overrides.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include <filesystem>

#include "wwise_file_overrides.h"
#include "archive_file_overrides.h"

namespace modengine::ext {

// This hook the IAkFileLocationResolver::open() method. It takes in a path
// and a so-called openMode. This openMode parameter is of type AkOpenMode
// which is an enum with 4 states: Read, Write, WriteOverwrite and ReadWrite.
// Passing in any of these 4 invariants will cause this function to yield a
// FileOperator used for sourcing the file bytes. FromSoftware added another
// possible state to AKOpenMode, decimal 9. which will make this fn yield an
// EBLFileOperator, this implementation does all its fetching from the BDTs.
// EBLFileOperator does not use the usual virtual path lookup that ME2 already
// hooks. Hence we need this hook here.
//
// In order to selectively make it read from disk again this hook checks if an
// override file exists and sets the openMode back from 9 to Read and replaces
// the virtual path parameter string with an absolute path.
//
// This is a messy one though:
// Figuring out if there is an override isn't straight forward. The hooked
// function gets invoked with a virtual path string ex: `sd:/50846376.wem`.
// Wwise uses subdirectories for localized content, meaning that the "simple"
// WEM example makes Wwise look in `/50846376.wem` but also in
// `enus/50846376.wem` (or `ja/50846376.wem` for AC6 which has `ja` as an extra
// locale for some audio and BNKs). To make matters even worse, Elden Ring
// specifically sorts the WEMs into a subdir (`wem/`) and then another subdir
// based on the first two digits of the WEM. So above example will spawn
// lookups in `/wem/50/50846376.wem` and `enus/wem/50/50846376.wem`. In order
// to figure out if there is an override we will need to look in multiple
// directories per request. Luckily, aside from boot, this routine is called
// quite infrequently.
//
// Also, worth pointing out that not all paths passed to this hook will have
// the `sd:/` prefix. So we cannot get away with the usual prefix / rewrite
// trick and will have to allocate a completely new string.

namespace fs = std::filesystem;

using namespace spdlog;

std::optional<fs::path> find_override_file(const fs::path game_path)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is identical to the code in archive_file_overrides.cpp, right? If so I'd just move it to mod_loader_extension.cpp and share the code between them to prevent losing some debugging time to slight drift in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per discussed in DMs I've removed the one in the wwise specific part and put a forward decl in the archive_file_overrides since the wwise_file_overrides.cpp -> archive_file_overrides.h dependency was already there

Should be resolved per b34fa30

{
for (const auto& root : hooked_file_roots) {
trace(L"Searching for {} in {}", game_path.wstring(), root);

auto file_path = root / fs::path(game_path);
if (fs::exists(file_path)) {
return file_path;
}
}

return {};
}

const wchar_t* prefixes[3] = {
L"sd/",
L"sd/enus/",
L"sd/ja/",
};

std::optional<fs::path> check_paths(const std::wstring filename) {
for (auto prefix: prefixes) {
if (auto override = find_override_file(prefix+filename)) {
return override;
}
}

return {};
}

std::optional<fs::path> find_override(const std::wstring filename)
{
// Check wem/<first to digits of filename>/<filename> too since ER uses
// this format
if (filename.ends_with(L".wem")) {
auto wem_path = L"wem/" + filename.substr(0,2) + L"/" + filename;
auto wem_path_result = check_paths(wem_path);

if (wem_path_result.has_value()) {
return wem_path_result;
}
}

return check_paths(filename);
}

std::optional<std::wstring> normalize_filename(const std::wstring path) {
if (path.length() > 3 && path.substr(0, 4) == L"sd:/") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be path.starts_with(...) from C++20? I think ME2 is targeting C++20 nowadays.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be resolved per 8a857f8

return std::wstring(path.substr(4));
}

return {};
}

ScannedHook<decltype(&ak_file_location_resolver_open)> hooked_ak_file_location_resolver_open;

void* __cdecl ak_file_location_resolver_open(UINT64 p1, wchar_t* path, UINT64 openMode, UINT64 p4, UINT64 p5, UINT64 p6)
{
std::wstring lookup(path);
debug(L"sd.bhd entry requested: {}", lookup);

auto normalized = normalize_filename(lookup);
if (!normalized.has_value()) {
return hooked_ak_file_location_resolver_open.original(p1, path, openMode, p4, p5, p6);
}

auto filename = normalized.value();
auto override = find_override(filename);
if (!override.has_value()) {
return hooked_ak_file_location_resolver_open.original(p1, path, openMode, p4, p5, p6);
}

auto override_path_string = override.value().wstring();
return hooked_ak_file_location_resolver_open.original(p1, override_path_string.data(), 0, p4, p5, p6);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make an AkOpenMode enum reflecting the documentation up top just so it's obvious this 0 is Read?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be resolved per fb2044a

}

}
11 changes: 11 additions & 0 deletions src/modengine/ext/mod_loader/wwise_file_overrides.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include "modengine/hook_set.h"

namespace modengine::ext {

void* __cdecl ak_file_location_resolver_open(UINT64 p1, wchar_t* path, UINT64 p3, UINT64 p4, UINT64 p5, UINT64 p6);

extern ScannedHook<decltype(&ak_file_location_resolver_open)> hooked_ak_file_location_resolver_open;

}