44
55#include " GameTypes/UE/Core/IO/IoStore.h"
66#include " IO/MemoryStreams.h"
7+ #include " IO/OodleUtils.h"
78
89SatisfactorySave::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
4344SatisfactorySave::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
6977std::vector<std::string> SatisfactorySave::IoStoreFile::getAllAssetFilenames () const {
@@ -80,6 +88,52 @@ std::vector<std::string> SatisfactorySave::IoStoreFile::getAllAssetFilenames() c
8088}
8189
8290std::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