OpenCGSS / DereTore

Music and beatmap authoring toolkit for THE iDOLM@STER Cinderella Girls Starlight Stage (CGSS/DereSute/デレステ). / 偶像大师灰姑娘女孩星光舞台音乐&谱面制作工具箱
MIT License
186 stars 27 forks source link

[Question] hca2wav loop support #24

Closed FZFalzar closed 7 years ago

FZFalzar commented 7 years ago

Hi, are hca loops supported for this tool? I've been trying to find a way to convert a similarly extracted HCA from another game with the key, but the tool keeps giving me a NotImplementedException in DecodeData (HCADecoder.Internal.cs)

Sample file is attached, key is - BGM_0001.zip

hozuki commented 7 years ago

Thank you for the report. Actually there is no yet working loop support. I wrote some code to handle HCAs with loops (or just check and throw an exception), but I was confused about how to deal with repetitions. In most of the games, looped audios are used as background music, which requires infinite loop. However wave audio doesn't support partial looping (e.g. 00:13.20-00:40.35), so if you want to write generated data into a file you should set a finite number. Anyway, I'll write a new audio stream class dedicated for the loops. I think that should fully enable the looping feature. Cheers.

FZFalzar commented 7 years ago

I'm just guessing, but could the AWB container hold the loop start/end times for the contained hca loop? I also remember seeing mention of loop start/end in the source code of "HCA Decoder 1.12", when you run it with option "i" (info)

hozuki commented 7 years ago

FYI, the loop range info is stored in HCA header. You can refer to HcaInfo.cs and HcaReader.cs and see how it works. I've almost completed simulated looping (by outputting looped section for multiple times). However there are still some blank periods whose causes are to be figured out. Right now I'm in the exam weeks so the progress will be somewhat slow. I checked the wave format this morning and found a good explanation for the LOOP section in (standard type) wave header. I'm not sure how would audio players (e.g. foobar2000, WMP) support this section, and the wave format has many sub-types. Anyway I'll test it.

hozuki commented 7 years ago

I finished simulated looping and tried to use your sample file as an input. However there is an IndexOutOfRangeException (in Channel.Decode1) thrown when I tried to decode it. Magic and checksum are all correct. I checked the cipher type property, both HcaReader class and hcainfo report that it is "not encrypted" (type 0). Then, to ensure that it is not a fault of arithmetic overflow, I tried to use hca2wav to decode it. Since hca2wav is written in C++ and it works as long as there is no invalid memory access, it gave me an decoded wave audio file. I was not surprised when I found the wave file is filled with noise. 😞 So could you please confirm your sample please?

FZFalzar commented 7 years ago

Hmm if my fears are true, it could be that what I posted is still encrypted, not as one of the HCA ciphers but by the CriWare plugin itself (usage of authenticationFile and enableAtomDecryption when initializing the CriWare system) which could have possibly encrypted the block data but keeping the header data in tact. Furthermore some RE indicates this is done within the native lib, so deriving the actual algo just became harder OTL

Recently alot of other games using CriWare (JPN mobages esp) also give rise to the same problem where the HCA extracted gives just noise when played although the headers look correct

I think it could be possible to create a system to "piggyback" the loading algorithm and dump the PCM data into its own file through IL injection, bypassing the encryption altogether.

Alternatively, create a Unity Project that uses the same method for decryption But that'll be for experimentation and future work

If I can get this working, I'll be sure to use DereTore/libcgss for conversion

Thanks for your time! And if you have any insight regarding RE of CriWare please do drop a PM :)

hozuki commented 7 years ago

Thank you for the reply.

As far as I know, a second encryption/decryption is possible using the authenticationFile. I tried to RE it last year but I thought it was too time-costing. The CRI plugin has its own file system abstraction (FS), audio output abstraction (Atom) and player (AtomEx). The plugin only exposes AtomEx APIs in CGSS's case so I can't study its audio output functions.

If we consider the plugin as a black box, we can intercept calls to it (initialization) and from it (audio output). By constructing a dummy shell we may be able to reuse the plugin to get waveform without cracking it. However there are pitfalls with this method: 1) Hooking system's audio APIs is hard. 2) CRI plugin is mainly used on platforms other than PC (iPhone, Android, consoles), making it harder to do the hooking. 3) We can only operate limited APIs exposed by the plugin. We can't control the inner processes.

If we consider the plugin as a open system, say, being REed, then everything will be fine, except... fighting with plugin's standalone (maybe complicated) IO API. A benefit is we can get what we want, especially by low level access of waveform generation.

I think I will try the first method to prove the possibility, and then the second. Either way will cost huge amount of time, and I won't have plenty of time until summer vacation. I mean, trying it will be exciting, as if holding the master key to most of the game audio files, but I have to put it later in my schedule.

It has been nice to discuss this problem with you and I hope to hear from you if there is any progress. 😃

FZFalzar commented 7 years ago

Hi, the other day I passed a key for the HCA file I uploaded, it turns out that the order of the hex that I posted was wrong; I swapped the positions of the first 8 and last 8 bytes and HCA Decoder v1.12 seemed to decode it properly, after some coaxing. Also what I've found out from some experimentation is that the authenticationFile seems to be unused for the entirety of Atom but has its part to play in Mana (by use of enableManaDecryption flag, mostly for video assets)

The authenticationFile is generated from one of the sdk apps that uses the key as a seed value of sorts, and the output is always 256 bytes long. I tested and confirmed this for a few games, by generating each file with its corresponding key and comparing it to each game's authenticationFile. Both the authenticationFile and key is then specified in the Unity Project Scene where the CriWare GameObject exists, then CriwareInitializer is called with these params, starting up the abstraction layers. I guess this partly makes up the reason why the key itself is rather difficult to extract, since it doesn't exist compiled into the code but rather, inside the scene that requires it.

hozuki commented 7 years ago

Thank you FZFalzar for exploration and such a detailed explanation. I've learned a lot from your words. :)

By the way did you try the new version of hca2wav (if you compiled it, since there are no new binaries released yet)? It should support finite times looping the looping range in an HCA file. I tested that with "holding" sound effect in CGSS and it works well.

FZFalzar commented 7 years ago

Hi, I have just built and tested it and it is working fine 👍 Maybe a suggestion would be to catch invalid key errors causing the program to throw "Index out of bounds" exception

Anyway I will close this issue now, all the best with your work and I would like to hear from you if you ever get down to doing the system-level stuff