Polyphase Game Engine
Loading...
Searching...
No Matches
ContentObfuscation.h
Go to the documentation of this file.
1#pragma once
2
3#include <stdint.h>
4
5#include "PolyphaseAPI.h"
6
7// Content obfuscation for "Static Content" packages.
8//
9// Cooked .oct assets, .lua scripts and AssetRegistry.txt are wrapped in a small
10// container and XOR'd with a keystream at package time, then decoded per-file at
11// read time. This defeats casual extraction and editing of a shipped package. It
12// is NOT DRM -- the key is a compile-time constant in ContentObfuscation.cpp and
13// therefore ships inside the executable.
14//
15// Three properties drive the design:
16//
17// * Endian independence. Every header field is read and written a byte at a
18// time and the keystream byte is extracted by shifting, never by aliasing a
19// uint32_t. The same package decodes identically on little-endian desktop and
20// big-endian GameCube/Wii, with no swap and no #if.
21//
22// * Offset addressability. A byte's keystream depends only on its own payload
23// offset, never on preceding bytes. That is what lets a truncated read
24// (Asset::LoadFile's console loadCap, AssetManager's header-only reads)
25// decode a prefix correctly on its own, and lets AudioManager seek into the
26// middle of an obfuscated .oct to stream PCM.
27//
28// * No per-file state and no allocation. Decode is in-place and single-pass, so
29// it never doubles peak memory while loading a multi-MB texture on GameCube,
30// and it is reentrant on the async asset-load worker.
31//
32// Detection is by magic, not by a config flag, so plain and obfuscated files
33// coexist in one package and the editor keeps reading its own project files
34// untouched.
35
37{
38 // Container header, all multi-byte fields little-endian byte-wise:
39 //
40 // 0..5 magic 'P','L','Y','O','B','F'
41 // 6 version (kVersion)
42 // 7 flags (kFlagChecksum)
43 // 8..11 decodedSize
44 // 12..15 salt
45 // 16..19 checksum FNV-1a-32 over the decoded payload
46 // 20..23 headerCheck FNV-1a-32 over header bytes 0..19
47 // 24.. payload
48 static const uint32_t kHeaderSize = 24;
49 static const uint8_t kVersion = 1;
50 static const uint8_t kFlagChecksum = 0x01;
51
52 // Extra bytes a capped Stream::ReadFile must request so that stripping the
53 // header still leaves the caller its requested byte count. Deliberately
54 // larger than kHeaderSize so a future header can grow without re-auditing
55 // every capped call site.
56 static const int32_t kReadHeadroom = 32;
57
58 // True if `data` carries a well-formed container header. Cheap enough to run
59 // on every file read: a magic compare plus a 20-byte hash.
60 bool IsContainer(const void* data, uint32_t size);
61
62 // Decoded payload length declared by a valid container header.
63 uint32_t GetDecodedSize(const void* data);
64
65 // Strips the header and decodes the payload in place, moving it down to
66 // offset 0. Returns false if `data` is not a container or the checksum
67 // failed; on success `outSize` receives the plaintext byte count, which is
68 // legitimately 0 for an empty file.
69 //
70 // `available` is how many bytes were actually read, which may be less than
71 // kHeaderSize + decodedSize for a capped read. In that case the available
72 // prefix is decoded, `outTruncated` is set, and the checksum is skipped
73 // because it covers the whole payload.
74 bool DecodeInPlace(char* data, uint32_t available, uint32_t* outSize, bool* outTruncated);
75
76 // Random-access decode of an already-read range, for the streaming reader
77 // that never holds the whole file. `payloadOffset` is an offset into the
78 // decoded payload, not into the file. Symmetric -- the same call encodes.
79 void DecodeRange(void* buffer, uint32_t count, uint32_t payloadOffset, uint32_t salt);
80
81 // Reads the salt out of a valid container header, for a streaming reader
82 // that keeps the handle open and decodes range by range.
83 uint32_t GetSalt(const void* data);
84
85 // Cook side. Wraps `src` into a newly malloc'd container; caller frees with
86 // free(). Not EDITOR-gated: the packaging sweep, a future .pak writer and
87 // build-target addons all need it.
88 bool Encode(const void* src, uint32_t srcSize, char** outData, uint32_t* outSize);
89
90 // Reads `path`, encodes it, writes it back. Returns true and does nothing if
91 // the file already carries a container, so re-running the packaging sweep is
92 // safe and idempotent.
93 bool EncodeFileInPlace(const char* path);
94}
Export macros for Polyphase Engine symbols.
Definition ContentObfuscation.h:37
uint32_t GetSalt(const void *data)
Definition ContentObfuscation.cpp:115
bool DecodeInPlace(char *data, uint32_t available, uint32_t *outSize, bool *outTruncated)
Definition ContentObfuscation.cpp:120
bool IsContainer(const void *data, uint32_t size)
Definition ContentObfuscation.cpp:91
bool EncodeFileInPlace(const char *path)
Definition ContentObfuscation.cpp:255
uint32_t GetDecodedSize(const void *data)
Definition ContentObfuscation.cpp:110
bool Encode(const void *src, uint32_t srcSize, char **outData, uint32_t *outSize)
Definition ContentObfuscation.cpp:205
void DecodeRange(void *buffer, uint32_t count, uint32_t payloadOffset, uint32_t salt)
Definition ContentObfuscation.cpp:183