PyCriCodecsEx 0.0.1__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
PyCriCodecsEx/hca.py ADDED
@@ -0,0 +1,302 @@
1
+ from io import BytesIO, FileIO
2
+ from struct import *
3
+ from typing import BinaryIO
4
+ from array import array
5
+ import CriCodecsEx
6
+
7
+ from PyCriCodecsEx.chunk import *
8
+
9
+ HcaHeaderStruct = Struct(">4sHH")
10
+ HcaFmtHeaderStruct = Struct(">4sIIHH")
11
+ HcaCompHeaderStruct = Struct(">4sHBBBBBBBBBB")
12
+ HcaDecHeaderStruct = Struct(">4sHBBBBBB")
13
+ HcaLoopHeaderStruct = Struct(">4sIIHH")
14
+ HcaAthHeaderStruct = Struct(">4sH")
15
+ HcaVbrHeaderStruct = Struct(">4sHH")
16
+ HcaCiphHeaderStruct = Struct(">4sH")
17
+ HcaRvaHeaderStruct = Struct(">4sf")
18
+
19
+ class HCA:
20
+ stream: BinaryIO
21
+ hcastream: BinaryIO
22
+ HcaSig: bytes
23
+ version: int
24
+ header_size: int
25
+ key: int
26
+ subkey: int
27
+ hca: dict
28
+ filetype: str
29
+ wavbytes: bytearray
30
+ hcabytes: bytearray
31
+ riffSignature: bytes
32
+ riffSize: int
33
+ wave: bytes
34
+ fmt: bytes
35
+ fmtSize: int
36
+ fmtType: int
37
+ fmtChannelCount: int
38
+ fmtSamplingRate: int
39
+ fmtSamplesPerSec: int
40
+ fmtSamplingSize: int
41
+ fmtBitCount: int
42
+ dataSig: bytes
43
+ dataSize: int
44
+ encrypted: bool
45
+ enc_table: array
46
+ table: array
47
+ looping: bool
48
+
49
+ def __init__(self, stream: BinaryIO, key: int = 0, subkey: int = 0) -> None:
50
+ if type(stream) == str:
51
+ self.stream = FileIO(stream)
52
+ self.hcastream = FileIO(stream)
53
+ else:
54
+ # copying since for encryption and decryption we use the internal buffer in C++.
55
+ stream = bytearray(stream).copy()
56
+ self.stream = BytesIO(stream)
57
+ self.hcastream = BytesIO(stream)
58
+ if type(key) == str:
59
+ self.key = int(key, 16)
60
+ else:
61
+ self.key = key
62
+ if type(subkey) == str:
63
+ self.subkey = int(subkey, 16)
64
+ else:
65
+ self.subkey = subkey
66
+ self.hcabytes: bytearray = b''
67
+ self.enc_table: array = b''
68
+ self.table: array = b''
69
+ self.Pyparse_header()
70
+
71
+
72
+ def Pyparse_header(self) -> None:
73
+ self.HcaSig, self.version, self.header_size = HcaHeaderStruct.unpack(
74
+ self.hcastream.read(HcaHeaderStruct.size)
75
+ )
76
+ if self.HcaSig in [HCAType.HCA.value, HCAType.EHCA.value]:
77
+ if not self.hcabytes:
78
+ self.filetype = "hca"
79
+ if self.HcaSig == HCAType.EHCA.value:
80
+ self.encrypted = True
81
+ else:
82
+ self.encrypted = False
83
+ if self.HcaSig != HCAType.HCA.value and self.HcaSig != HCAType.EHCA.value:
84
+ raise ValueError("Invalid HCA file.")
85
+ elif self.HcaSig == HCAType.EHCA.value and not self.key:
86
+ self.key = 0xCF222F1FE0748978 # Default HCA key.
87
+ elif self.key < 0:
88
+ raise ValueError("HCA key cannot be a negative.")
89
+ elif self.key > 0xFFFFFFFFFFFFFFFF:
90
+ raise OverflowError("HCA key cannot exceed the maximum size of 8 bytes.")
91
+ elif self.subkey < 0:
92
+ raise ValueError("HCA subkey cannot be a negative.")
93
+ elif self.subkey > 0xFFFF:
94
+ raise OverflowError("HCA subkey cannot exceed 65535.")
95
+
96
+ fmtsig, temp, framecount, encoder_delay, encoder_padding = HcaFmtHeaderStruct.unpack(
97
+ self.hcastream.read(HcaFmtHeaderStruct.size)
98
+ )
99
+ channelcount = temp >> 24
100
+ samplerate = temp & 0x00FFFFFF
101
+
102
+ self.hca = dict(
103
+ Encrypted = self.encrypted,
104
+ Header=self.HcaSig,
105
+ version=hex(self.version),
106
+ HeaderSize=self.header_size,
107
+ FmtSig = fmtsig,
108
+ ChannelCount = channelcount,
109
+ SampleRate = samplerate,
110
+ FrameCount = framecount,
111
+ EncoderDelay = encoder_delay,
112
+ EncoderPadding = encoder_padding,
113
+ )
114
+
115
+ while True:
116
+ sig = unpack(">I", self.hcastream.read(4))[0]
117
+ self.hcastream.seek(-4, 1)
118
+ sig = int.to_bytes(sig & 0x7F7F7F7F, 4, "big")
119
+ if sig == b"comp":
120
+ compsig, framesize, minres, maxres, trackcount, channelconfig, totalbandcount, basebandcount, stereobandcount, bandsperhfrgroup, r1, r2 = HcaCompHeaderStruct.unpack(
121
+ self.hcastream.read(HcaCompHeaderStruct.size)
122
+ )
123
+ self.hca.update(
124
+ dict(
125
+ CompSig = compsig,
126
+ FrameSize = framesize,
127
+ MinResolution = minres,
128
+ MaxResolution = maxres,
129
+ TrackCount = trackcount,
130
+ ChannelConfig = channelconfig,
131
+ TotalBandCount = totalbandcount,
132
+ BaseBandCount = basebandcount,
133
+ StereoBandCount = stereobandcount,
134
+ BandsPerHfrGroup = bandsperhfrgroup,
135
+ ReservedByte1 = r1,
136
+ ReservedByte2 = r2
137
+ )
138
+ )
139
+ elif sig == b"ciph":
140
+ ciphsig, ciphertype = HcaCiphHeaderStruct.unpack(
141
+ self.hcastream.read(HcaCiphHeaderStruct.size)
142
+ )
143
+ if ciphertype == 1:
144
+ self.encrypted = True
145
+ self.hca.update(dict(CiphSig = ciphsig, CipherType = ciphertype))
146
+ elif sig == b"loop":
147
+ self.looping = True
148
+ loopsig, loopstart, loopend, loopstartdelay, loopendpadding = HcaLoopHeaderStruct.unpack(
149
+ self.hcastream.read(HcaLoopHeaderStruct.size)
150
+ )
151
+ self.hca.update(dict(LoopSig = loopsig, LoopStart = loopstart, LoopEnd = loopend, LoopStartDelay = loopstartdelay, LoopEndPadding = loopendpadding))
152
+ elif sig == b"dec\00":
153
+ decsig, framesize, maxres, minres, totalbandcount, basebandcount, temp, stereotype = HcaDecHeaderStruct.unpack(
154
+ self.hcastream.read(HcaDecHeaderStruct.size)
155
+ )
156
+ trackcount = temp >> 4
157
+ channelconfig = temp & 0xF
158
+ self.hca.update(
159
+ dict(
160
+ DecSig = decsig,
161
+ FrameSize = framesize,
162
+ MinResolution = minres,
163
+ MaxResolution = maxres,
164
+ TotalBandCount = totalbandcount,
165
+ BaseBandCoung = basebandcount,
166
+ TrackCount = trackcount,
167
+ ChannelConfig = channelconfig,
168
+ StereoType = stereotype
169
+ )
170
+ )
171
+ elif sig == b"ath\00":
172
+ athsig, tabletype = HcaAthHeaderStruct.unpack(
173
+ self.hcastream.read(HcaAthHeaderStruct.size)
174
+ )
175
+ self.hca.update(dict(AthSig = athsig, TableType = tabletype))
176
+ elif sig == b"vbr\00":
177
+ vbrsig, maxframesize, noiselevel = HcaVbrHeaderStruct.unpack(
178
+ self.hcastream.read(HcaVbrHeaderStruct.size)
179
+ )
180
+ self.hca.update(dict(VbrSig = vbrsig, MaxFrameSize = maxframesize, NoiseLevel = noiselevel))
181
+ elif sig == b"rva\00":
182
+ rvasig, volume = HcaRvaHeaderStruct.unpack(
183
+ self.hcastream.read(HcaRvaHeaderStruct.size)
184
+ )
185
+ self.hca.update(dict(RvaSig = rvasig, Volume = volume))
186
+ else:
187
+ break
188
+ Crc16 = self.hcastream.read(2)
189
+ self.hca.update(dict(Crc16 = Crc16))
190
+
191
+ elif self.HcaSig == b"RIFF":
192
+ self.filetype = "wav"
193
+ self.riffSignature, self.riffSize, self.wave, self.fmt, self.fmtSize, self.fmtType, self.fmtChannelCount, self.fmtSamplingRate, self.fmtSamplesPerSec, self.fmtSamplingSize, self.fmtBitCount = WavHeaderStruct.unpack(
194
+ self.stream.read(WavHeaderStruct.size)
195
+ )
196
+ if self.riffSignature == b"RIFF" and self.wave == b'WAVE' and self.fmt == b'fmt ':
197
+ if self.fmtBitCount != 16:
198
+ raise ValueError(f"WAV bitdepth of {self.fmtBitCount} is not supported, only 16 bit WAV files are supported.")
199
+ elif self.fmtSize != 16:
200
+ raise ValueError(f"WAV file has an FMT chunk of an unsupported size: {self.fmtSize}, the only supported size is 16.")
201
+ if self.stream.read(4) == b"smpl":
202
+ self.stream.seek(-4, 1)
203
+ self.looping = True
204
+ # Will just be naming the important things here.
205
+ smplsig, smplesize, _, _, _, _, _, _, _, self.LoopCount, _, _, _, self.LoopStartSample, self.LoopEndSample, _, _ = WavSmplHeaderStruct.unpack(
206
+ self.stream.read(WavSmplHeaderStruct.size)
207
+ )
208
+ if self.LoopCount != 1:
209
+ self.looping = False # Unsupported multiple looping points, so backtracks, and ignores looping data.
210
+ self.stream.seek(-WavSmplHeaderStruct.size, 1)
211
+ self.stream.seek(8 + smplesize, 1)
212
+ else:
213
+ self.stream.seek(-4, 1)
214
+ self.looping = False
215
+ if self.stream.read(4) == b"note": # There's no use for this on ADX.
216
+ len = self.stream.read(4)
217
+ self.stream.seek(len+4) # + 1? + padding maybe?
218
+ else:
219
+ self.stream.seek(-4, 1)
220
+ if self.stream.read(4) == b"data":
221
+ self.stream.seek(-4, 1)
222
+ self.dataSig, self.dataSize = WavDataHeaderStruct.unpack(
223
+ self.stream.read(WavDataHeaderStruct.size)
224
+ )
225
+ else:
226
+ raise ValueError("Invalid or an unsupported wav file.")
227
+ else:
228
+ raise ValueError("Invalid HCA or WAV file.")
229
+ self.stream.seek(0)
230
+ self.hcastream.seek(0)
231
+
232
+ def info(self) -> dict:
233
+ """ Returns info related to the input file. """
234
+ if self.filetype == "hca":
235
+ return self.hca
236
+ elif self.filetype == "wav":
237
+ wav = dict(RiffSignature=self.riffSignature.decode(), riffSize=self.riffSize, WaveSignature=self.wave.decode(), fmtSignature=self.fmt.decode(), fmtSize=self.fmtSize, fmtType=self.fmtType, fmtChannelCount=self.fmtChannelCount, fmtSamplingRate=self.fmtSamplingRate, fmtSamplesPerSec=self.fmtSamplesPerSec, fmtSamplingSize=self.fmtSamplingSize, fmtBitCount=self.fmtBitCount, dataSignature=self.dataSig.decode(), dataSize=self.dataSize)
238
+ return wav
239
+
240
+ def decode(self) -> bytes:
241
+ if self.filetype == "wav":
242
+ raise ValueError("Input type for decoding must be an HCA file.")
243
+ self.hcastream.seek(0)
244
+ self.wavbytes = CriCodecsEx.HcaDecode(self.hcastream.read(), self.header_size, self.key, self.subkey)
245
+ self.stream = BytesIO(self.wavbytes)
246
+ self.hcastream.seek(0)
247
+ return bytes(self.wavbytes)
248
+
249
+ def encode(self, force_not_looping: bool = False, encrypt: bool = False, keyless: bool = False, quality_level: CriHcaQuality = CriHcaQuality.High) -> bytes:
250
+ if self.filetype == "hca":
251
+ raise ValueError("Input type for encoding must be a WAV file.")
252
+ if force_not_looping == False:
253
+ force_not_looping = 0
254
+ elif force_not_looping == True:
255
+ force_not_looping = 1
256
+ else:
257
+ raise ValueError("Forcing the encoder to not loop is by either False or True.")
258
+ if quality_level not in list(CriHcaQuality):
259
+ raise ValueError("Chosen quality level is not valid or is not the appropiate enumeration value.")
260
+ self.stream.seek(0)
261
+ self.hcabytes = CriCodecsEx.HcaEncode(self.stream.read(), force_not_looping, quality_level.value)
262
+ self.hcastream = BytesIO(self.hcabytes)
263
+ self.Pyparse_header()
264
+ if encrypt:
265
+ if self.key == 0 and not keyless:
266
+ self.key = 0xCF222F1FE0748978 # Default key.
267
+ self.encrypt(self.key, keyless)
268
+ return self.get_hca()
269
+
270
+ def encrypt(self, keycode: int, subkey: int = 0, keyless: bool = False) -> None:
271
+ if(self.encrypted):
272
+ raise ValueError("HCA is already encrypted.")
273
+ self.encrypted = True
274
+ enc = CriCodecsEx.HcaCrypt(self.get_hca(), 1, self.header_size, (1 if keyless else 56), keycode, subkey)
275
+ self.hcastream = BytesIO(enc)
276
+
277
+ def decrypt(self, keycode: int, subkey: int = 0) -> None:
278
+ if(not self.encrypted):
279
+ raise ValueError("HCA is already decrypted.")
280
+ self.encrypted = False
281
+ dec = CriCodecsEx.HcaCrypt(self.get_hca(), 0, self.header_size, 0, keycode, subkey)
282
+ self.hcastream = BytesIO(dec)
283
+
284
+ def get_hca(self) -> bytes:
285
+ """ Use this function to get the HCA file bytes after encrypting or decrypting. """
286
+ self.hcastream.seek(0)
287
+ fl: bytes = self.hcastream.read()
288
+ self.hcastream.seek(0)
289
+ return fl
290
+
291
+ def get_frames(self):
292
+ """ Generator function to yield Frame number, and Frame data. """
293
+ self.hcastream.seek(self.header_size, 0)
294
+ for i in range(self.hca['FrameCount']):
295
+ yield (i, self.hcastream.read(self.hca['FrameSize']))
296
+
297
+ def get_header(self) -> bytes:
298
+ """ Use this function to retrieve the HCA Header. """
299
+ self.hcastream.seek(0)
300
+ header = self.hcastream.read(self.header_size)
301
+ self.hcastream.seek(0)
302
+ return header