Skip to content

Commit 66f7fb5

Browse files
committed
add utoc parsing
1 parent 86dd8ee commit 66f7fb5

12 files changed

Lines changed: 377 additions & 19 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <cstdint>
5+
6+
#include "../../../../IO/Archive/Archive.h"
7+
#include "satisfactorysave_export.h"
8+
9+
namespace SatisfactorySave {
10+
class SATISFACTORYSAVE_API FIoChunkId {
11+
public:
12+
std::array<uint8_t, 12> Id{};
13+
14+
void serialize(Archive& ar) {
15+
ar << Id;
16+
}
17+
};
18+
} // namespace SatisfactorySave
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
#include "../../../../IO/Archive/Archive.h"
6+
#include "satisfactorysave_export.h"
7+
8+
namespace SatisfactorySave {
9+
class SATISFACTORYSAVE_API FIoContainerId {
10+
public:
11+
static constexpr uint64_t InvalidId = static_cast<uint64_t>(-1);
12+
13+
uint64_t Id = InvalidId;
14+
15+
void serialize(Archive& ar) {
16+
ar << Id;
17+
}
18+
};
19+
} // namespace SatisfactorySave
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <cstdint>
5+
6+
#include "../../../../IO/Archive/Archive.h"
7+
#include "satisfactorysave_export.h"
8+
9+
namespace SatisfactorySave {
10+
class SATISFACTORYSAVE_API FIoChunkHash {
11+
public:
12+
std::array<uint8_t, 32> Hash{};
13+
14+
void serialize(Archive& ar) {
15+
ar << Hash;
16+
}
17+
};
18+
19+
enum class EIoContainerFlags : uint8_t {
20+
None,
21+
Compressed = 1 << 0,
22+
Encrypted = 1 << 1,
23+
Signed = 1 << 2,
24+
Indexed = 1 << 3,
25+
OnDemand = 1 << 4,
26+
};
27+
} // namespace SatisfactorySave
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <cstdint>
5+
6+
#include "../../../../IO/Archive/Archive.h"
7+
#include "satisfactorysave_export.h"
8+
9+
namespace SatisfactorySave {
10+
struct SATISFACTORYSAVE_API FIoOffsetAndLength {
11+
std::array<uint8_t, 10> OffsetAndLength{};
12+
13+
[[nodiscard]] inline uint64_t GetOffset() const {
14+
return static_cast<uint64_t>(OffsetAndLength[4]) | static_cast<uint64_t>(OffsetAndLength[3]) << 8 |
15+
static_cast<uint64_t>(OffsetAndLength[2]) << 16 | static_cast<uint64_t>(OffsetAndLength[1]) << 24 |
16+
static_cast<uint64_t>(OffsetAndLength[0]) << 32;
17+
}
18+
19+
[[nodiscard]] inline uint64_t GetLength() const {
20+
return static_cast<uint64_t>(OffsetAndLength[9]) | static_cast<uint64_t>(OffsetAndLength[8]) << 8 |
21+
static_cast<uint64_t>(OffsetAndLength[7]) << 16 | static_cast<uint64_t>(OffsetAndLength[6]) << 24 |
22+
static_cast<uint64_t>(OffsetAndLength[5]) << 32;
23+
}
24+
25+
void serialize(Archive& ar) {
26+
ar << OffsetAndLength;
27+
}
28+
};
29+
} // namespace SatisfactorySave
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <cstdint>
5+
#include <cstring>
6+
#include <vector>
7+
8+
#include "../../../../IO/Archive/Archive.h"
9+
#include "../Misc/Guid.h"
10+
#include "../Misc/SecureHash.h"
11+
#include "../UObject/NameTypes.h"
12+
#include "IoChunkId.h"
13+
#include "IoContainerId.h"
14+
#include "IoDispatcher.h"
15+
#include "IoOffsetLength.h"
16+
#include "satisfactorysave_export.h"
17+
18+
namespace SatisfactorySave {
19+
20+
struct SATISFACTORYSAVE_API FIoStoreTocHeader {
21+
static constexpr inline char TocMagicImg[] = "-==--==--==--==-";
22+
23+
std::array<uint8_t, 16> TocMagic{};
24+
uint8_t Version = 0;
25+
uint8_t Reserved0 = 0;
26+
uint16_t Reserved1 = 0;
27+
uint32_t TocHeaderSize = 0;
28+
uint32_t TocEntryCount = 0;
29+
uint32_t TocCompressedBlockEntryCount = 0;
30+
uint32_t TocCompressedBlockEntrySize = 0;
31+
uint32_t CompressionMethodNameCount = 0;
32+
uint32_t CompressionMethodNameLength = 0;
33+
uint32_t CompressionBlockSize = 0;
34+
uint32_t DirectoryIndexSize = 0;
35+
uint32_t PartitionCount = 0;
36+
FIoContainerId ContainerId;
37+
FGuid EncryptionKeyGuid;
38+
/*EIoContainerFlags*/ uint8_t ContainerFlags = 0;
39+
uint8_t Reserved3 = 0;
40+
uint16_t Reserved4 = 0;
41+
uint32_t TocChunkPerfectHashSeedsCount = 0;
42+
uint64_t PartitionSize = 0;
43+
uint32_t TocChunksWithoutPerfectHashCount = 0;
44+
uint32_t Reserved7 = 0;
45+
std::array<uint64_t, 5> Reserved8{};
46+
47+
[[nodiscard]] bool CheckMagic() const {
48+
return std::memcmp(TocMagic.data(), TocMagicImg, sizeof(TocMagic)) == 0;
49+
}
50+
51+
void serialize(Archive& ar);
52+
};
53+
54+
enum class FIoStoreTocEntryMetaFlags : uint8_t {
55+
None,
56+
Compressed = 1 << 0,
57+
MemoryMapped = 1 << 1,
58+
};
59+
60+
struct SATISFACTORYSAVE_API FIoStoreTocEntryMeta {
61+
FIoChunkHash ChunkHash;
62+
/*FIoStoreTocEntryMetaFlags*/ uint8_t Flags = 0;
63+
};
64+
65+
struct SATISFACTORYSAVE_API FIoStoreTocCompressedBlockEntry {
66+
std::array<uint8_t, 5 + 3 + 3 + 1> Data{};
67+
68+
void serialize(Archive& ar) {
69+
ar << Data;
70+
}
71+
};
72+
73+
struct SATISFACTORYSAVE_API FIoStoreTocResource {
74+
FIoStoreTocHeader Header;
75+
std::vector<FIoChunkId> ChunkIds;
76+
std::vector<FIoOffsetAndLength> ChunkOffsetLengths;
77+
std::vector<int32_t> ChunkPerfectHashSeeds;
78+
std::vector<int32_t> ChunkIndicesWithoutPerfectHash;
79+
std::vector<FIoStoreTocCompressedBlockEntry> CompressionBlocks;
80+
std::vector<FName> CompressionMethods;
81+
// FSHAHash SignatureHash;
82+
std::vector<FSHAHash> ChunkBlockSignatures;
83+
std::vector<FIoStoreTocEntryMeta> ChunkMetas;
84+
std::vector<uint8_t> DirectoryIndexBuffer;
85+
86+
void serialize(Archive& ar);
87+
};
88+
} // namespace SatisfactorySave

libsave/include/SatisfactorySave/IO/Archive/Archive.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ namespace SatisfactorySave {
7474
return *this;
7575
}
7676

77+
template<typename T, std::size_t N>
78+
inline Archive& operator<<(std::array<T, N>& a) {
79+
if constexpr (std::is_arithmetic_v<T>) {
80+
serialize(a.data(), N * sizeof(T));
81+
} else {
82+
for (auto& val : a) {
83+
*this << val;
84+
}
85+
}
86+
return *this;
87+
}
88+
7789
template<typename T>
7890
inline Archive& operator<<(std::optional<T>& v) {
7991
bool hasValue = v.has_value();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <filesystem>
4+
5+
#include "../IO/Archive/IStreamArchive.h"
6+
#include "satisfactorysave_export.h"
7+
8+
namespace SatisfactorySave {
9+
10+
class SATISFACTORYSAVE_API IoStoreFile {
11+
public:
12+
explicit IoStoreFile(const std::filesystem::path& path);
13+
14+
private:
15+
std::unique_ptr<IFStreamArchive> ioStoreAr_;
16+
};
17+
} // namespace SatisfactorySave

libsave/include/SatisfactorySave/Pak/PakManager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <string>
77
#include <vector>
88

9+
#include "IoStoreFile.h"
910
#include "PakFile.h"
1011
#include "satisfactorysave_export.h"
1112

@@ -58,6 +59,7 @@ namespace SatisfactorySave {
5859
void cacheLatestPakNames(const std::optional<std::string>& modPrefix = std::nullopt);
5960

6061
std::vector<std::shared_ptr<PakFile>> pakFiles_;
62+
std::vector<std::shared_ptr<IoStoreFile>> ioStoreFiles_;
6163

6264
std::unordered_map<std::string, std::pair<std::size_t, std::string>> packageNames_;
6365
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
namespace SatisfactorySave {
6+
template<typename T>
7+
inline std::size_t vector_bin_size(const std::vector<T>& vec) {
8+
return vec.size() * sizeof(T);
9+
}
10+
} // namespace SatisfactorySave
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#include "GameTypes/UE/Core/IO/IoStore.h"
2+
3+
#include "IO/Archive/IStreamArchive.h"
4+
#include "Utils/VectorUtils.h"
5+
6+
void SatisfactorySave::FIoStoreTocHeader::serialize(Archive& ar) {
7+
ar << TocMagic;
8+
ar << Version;
9+
ar << Reserved0;
10+
ar << Reserved1;
11+
ar << TocHeaderSize;
12+
ar << TocEntryCount;
13+
ar << TocCompressedBlockEntryCount;
14+
ar << TocCompressedBlockEntrySize;
15+
ar << CompressionMethodNameCount;
16+
ar << CompressionMethodNameLength;
17+
ar << CompressionBlockSize;
18+
ar << DirectoryIndexSize;
19+
ar << PartitionCount;
20+
ar << ContainerId;
21+
ar << EncryptionKeyGuid;
22+
ar << ContainerFlags;
23+
ar << Reserved3;
24+
ar << Reserved4;
25+
ar << TocChunkPerfectHashSeedsCount;
26+
ar << PartitionSize;
27+
ar << TocChunksWithoutPerfectHashCount;
28+
ar << Reserved7;
29+
ar << Reserved8;
30+
}
31+
32+
void SatisfactorySave::FIoStoreTocResource::serialize(Archive& ar) {
33+
if (!ar.isIArchive()) {
34+
throw std::runtime_error("Only IStreamArchive support implemented!");
35+
}
36+
auto& inAr = dynamic_cast<IStreamArchive&>(ar);
37+
38+
// https://github.com/EpicGames/UnrealEngine/blob/5.3.2-release/Engine/Source/Runtime/Core/Private/IO/IoStore.cpp#L2917
39+
40+
inAr << Header;
41+
if (!Header.CheckMagic()) {
42+
throw std::runtime_error("Bad IoStore magic!");
43+
}
44+
if (Header.Version != 5) {
45+
throw std::runtime_error("Bad IoStore header version!");
46+
}
47+
if (Header.TocHeaderSize != sizeof(FIoStoreTocHeader)) {
48+
throw std::runtime_error("Bad IoStore header size!");
49+
}
50+
51+
ChunkIds.resize(Header.TocEntryCount);
52+
inAr.serializeRaw(ChunkIds.data(), vector_bin_size(ChunkIds));
53+
54+
ChunkOffsetLengths.resize(Header.TocEntryCount);
55+
inAr.serializeRaw(ChunkOffsetLengths.data(), vector_bin_size(ChunkOffsetLengths));
56+
57+
ChunkPerfectHashSeeds.resize(Header.TocChunkPerfectHashSeedsCount);
58+
inAr.serializeRaw(ChunkPerfectHashSeeds.data(), vector_bin_size(ChunkPerfectHashSeeds));
59+
60+
ChunkIndicesWithoutPerfectHash.resize(Header.TocChunksWithoutPerfectHashCount);
61+
inAr.serializeRaw(ChunkIndicesWithoutPerfectHash.data(), vector_bin_size(ChunkIndicesWithoutPerfectHash));
62+
63+
CompressionBlocks.resize(Header.TocCompressedBlockEntryCount);
64+
inAr.serializeRaw(CompressionBlocks.data(), vector_bin_size(CompressionBlocks));
65+
66+
CompressionMethods.emplace_back("None");
67+
for (uint32_t i = 0; i < Header.CompressionMethodNameCount; i++) {
68+
const auto name = inAr.read_buffer(Header.CompressionMethodNameLength);
69+
CompressionMethods.emplace_back(std::string(name.data()));
70+
}
71+
72+
if (Header.ContainerFlags & static_cast<std::underlying_type_t<EIoContainerFlags>>(EIoContainerFlags::Signed)) {
73+
const auto HashSize = inAr.read<int32_t>();
74+
std::vector<uint8_t> TocSignature(HashSize);
75+
inAr.serializeRaw(TocSignature.data(), vector_bin_size(TocSignature));
76+
std::vector<uint8_t> BlockSignature(HashSize);
77+
inAr.serializeRaw(BlockSignature.data(), vector_bin_size(BlockSignature));
78+
ChunkBlockSignatures.resize(Header.TocCompressedBlockEntryCount);
79+
inAr.serializeRaw(ChunkBlockSignatures.data(), vector_bin_size(ChunkBlockSignatures));
80+
}
81+
82+
DirectoryIndexBuffer.resize(Header.DirectoryIndexSize);
83+
inAr.serializeRaw(DirectoryIndexBuffer.data(), vector_bin_size(DirectoryIndexBuffer));
84+
85+
ChunkMetas.resize(Header.TocEntryCount);
86+
inAr.serializeRaw(ChunkMetas.data(), vector_bin_size(ChunkMetas));
87+
}

0 commit comments

Comments
 (0)