Skip to content
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

add support for WUHB file format #1190

Merged
merged 39 commits into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
745b086
WUHB WIP
goeiecool9999 Apr 24, 2024
271c7fc
remove unnecessary structs and make all BE
goeiecool9999 Apr 29, 2024
8e50c2a
more progress
goeiecool9999 Apr 29, 2024
28438c3
we're reading
goeiecool9999 Apr 29, 2024
8f16ea0
read meta/meta.ini
goeiecool9999 Apr 29, 2024
6555a1a
more work
goeiecool9999 Apr 30, 2024
03386e9
correctly resolve hash collisions
goeiecool9999 Apr 30, 2024
a8bcbd7
CMake tab->spaces
goeiecool9999 Apr 30, 2024
7b590ac
simplify hash traversal code
goeiecool9999 Apr 30, 2024
c127aed
simplify while loop condition
goeiecool9999 Apr 30, 2024
9016888
implement directory iterator
goeiecool9999 Apr 30, 2024
820da3e
decompress iconTex.tga.gz
goeiecool9999 Apr 30, 2024
b99d778
free wuhbreader on unmount
goeiecool9999 Apr 30, 2024
774c3bc
eliminate duplicate code with template
goeiecool9999 Apr 30, 2024
5e97248
remove unnecessary lambda expression
goeiecool9999 Apr 30, 2024
f4f1334
zlib utility: decompress directly to result vector
goeiecool9999 Apr 30, 2024
0bfb6dc
fix comment and formatting
goeiecool9999 Apr 30, 2024
37cd32d
fix mistake from conversion to template
goeiecool9999 Apr 30, 2024
1461e71
Merge branch 'refs/heads/main' into WUHB
goeiecool9999 Apr 30, 2024
d4d06f9
fix the way accessFlags are used
goeiecool9999 Apr 30, 2024
1ba4e31
remove redundant flag check
goeiecool9999 Apr 30, 2024
ed93c50
stylistic rejigging (clang-format and some manual cleanup)
goeiecool9999 May 1, 2024
09ef8e2
skip leading forward slash
goeiecool9999 May 1, 2024
d1bcee8
automatically advance read pointer and don't read past the end of a file
goeiecool9999 May 1, 2024
bce7293
add wuhb extension to load menu
goeiecool9999 May 1, 2024
a32cb39
don't allow reads past the end of the file
goeiecool9999 May 1, 2024
873b54c
rename function
goeiecool9999 May 2, 2024
08196ac
more graceful error handling. constify all reader functions
goeiecool9999 May 2, 2024
66f6784
full acronym
goeiecool9999 May 2, 2024
08ea5d4
fix compilation for non-linux (?)
goeiecool9999 May 2, 2024
bab58c1
do metadata the fast and easy way
goeiecool9999 May 3, 2024
cc9bd1d
move zlibdecompress to helpers.cpp
goeiecool9999 May 3, 2024
7539e10
reduce the amount of auto used, include own code using quotes
goeiecool9999 May 3, 2024
aec2171
show the right format in Title Manager
goeiecool9999 May 3, 2024
b298067
update TileInfo copy constructor fail condition
goeiecool9999 May 4, 2024
b1ed794
add WUHB to GetPrintPath
goeiecool9999 May 4, 2024
1ce9ea7
conform to the style guide
goeiecool9999 May 4, 2024
e1aad4d
put braces on their own line
goeiecool9999 May 4, 2024
e6aa785
double decompression chunk size each iteration, start at parameter
goeiecool9999 May 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Cafe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_library(CemuCafe
Filesystem/fscDeviceRedirect.cpp
Filesystem/fscDeviceWua.cpp
Filesystem/fscDeviceWud.cpp
Filesystem/fscDeviceWuhb.cpp
Filesystem/fsc.h
Filesystem/FST/FST.cpp
Filesystem/FST/FST.h
Expand All @@ -18,6 +19,9 @@ add_library(CemuCafe
Filesystem/FST/KeyCache.h
Filesystem/WUD/wud.cpp
Filesystem/WUD/wud.h
Filesystem/WUHB/RomFSStructs.h
Filesystem/WUHB/WUHBReader.cpp
Filesystem/WUHB/WUHBReader.h
GamePatch.cpp
GamePatch.h
GameProfile/GameProfile.cpp
Expand Down
40 changes: 40 additions & 0 deletions src/Cafe/Filesystem/WUHB/RomFSStructs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

struct romfs_header_t
{
uint32 header_magic;
uint32be header_size;
uint64be dir_hash_table_ofs;
uint64be dir_hash_table_size;
uint64be dir_table_ofs;
uint64be dir_table_size;
uint64be file_hash_table_ofs;
uint64be file_hash_table_size;
uint64be file_table_ofs;
uint64be file_table_size;
uint64be file_partition_ofs;
};

struct romfs_direntry_t
{
uint32be parent;
uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling")
uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child")
uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file")
uint32be hash;
uint32be name_size;
std::string name;
};

struct romfs_fentry_t
{
uint32be parent;
uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling")
uint64be offset;
uint64be size;
uint32be hash;
uint32be name_size;
std::string name;
};

#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF
224 changes: 224 additions & 0 deletions src/Cafe/Filesystem/WUHB/WUHBReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#include "WUHBReader.h"
WUHBReader* WUHBReader::FromPath(const fs::path& path)
{
FileStream* fileIn{FileStream::openFile2(path)};
if (!fileIn)
return nullptr;

WUHBReader* ret = new WUHBReader(fileIn);
if (!ret->CheckMagicValue())
{
delete ret;
return nullptr;
}

if (!ret->ReadHeader())
{
delete ret;
return nullptr;
}

return ret;
}

static const romfs_direntry_t fallbackDirEntry{
.parent = ROMFS_ENTRY_EMPTY,
.listNext = ROMFS_ENTRY_EMPTY,
.dirListHead = ROMFS_ENTRY_EMPTY,
.fileListHead = ROMFS_ENTRY_EMPTY,
.hash = ROMFS_ENTRY_EMPTY,
.name_size = 0,
.name = ""
};
static const romfs_fentry_t fallbackFileEntry{
.parent = ROMFS_ENTRY_EMPTY,
.listNext = ROMFS_ENTRY_EMPTY,
.offset = 0,
.size = 0,
.hash = ROMFS_ENTRY_EMPTY,
.name_size = 0,
.name = ""
};
template<bool File>
const WUHBReader::EntryType<File>& WUHBReader::GetFallback()
{
if constexpr (File)
return fallbackFileEntry;
else
return fallbackDirEntry;
}

template<bool File>
WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const
{
auto fallback = GetFallback<File>();
if(offset == ROMFS_ENTRY_EMPTY)
return fallback;

const char* typeName = File ? "fentry" : "direntry";
EntryType<File> ret;
if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size))
{
cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName);
return fallback;
}

// read the entry
m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset);
auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name));
if (read != offsetof(EntryType<File>, name))
{
cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset);
return fallback;
}

// read the name
ret.name.resize(ret.name_size);
read = m_fileIn->readData(ret.name.data(), ret.name_size);
if (read != ret.name_size)
{
cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName);
return fallback;
}

return ret;
}

romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const
{
return GetEntry<false>(offset);
}
romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const
{
return GetEntry<true>(offset);
}

uint64 WUHBReader::GetFileSize(uint32 entryOffset) const
{
return GetFileEntry(entryOffset).size;
}

uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const
{
const auto fileEntry = GetFileEntry(entryOffset);
if (fileOffset >= fileEntry.size)
return 0;
const uint64 readAmount = std::min(length, fileEntry.size - fileOffset);
const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset;
m_fileIn->SetPosition(wuhbOffset);
return m_fileIn->readData(buffer, readAmount);
}

uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const
{
const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size);
const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs);

const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32);
const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32);

m_fileIn->SetPosition(hash_table_entry_offset);
uint32 tableOffset;
if (!m_fileIn->readU32(tableOffset))
{
cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset);
return ROMFS_ENTRY_EMPTY;
}

return uint32be::from_bevalue(tableOffset);
}

template<bool T>
bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const
{
for (;;)
{
if (entryOffset == ROMFS_ENTRY_EMPTY)
return false;
auto entry = GetEntry<T>(entryOffset);

if (entry.name == targetName)
return true;
entryOffset = entry.hash;
}
return false;
}

uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const
{
uint32 currentEntryOffset = 0;
auto look = [&](const fs::path& part, bool lookInFileHT) {
const auto partString = part.string();
currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT);
if (lookInFileHT)
return SearchHashList<true>(currentEntryOffset, part);
else
return SearchHashList<false>(currentEntryOffset, part);
};
// look for the root entry
if (!look("", false))
return ROMFS_ENTRY_EMPTY;

auto it = path.begin();
while (it != path.end())
{
fs::path part = *it;
++it;
// no need to recurse after trailing forward slash (e.g. directory/)
if (part.empty() && !isFile)
break;
// skip leading forward slash
if (part == "/")
continue;

// if the lookup target is a file and this is the last iteration, look in the file hash table instead.
if (!look(part, it == path.end() && isFile))
return ROMFS_ENTRY_EMPTY;
}
return currentEntryOffset;
}
bool WUHBReader::CheckMagicValue() const
{
uint8 magic[4];
m_fileIn->SetPosition(0);
int read = m_fileIn->readData(magic, 4);
if (read != 4)
{
cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers");
return false;
}
static_assert(sizeof(magic) == s_headerMagicValue.size());
return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0;
}
bool WUHBReader::ReadHeader()
{
m_fileIn->SetPosition(0);
auto read = m_fileIn->readData(&m_header, sizeof(m_header));
auto readSuccess = read == sizeof(m_header);
if (!readSuccess)
cemuLog_log(LogType::Force, "Failed to read WUHB header");
return readSuccess;
}
unsigned char WUHBReader::NormalizeChar(unsigned char c)
{
if (c >= 'a' && c <= 'z')
{
return c + 'A' - 'a';
}
else
{
return c;
}
}
uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len)
{
cemu_assert(path != nullptr || path_len == 0);
uint32 hash = parent ^ 123456789;
for (uint32 i = 0; i < path_len; i++)
{
hash = (hash >> 5) | (hash << 27);
hash ^= NormalizeChar(path[start + i]);
}

return hash;
}
45 changes: 45 additions & 0 deletions src/Cafe/Filesystem/WUHB/WUHBReader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once
#include <Common/FileStream.h>
#include "RomFSStructs.h"
class WUHBReader
{
public:
static WUHBReader* FromPath(const fs::path& path);

romfs_direntry_t GetDirEntry(uint32 offset) const;
romfs_fentry_t GetFileEntry(uint32 offset) const;

uint64 GetFileSize(uint32 entryOffset) const;

uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const;

uint32 Lookup(const std::filesystem::path& path, bool isFile) const;

private:
WUHBReader(FileStream* file)
: m_fileIn(file)
{
cemu_assert_debug(file != nullptr);
};
WUHBReader() = delete;

romfs_header_t m_header;
std::unique_ptr<FileStream> m_fileIn;
constexpr static std::string_view s_headerMagicValue = "WUHB";
bool ReadHeader();
bool CheckMagicValue() const;

static inline unsigned char NormalizeChar(unsigned char c);
static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len);

template<bool File>
using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>;
template<bool File>
static const EntryType<File>& GetFallback();
template<bool File>
EntryType<File> GetEntry(uint32 offset) const;

template<bool T>
bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const;
uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const;
};
3 changes: 3 additions & 0 deletions src/Cafe/Filesystem/fsc.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination
// wua device
bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);

// wuhb device
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority);

// hostFS device
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority);

Expand Down
Loading
Loading