-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Loading assets from disk #22
Draft
vswarte
wants to merge
27
commits into
garyttierney:main
Choose a base branch
from
vswarte:feat/vfs-hook
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
bc9c422
Horror
vswarte 2929723
Set up CXX for DLString manipulation
vswarte 9de609f
Change approach around DLString operations
vswarte f19365d
Modify C++ DLWString end
Dasaav-dsv 6f298d7
Use wide OSString as Vec<u16>
Dasaav-dsv 87a5c33
Adapt to new C++ end
Dasaav-dsv 7076c76
Include new C++ headers
Dasaav-dsv 9c3711e
Merge pull request #3 from Dasaav-dsv/feat/vfs-hook
vswarte 1a267d3
WIP
vswarte 14cdafb
Cleanup, added some tests
vswarte 3724dbf
Use #pragma once instead
vswarte ba5286e
Inline == operator
vswarte e9630da
Remove stale debug
vswarte 8b43b3f
Use RTTI to acquire hooking location
vswarte 7e4a56d
Side-by-side me2 and me3 hooks
vswarte 27d0f2a
Clean some code round the asset hook
vswarte 8ca44d2
Drop unused binary_analysis crate again
vswarte bf24173
Remove unused RSResourceFileRequest, cleanup minor nit
vswarte dda9878
More cleanup
vswarte 49fabb2
Un-public the package sources
vswarte 8d513e3
Restore 5s startup timeout
vswarte b3dabfd
Rust fmt
vswarte 157c7bf
Use tracing
vswarte 80a16de
Remove unused windows imports
vswarte 1f32785
Revert "Drop unused binary_analysis crate again"
vswarte 2903466
Update for 1.14.0
vswarte 0f5624c
Binary mapper poc
vswarte File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "me3-mod-host-assets" | ||
version.workspace = true | ||
edition.workspace = true | ||
repository.workspace = true | ||
license.workspace = true | ||
|
||
[dependencies] | ||
cxx = { version = "1.0", features = ["c++17"] } | ||
thiserror.workspace = true | ||
me3-mod-protocol.workspace = true | ||
|
||
[build-dependencies] | ||
cxx-build = "1.0" | ||
|
||
[lints] | ||
workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
fn main() { | ||
cxx_build::bridge("src/lib.rs") | ||
.include("include") | ||
.compile("test"); | ||
|
||
println!("cargo:rerun-if-changed=src/lib.rs"); | ||
println!("cargo:rerun-if-changed=include/dl_allocator.hpp"); | ||
println!("cargo:rerun-if-changed=include/dl_string_bridge.hpp"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#pragma once | ||
|
||
#include <cstdint> | ||
#include <type_traits> | ||
|
||
namespace DLKR { | ||
class DLAllocationInterface { | ||
public: | ||
virtual ~DLAllocationInterface() = default; | ||
virtual uint32_t GetAllocatorId() = 0; | ||
virtual int32_t _unk0x10() = 0; | ||
virtual uint32_t& GetHeapFlags(uint32_t& out) = 0; | ||
virtual uint64_t GetHeapCapacity() = 0; | ||
virtual uint64_t GetHeapSize() = 0; | ||
virtual uint64_t GetBackingHeapCapacity() = 0; | ||
virtual uint64_t GetAllocationCount() = 0; | ||
virtual uint64_t GetSizeOfAllocation(void* pAllocation) = 0; | ||
virtual void* AllocateMemory(uint64_t sizeBytes) = 0; | ||
virtual void* AllocateAlignedMemory(uint64_t sizeBytes, uint64_t alignment) = 0; | ||
virtual void* ReallocateMemory(void* pAllocation, uint64_t sizeBytes) = 0; | ||
virtual void* ReallocateAlignedMemory(void* pAllocation, uint64_t sizeBytes, uint64_t alignment) = 0; | ||
virtual void FreeMemory(void* pAllocation) = 0; | ||
}; | ||
|
||
template <typename T> | ||
class DLAllocatorAdapter { | ||
public: | ||
using value_type = T; | ||
using size_type = uint64_t; | ||
using difference_type = int64_t; | ||
|
||
using propagate_on_container_move_assignment = std::true_type; | ||
using is_always_equal = std::false_type; | ||
|
||
template <typename U> | ||
DLAllocatorAdapter(const DLAllocatorAdapter<U>& other) noexcept : allocator(other.allocator) {} | ||
|
||
T* allocate(size_type count) { | ||
return reinterpret_cast<T*>(allocator.AllocateAlignedMemory(count * sizeof(T), alignof(T))); | ||
} | ||
|
||
void deallocate(T* pAllocation, size_type count = 0) { | ||
allocator.FreeMemory(reinterpret_cast<void*>(pAllocation)); | ||
} | ||
|
||
template <typename T1, typename T2> | ||
friend bool operator==(const DLAllocatorAdapter<T1>& lhs, const DLAllocatorAdapter<T2>& rhs) noexcept { | ||
return &lhs.allocator == &rhs.allocator; | ||
} | ||
|
||
private: | ||
DLAllocationInterface& allocator; | ||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#pragma once | ||
|
||
#include <string> | ||
#include <rust/cxx.h> | ||
#include "dl_allocator.hpp" | ||
|
||
#if defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0 | ||
#error "_ITERATOR_DEBUG_LEVEL" must be defined as "0" for STL containers to be compatible with the ELDEN RING ABI. | ||
#endif | ||
|
||
template <typename CharT> | ||
struct DLBasicString { | ||
mutable std::basic_string<CharT, std::char_traits<CharT>, DLKR::DLAllocatorAdapter<CharT>> inner; | ||
bool _unk0x28; | ||
}; | ||
|
||
using DLWString = DLBasicString<char16_t>; | ||
|
||
rust::string get_dlwstring_contents(const DLWString& str) noexcept { | ||
return rust::string(str.inner.data(), str.inner.length()); | ||
} | ||
|
||
void set_dlwstring_contents(const DLWString& str, rust::slice<const uint16_t> contents) noexcept { | ||
const char16_t* first = reinterpret_cast<const char16_t*>(contents.data()); | ||
size_t length = contents.size(); | ||
str.inner.assign(first, length); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use std::{error::Error, fs::File, sync::{OnceLock, RwLock}}; | ||
|
||
use ffi::DLWString; | ||
|
||
pub mod mapping; | ||
|
||
#[cxx::bridge] | ||
pub mod ffi { | ||
unsafe extern "C++" { | ||
include!("dl_string_bridge.hpp"); | ||
|
||
pub type DLWString; | ||
|
||
pub fn get_dlwstring_contents(string: &DLWString) -> String; | ||
pub fn set_dlwstring_contents(string: &DLWString, contents: &[u16]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
use std::{ | ||
collections::{HashMap, VecDeque}, | ||
fs, | ||
os::windows::ffi::OsStrExt, | ||
path::{Path, PathBuf, StripPrefixError}, | ||
}; | ||
|
||
use me3_mod_protocol::package::AssetOverrideSource; | ||
use thiserror::Error; | ||
|
||
#[derive(Debug, Default)] | ||
pub struct ArchiveOverrideMapping { | ||
map: HashMap<String, Vec<u16>>, | ||
} | ||
|
||
#[derive(Debug, Error)] | ||
pub enum ArchiveOverrideMappingError { | ||
#[error("Package source specified is not a directory {0}.")] | ||
InvalidDirectory(PathBuf), | ||
|
||
#[error("Could not read directory while discovering override assets {0}")] | ||
ReadDir(std::io::Error), | ||
|
||
#[error("Could not acquire directory entry")] | ||
DirEntryAcquire(std::io::Error), | ||
|
||
#[error("Could not acquire directory entry")] | ||
StripPrefix(#[from] StripPrefixError), | ||
} | ||
|
||
impl ArchiveOverrideMapping { | ||
pub fn scan_directories<I, S>(&mut self, sources: I) -> Result<(), ArchiveOverrideMappingError> | ||
where | ||
I: Iterator<Item = S>, | ||
S: AssetOverrideSource, | ||
{ | ||
sources | ||
.map(|p| self.scan_directory(p.asset_path())) | ||
.collect::<Result<Vec<_>, _>>()?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Traverses a folder structure, mapping discovered assets into itself. | ||
fn scan_directory<P: AsRef<Path>>( | ||
&mut self, | ||
base_directory: P, | ||
) -> Result<(), ArchiveOverrideMappingError> { | ||
let base_directory = normalize_path(base_directory.as_ref()); | ||
if (!base_directory.is_dir()) { | ||
return Err(ArchiveOverrideMappingError::InvalidDirectory( | ||
base_directory.clone(), | ||
)); | ||
} | ||
|
||
// Keep a dequeue to push yet-to-be-scanned paths so we can avoid | ||
// recursion. | ||
let mut paths_to_scan = VecDeque::from(vec![base_directory.clone()]); | ||
while let Some(current_path) = paths_to_scan.pop_front() { | ||
for dir_entry in | ||
fs::read_dir(current_path).map_err(ArchiveOverrideMappingError::ReadDir)? | ||
{ | ||
let dir_entry = dir_entry | ||
.map_err(ArchiveOverrideMappingError::DirEntryAcquire)? | ||
.path(); | ||
|
||
// Push to the path deque for later scanning, if it's a folder | ||
// add it to the mapping otherwise. | ||
if dir_entry.is_dir() { | ||
paths_to_scan.push_back(dir_entry); | ||
} else { | ||
let override_path = normalize_path(dir_entry); | ||
let vfs_path = path_to_asset_lookup_key(&base_directory, &override_path)?; | ||
|
||
self.map.insert( | ||
vfs_path, | ||
override_path.into_os_string().encode_wide().collect(), | ||
); | ||
} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn get_override(&self, path: &str) -> Option<&[u16]> { | ||
let key = path.split_once(":/").map(|r| r.1).unwrap_or(path); | ||
|
||
self.map.get(key).map(|v| &v[..]) | ||
} | ||
} | ||
|
||
/// Normalizes paths to use / as a path seperator. | ||
fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf { | ||
PathBuf::from(path.as_ref().to_string_lossy().replace('\\', "/")) | ||
} | ||
|
||
/// Turns an asset path into an asset lookup key using the mods base path. | ||
fn path_to_asset_lookup_key<P: AsRef<Path>>(base: P, path: P) -> Result<String, StripPrefixError> { | ||
path.as_ref() | ||
.strip_prefix(base) | ||
.map(|p| p.to_string_lossy().to_lowercase()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use std::{ | ||
collections::HashMap, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use crate::mapping::{path_to_asset_lookup_key, ArchiveOverrideMapping}; | ||
|
||
#[test] | ||
fn asset_path_lookup_keys() { | ||
const FAKE_MOD_BASE: &str = "D:/ModBase/"; | ||
let base_path = PathBuf::from(FAKE_MOD_BASE); | ||
|
||
assert_eq!( | ||
path_to_asset_lookup_key( | ||
&base_path, | ||
&PathBuf::from(format!( | ||
"{FAKE_MOD_BASE}/parts/aet/aet007/aet007_071.tpf.dcx" | ||
)), | ||
) | ||
.unwrap(), | ||
"parts/aet/aet007/aet007_071.tpf.dcx", | ||
); | ||
|
||
assert_eq!( | ||
path_to_asset_lookup_key( | ||
&base_path, | ||
&PathBuf::from(format!( | ||
"{FAKE_MOD_BASE}/hkxbnd/m60_42_36_00/h60_42_36_00_423601.hkx.dcx" | ||
)), | ||
) | ||
.unwrap(), | ||
"hkxbnd/m60_42_36_00/h60_42_36_00_423601.hkx.dcx", | ||
); | ||
|
||
assert_eq!( | ||
path_to_asset_lookup_key( | ||
&base_path, | ||
&PathBuf::from(format!("{FAKE_MOD_BASE}/regulation.bin")), | ||
) | ||
.unwrap(), | ||
"regulation.bin", | ||
); | ||
} | ||
|
||
#[test] | ||
fn scan_directory_and_overrides() { | ||
let mut asset_mapping = ArchiveOverrideMapping::default(); | ||
|
||
let test_mod_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("test-data/test-mod"); | ||
asset_mapping.scan_directory(test_mod_dir).unwrap(); | ||
|
||
assert!( | ||
asset_mapping | ||
.get_override("data0:/regulation.bin") | ||
.is_some(), | ||
"override for regulation.bin was not found" | ||
); | ||
assert!( | ||
asset_mapping | ||
.get_override("data0:/event/common.emevd.dcx") | ||
.is_some(), | ||
"override for event/common.emevd.dcx not found" | ||
); | ||
assert!( | ||
asset_mapping | ||
.get_override("data0:/common.emevd.dcx") | ||
.is_none(), | ||
"event/common.emevd.dcx was found incorrectly under the regulation root" | ||
); | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
crates/mod-host-assets/test-data/test-mod/event/common.emevd.dcx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Empty file for testing override discovery. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Empty file for testing override discovery. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you share the config this code is causing problems with?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.