Skip to content

Commit 78a4c63

Browse files
committed
read assets from ucas file
1 parent 3245998 commit 78a4c63

5 files changed

Lines changed: 141 additions & 3 deletions

File tree

libsave/include/SatisfactorySave/GameTypes/UE/Core/IO/IoStore.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ namespace SatisfactorySave {
6565
struct SATISFACTORYSAVE_API FIoStoreTocCompressedBlockEntry {
6666
std::array<uint8_t, 5 + 3 + 3 + 1> Data{};
6767

68+
static constexpr uint32_t OffsetBits = 40;
69+
static constexpr uint64_t OffsetMask = (1ull << OffsetBits) - 1ull;
70+
static constexpr uint32_t SizeBits = 24;
71+
static constexpr uint32_t SizeMask = (1 << SizeBits) - 1;
72+
static constexpr uint32_t SizeShift = 8;
73+
74+
[[nodiscard]] inline uint64_t GetOffset() const {
75+
const auto* Offset = reinterpret_cast<const uint64_t*>(Data.data());
76+
return *Offset & OffsetMask;
77+
}
78+
79+
[[nodiscard]] inline uint32_t GetCompressedSize() const {
80+
const auto* Size = reinterpret_cast<const uint32_t*>(Data.data()) + 1;
81+
return (*Size >> SizeShift) & SizeMask;
82+
}
83+
84+
[[nodiscard]] inline uint32_t GetUncompressedSize() const {
85+
const auto* UncompressedSize = reinterpret_cast<const uint32_t*>(Data.data()) + 2;
86+
return *UncompressedSize & SizeMask;
87+
}
88+
89+
[[nodiscard]] inline uint8_t GetCompressionMethodIndex() const {
90+
const auto* Index = reinterpret_cast<const uint32_t*>(Data.data()) + 2;
91+
return static_cast<uint8_t>(*Index >> SizeBits);
92+
}
93+
6894
void serialize(Archive& ar) {
6995
ar << Data;
7096
}

libsave/include/SatisfactorySave/Pak/IoStoreFile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@ namespace SatisfactorySave {
4747
private:
4848
FIoStoreTocResource utoc_;
4949
std::unique_ptr<DirectoryIndexReader> dirIndex_;
50+
std::unique_ptr<IFStreamArchive> ucasAr_;
5051
};
5152
} // namespace SatisfactorySave

libsave/src/IO/OodleUtils.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include "OodleUtils.h"
2+
3+
#include <cstdint>
4+
#include <stdexcept>
5+
6+
#ifdef _WIN32
7+
#include <Windows.h>
8+
#endif
9+
10+
std::vector<char> SatisfactorySave::oodleDecompress(const std::vector<char>& buffer, std::size_t decompressed_size) {
11+
std::vector<char> buffer_uncompressed(decompressed_size);
12+
oodleDecompress(buffer_uncompressed.data(), decompressed_size, buffer.data(), buffer.size());
13+
return buffer_uncompressed;
14+
}
15+
16+
#ifdef _WIN32
17+
18+
typedef intptr_t(__stdcall* OodleLZ_DecompressFunc)(const void* compBuf, intptr_t compBufSize, void* rawBuf,
19+
intptr_t rawLen, int32_t fuzzSafe, int32_t checkCRC, int32_t verbosity, void* decBufBase, intptr_t decBufSize,
20+
void* fpCallback, void* callbackUserData, void* decoderMemory, intptr_t decoderMemorySize, int32_t threadPhase);
21+
22+
void SatisfactorySave::oodleDecompress(char* dest, std::size_t destLen, const char* source, std::size_t sourceLen) {
23+
static OodleLZ_DecompressFunc OodleLZ_Decompress = nullptr;
24+
if (OodleLZ_Decompress == nullptr) {
25+
HMODULE handle = LoadLibrary(TEXT("oo2core_9_win64.dll"));
26+
if (handle == NULL) {
27+
throw std::runtime_error("Error loading Oodle DLL!");
28+
}
29+
OodleLZ_Decompress = (OodleLZ_DecompressFunc) GetProcAddress(handle, "OodleLZ_Decompress");
30+
if (OodleLZ_Decompress == NULL) {
31+
throw std::runtime_error("Error loading OodleLZ_Decompress!");
32+
}
33+
}
34+
35+
auto len = OodleLZ_Decompress(source, static_cast<int64_t>(sourceLen), dest, static_cast<int64_t>(destLen), 1, 0, 0,
36+
nullptr, 0, nullptr, nullptr, nullptr, 0, 3);
37+
if (len != destLen) {
38+
throw std::runtime_error("Error decompressing Oodle!");
39+
}
40+
}
41+
42+
#else
43+
44+
void SatisfactorySave::oodleDecompress(char* dest, std::size_t destLen, const char* source, std::size_t sourceLen) {
45+
throw std::runtime_error("Not implemented!");
46+
}
47+
48+
#endif

libsave/src/IO/OodleUtils.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
namespace SatisfactorySave {
6+
std::vector<char> oodleDecompress(const std::vector<char>& buffer, std::size_t decompressed_size);
7+
8+
void oodleDecompress(char* dest, std::size_t destLen, const char* source, std::size_t sourceLen);
9+
} // namespace SatisfactorySave

libsave/src/Pak/IoStoreFile.cpp

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "GameTypes/UE/Core/IO/IoStore.h"
66
#include "IO/MemoryStreams.h"
7+
#include "IO/OodleUtils.h"
78

89
SatisfactorySave::DirectoryIndexReader::DirectoryIndexReader(const FIoDirectoryIndexResource& res) : res_(res) {
910
if (!res_.MountPoint.starts_with("../../../")) {
@@ -42,7 +43,12 @@ void SatisfactorySave::DirectoryIndexReader::parseFile(uint32_t file_idx, const
4243

4344
SatisfactorySave::IoStoreFile::IoStoreFile(const std::filesystem::path& path) {
4445
if (!std::filesystem::is_regular_file(path)) {
45-
throw std::runtime_error("IoStore file invalid: " + path.string());
46+
throw std::runtime_error("IoStore utoc file invalid: " + path.string());
47+
}
48+
std::filesystem::path ucas_path = path;
49+
ucas_path.replace_extension(std::filesystem::path(".ucas"));
50+
if (!std::filesystem::is_regular_file(ucas_path)) {
51+
throw std::runtime_error("IoStore ucas file invalid: " + ucas_path.string());
4652
}
4753

4854
IFStreamArchive utocAr(path);
@@ -64,6 +70,8 @@ SatisfactorySave::IoStoreFile::IoStoreFile(const std::filesystem::path& path) {
6470

6571
dirIndex_ = std::make_unique<DirectoryIndexReader>(dirIndexRes);
6672
}
73+
74+
ucasAr_ = std::make_unique<IFStreamArchive>(ucas_path);
6775
}
6876

6977
std::vector<std::string> SatisfactorySave::IoStoreFile::getAllAssetFilenames() const {
@@ -80,6 +88,52 @@ std::vector<std::string> SatisfactorySave::IoStoreFile::getAllAssetFilenames() c
8088
}
8189

8290
std::vector<char> SatisfactorySave::IoStoreFile::readAssetFileContent(const std::string& filename) {
83-
// TODO
84-
return {};
91+
if (dirIndex_ == nullptr) {
92+
throw std::runtime_error("Missing directory index!");
93+
}
94+
if (!dirIndex_->directoryEntries().contains(filename)) {
95+
throw std::runtime_error("Asset file not found in utoc: " + filename);
96+
}
97+
98+
// UserData of directory index file entry is index to chunk data
99+
uint32_t chunkIdx = dirIndex_->directoryEntries().at(filename);
100+
const auto& chunk = utoc_.ChunkOffsetLengths[chunkIdx];
101+
102+
// The idea behind chunk data seems to be that all data is stored in a single address space. The address space is
103+
// divided into blocks. All data seems aligned to a block start. Each block is then compressed individually. The
104+
// ucas stores the compressed blocks directly after each other. Divide address space offset and length of chunk
105+
// data by the block size to get index and number of compression blocks.
106+
const auto& blockSize = utoc_.Header.CompressionBlockSize;
107+
const uint64_t compBlockIdx = chunk.GetOffset() / blockSize;
108+
const uint32_t compBlockNum = (chunk.GetLength() + blockSize - 1) / blockSize;
109+
110+
// Allocate output
111+
std::vector<char> buf(chunk.GetLength());
112+
113+
auto& ar = *ucasAr_;
114+
115+
// Loop over compression blocks
116+
for (uint32_t i = 0; i < compBlockNum; i++) {
117+
const auto& compBlock = utoc_.CompressionBlocks[compBlockIdx + i];
118+
119+
// Validate that uncompressed size of all compression blocks (except the last one) is block size.
120+
if (i < compBlockNum - 1 && compBlock.GetUncompressedSize() != blockSize) {
121+
throw std::runtime_error("Unexpected uncompressed block size");
122+
}
123+
124+
ar.seek(compBlock.GetOffset());
125+
const auto compMethod = utoc_.CompressionMethods.at(compBlock.GetCompressionMethodIndex());
126+
if (compMethod == "None") {
127+
// Uncompressed, copy directoy to out buffer.
128+
ar.serializeRaw(buf.data() + i * blockSize, compBlock.GetCompressedSize());
129+
} else if (compMethod == "Oodle") {
130+
const auto comp_buf = ar.read_buffer(compBlock.GetCompressedSize());
131+
oodleDecompress(buf.data() + i * blockSize, compBlock.GetUncompressedSize(), comp_buf.data(),
132+
compBlock.GetCompressedSize());
133+
} else {
134+
throw std::runtime_error("Unknown compression method: " + compMethod);
135+
}
136+
}
137+
138+
return buf;
85139
}

0 commit comments

Comments
 (0)