Currently we use hcaenc_lite.dll
to encode HCA files from WAVE files.
More detailed information is unknown for now. So here we mainly talk
about the cipher conversion between HCA files.
The basis to everything is: the deciphering and the decoding process are independent. Thanks to @hyspace for figuring this out. Moreover, the checksum verification process is also independent. In the whole HCA to WAVE streamline, their orders are as below:
HCA data frame => Checksum verification -> Deciphering -> Decoding => WAVE data block
Thus here, all the settings, such as sampling rate, channel count, compression quality, only matter in the decoding process. All we need to know in the first two steps is the frame size, which is described in the HCA header.
The header itself, along with all the audio frames, is signed with a checksum.
The checksum is calculated through hashing all the data bytes (see CHCA::Checksum()
).
Remember, data bytes, and data bytes only. If we consider the header as a special
frame, all the frames in an HCA file can be described as below:
| Data | Checksum |
|------------------------------|----------|
| dd dd dd .. dd dd cc cc |
As you can see, the last two bytes (a WORD
) are always the checksum. If the data is
corrupted, verifying the checksum will expose the corrupted frame. Changing the data
is very likely for the decoder to recognize the frame is corrupted, causing an error.
We are not able to bypass the security check, so we must calculate the checksum ourselves.
Luckily, the algorithm is interesting. It is based on an XOR table. Have you noticed that, this table is carefully designed, and it has a great characteristic:
For byte sequence S(b1, b2, ..., bn), use the checksum function f to calculate its checksum c(c1, c2):
f(S) -> c
Concatenate c after S to form a new sequence S'(b1, b2, ..., bn, c1, c2). Now the checksum of S' is 0:
f(S') -> 0
To pass the checksum verification, the checksum of the whole frame should be 0. It can be easily met by adding the checksum of real data after the data bytes, in big endian.
Decryption of HCA file is simple. It only uses a substitution table for byte mapping:
f := b -> b'
Under all three cipher types, all 256 bytes are uniquely mapped in the substitution tables. So the inverse function, which is also the encryption function, is unique, given a well-formed substitution table.
Type 0 is a special case whose table maps a byte to itself. Obviously, type 0 is the same as plain text. Before decoding, the decryption function always decrypts the encrypted to plain text. With another encryption function, the plain text can be once again encrypted. By the way, type 1 is another special case, whose substitution table is static (generated without any argument).
Thus re-encryption is also simple:
- use cipher module #1, configured with cipher type and keys (if necessary), to decrypt the HCA file;
- the audio data is in plain text;
- use cipher module #2, configured with cipher type and keys (if necessary), to encrypt the HCA file.
The final HCA file is completed encrypted or still plain text, depending on the configuration of cipher module #2.