Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Commit e4f1bb2

Browse files
committed
usm: Default HCA codec decryption key should be the USM key itself (#4)
1 parent f085e3b commit e4f1bb2

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

.vscode/launch.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@
7878
"request": "launch",
7979
"module": "Tests.test_CPK",
8080
"justMyCode": false
81-
},
81+
},
82+
{
83+
"name": "Temp Issue 4",
84+
"type": "debugpy",
85+
"request": "launch",
86+
"module": "Tests.issue4",
87+
"justMyCode": false
88+
}
8289
]
8390
}

PyCriCodecsEx/usm.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class USMCrypt:
2020
videomask1: bytearray
2121
videomask2: bytearray
2222
audiomask: bytearray
23-
23+
usm_key: int = 0
2424
def init_key(self, key: str):
2525
if type(key) == str:
2626
if len(key) <= 16:
@@ -36,6 +36,7 @@ def init_key(self, key: str):
3636
raise ValueError(
3737
"Invalid key format, must be either a string or an integer."
3838
)
39+
self.usm_key = int.from_bytes(key1, "big") | (int.from_bytes(key2, "big") << 32)
3940
t = bytearray(0x20)
4041
t[0x00:0x09] = [
4142
key1[3],
@@ -506,13 +507,19 @@ def get_video(self) -> VP9Codec | H264Codec | MPEG1Codec:
506507
stream.filename = sfname
507508
return stream
508509

509-
def get_audios(self) -> List[ADXCodec | HCACodec]:
510-
"""Create a list of audio codecs from the available streams."""
510+
def get_audios(self, hca_key = 0, hca_subkey = 0) -> List[ADXCodec | HCACodec]:
511+
"""Create a list of audio codecs from the available streams.
512+
513+
Args:
514+
hca_key (int, optional): The HCA decryption key. Either int64 or a hex string. Defaults to 0 - in which
515+
case the key for USM (if used) would also be used for HCA decryption.
516+
hca_subkey (int, optional): The HCA decryption subkey. Either int64 or a hex string. Defaults to 0.
517+
"""
511518
match self.audio_codec:
512519
case ADXCodec.AUDIO_CODEC:
513520
return [ADXCodec(s[2], s[1]) for s in self.streams if s[0] == USMChunckHeaderType.SFA.value]
514521
case HCACodec.AUDIO_CODEC:
515-
return [HCACodec(s[2], s[1]) for s in self.streams if s[0] == USMChunckHeaderType.SFA.value] # HCAs are never encrypted in USM
522+
return [HCACodec(s[2], s[1], key=hca_key or self.usm_key, subkey=hca_subkey) for s in self.streams if s[0] == USMChunckHeaderType.SFA.value] # HCAs are never encrypted in USM
516523
case _:
517524
return []
518525

Tests/issue4.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# USM Sample from Digimon Story: Cyber Sleuth (PC)
2+
# Uses same key for both HCA and USM decryption
3+
# HCA Key {2897314143465725881}, // 283553DCE3FD5FB9
4+
# USM Key 2897314143465725881
5+
from . import sample_file_path, temp_file_path
6+
from PyCriCodecsEx.usm import USM, USMBuilder, ADXCodec, HCACodec
7+
8+
def test_usm_decode_and_mux():
9+
usm = USM(temp_file_path('S01_B.usm'), 2897314143465725881)
10+
audio = usm.get_audios()
11+
video = usm.get_video()
12+
audio = audio[0] if audio else None
13+
# Mux into MP4
14+
import ffmpeg, os
15+
def mux_av(video_src: str, audio_src: str, output: str, delete: bool = False):
16+
(
17+
ffmpeg.output(
18+
ffmpeg.input(video_src),
19+
ffmpeg.input(audio_src),
20+
output,
21+
vcodec='copy',
22+
acodec='copy',
23+
).overwrite_output()
24+
).run()
25+
if delete:
26+
print('* Cleaning up temporary files')
27+
os.unlink(video_src)
28+
os.unlink(audio_src)
29+
print(f'* Result available at: {output}')
30+
saved_video = temp_file_path('tmp_video.mp4')
31+
saved_audio = temp_file_path('tmp_audio.wav')
32+
saved_hca = temp_file_path('tmp_audio.hca')
33+
result = temp_file_path('muxed_result1.mp4')
34+
open(saved_hca,'wb').write(audio.get_hca())
35+
video.save(saved_video)
36+
audio.save(saved_audio)
37+
mux_av(saved_video, saved_audio, result)
38+
print('Remux Done.')
39+
40+
if __name__ == "__main__":
41+
test_usm_decode_and_mux()

0 commit comments

Comments
 (0)