Asset Encryption¶
This page describes the custom asset encryption format (known as the muast format) used to protect the game assets.
The encryption method does not apply a uniform algorithm to the entire file. Instead, it splits the file into multiple segments, applies different decryption techniques (standard AES-CBC, AES-CBC with a reduced key length, a mutated AES-CBC with altered key/IV arrays, and byte-level endian/bit inversions), and shuffles the data sequence (specifically, reversing the decrypted blocks).
Technical Concept Overview¶
To visualize the workflow of the decryption process, refer to the conceptual diagram below:
Decryption Flowchart¶
flowchart TD
classDef plain fill:#2d3436,stroke:#b2bec3,stroke-width:2px,color:#fff;
classDef encAes256 fill:#0984e3,stroke:#74b9ff,stroke-width:2px,color:#fff;
classDef encAes128 fill:#00b894,stroke:#55efc4,stroke-width:2px,color:#fff;
classDef encMutant fill:#6c5ce7,stroke:#a29bfe,stroke-width:2px,color:#fff;
classDef reverse fill:#d63031,stroke:#fab1a0,stroke-width:2px,color:#fff;
Start([muast File Input]) --> Split1{"Boundary Split"}
Split1 -->|Unencrypted| PlainStart("[Plain Initial (1%)]"):::plain
Split1 -->|Encrypted Segment| EncData{"Encrypted Blocks"}
Split1 -->|Unencrypted| PlainEnd("[Plain Final]"):::plain
EncData --> Half1["First Half Block"]
EncData --> Half2["Second Half Block"]
Half2 --> PDDEC["Extract Mid 32-Bytes\n(at final)"]
Half2 -.Padding Shared.-> RJDEC256
PDDEC -->|Endian Swap & NOT| P_Bytes["Puzzle Solved Parts (byteP1 / byteP2)"]
Half1 --> RJDEC256["RJDEC\n[AES-256-CBC]"]:::encAes256
Half2 --> RJDEC128["RJDEC\n[AES-128-CBC]"]:::encAes128
RJDEC256 -.Parsing Info.-> HTDEC
HTDEC_Target["HTDEC Target Extraction"] --> HTDEC["HTDEC\n[Mutated AES-256-CBC]"]:::encMutant
RJDEC128 --> Join1{"Part Integration"}
RJDEC256 --> Join1
P_Bytes --> Join1
HTDEC --> Join1
Join1 --> Reverser["Block-Level Full Sequence Reversal"]:::reverse
PlainStart --> FinalJoin{"Final Merge"}
Reverser --> FinalJoin
PlainEnd --> FinalJoin
FinalJoin --> End([Decrypted Plaintext Output])
The core characteristic of this encryption format is the puzzle-like reconstruction and final sequence reversal (reverse). Instead of traditional sequential decryption, it decrypts different parts of the file using different keys and IVs, extracts necessary pad data dynamically, and finally reverses the decrypted block sequence before merging them with the unencrypted plain boundaries.
File Structure and Decryption Ranges¶
The file contains unencrypted plaintext areas at the very beginning and at the end. Only the middle section is subject to cryptographic transformation. The boundaries are calculated using predetermined percentages (typically rangeIni = 1 and rangeFin = 100).
flowchart LR
classDef plain fill:#2d3436,stroke:#b2bec3,stroke-width:2px,color:#fff;
classDef encrypted fill:#6c5ce7,stroke:#a29bfe,stroke-width:2px,color:#fff;
classDef boundary fill:#ffeaa7,stroke:#fdcb6e,stroke-width:1px,color:#2d3436,font-weight:bold;
Start([0]) --> P1["Plain Initial<br>(Unencrypted 1%)"]:::plain
P1 --> init["initial"]:::boundary
init --> EM["Encrypted Middle<br>(Subject to Decryption)"]:::encrypted
EM --> fin["final"]:::boundary
fin --> PF["Plain Final<br>(Unencrypted)"]:::plain
PF --> End([EOF])
Boundary Computation Details¶
Given the file input input (excluding the 4-byte header):
-
Base Size Calculation: \(\text{lenWithoutPadding} = \text{len}(input) - 32\)
-
Plain Initial Boundary (
initial): \(\text{initial} = \frac{\text{lenWithoutPadding} \times \text{rangeIni}}{100}\) (Typically represents the first 1% of the file size) -
Encrypted Block Calculation: \(\text{cipherSize} = \text{lenWithoutPadding} - \text{initial}\) \(\text{numEncrypted} = \frac{\text{rangeFin} \times (\text{cipherSize} / 16)}{100}\)
-
Split Coordinates:
- \(\text{half} = \text{initial} + (\text{numEncrypted} / 2) \times 16\)
- \(\text{final} = \text{initial} + \text{numEncrypted} \times 16\)
Decryption Process and Algorithms¶
The decryption logic consists of 5 distinct processing blocks, executed sequentially.
1. Key and IV Definitions (Conceptual)¶
The algorithm operates using two primary secrets defined in Decrypt.cs (Meige.Decrypt.Main):
- key (32 bytes):
<key from Decrypt.cs> - vec (16 bytes):
<vec from Decrypt.cs>
From these, three derived keys and IVs are extracted:
- Key-256: The full 32-byte
key. - Key-128: The central 16 bytes of the
key(key[8:24]). - Mutated Key/IV (HTDEC): Formed via specific permute operations.
2. PDDEC: Mid-Block Bit Inversion Puzzle¶
A 32-byte block is read from [final : final + 32]. The middle 16 bytes (index [8:24]) are processed as follows:
- Read as 4-byte integers in little-endian format.
- Invert the endianness and apply a bitwise
NOT(XOR with0xFFFFFFFF).
This results in a reconstructed 32-byte block, split into two 16-byte halves: byteP1 and byteP2. These serve as dynamic padding/keys in subsequent steps.
3. RJDEC (Second Half): AES-128-CBC Decryption¶
The segment [half : final] is merged with byteP2 to form the ciphertext payload.
- Algorithm: AES-128-CBC
- Key: Key-128 (
key[8:24]) - IV:
vec
From the decrypted output, the first 16 bytes (leftPad) and the final 16 bytes (updated byteP2) are extracted, and the rest forms the plain second-half data segment.
4. RJDEC (First Half): AES-256-CBC Decryption¶
The segment [initial + 16 : half] is merged with the newly extracted leftPad and byteP1.
- Algorithm: AES-256-CBC
- Key: Key-256 (Full 32-byte
key) - IV:
vec
Similar to the second half, the resulting output is sliced to update leftPad and byteP1, leaving the plain first-half data segment.
5. HTDEC: Mutated AES-256-CBC Decryption¶
The segment [initial : initial + 16] is joined with leftPad (32 bytes total) and decrypted using a customized AES variant.
Mutated Key Generation¶
The 32-byte key is permuted by rearranging the 8-byte chunks of the key in the sequence [1, 3, 0, 2]:
HTDEC Key = key[8:16] + key[24:32] + key[0:8] + key[16:24]
Mutated IV Generation¶
The 16-byte vec is split into four 4-byte chunks. Each chunk is endian-flipped and bitwise negated (NOT).
These mutated parameters are used to decrypt the 32-byte payload via AES-256-CBC.
6. Sequence Reversal and Merging¶
The decrypted parts are joined in the following order: \(\text{decryptedBlock} = \text{HTDEC Output} + \text{First Half} + \text{byteP1} + \text{Second Half}\)
The entire joined array is reversed (first byte swapped with last byte, and so on). This step is crucial and highly unique to the muast format.
Finally, the complete plaintext file is reassembled: \(\text{Plain Output} = input[0:\text{initial}] + \text{reversedBlock} + \text{byteP2} + input[\text{final}+32:]\)