PyCriCodecsEx 0.0.2__tar.gz → 0.0.5__tar.gz

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.
Files changed (33) hide show
  1. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/adx.cpp +2 -2
  2. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/crilayla.cpp +103 -64
  3. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/hca.cpp +2 -2
  4. pycricodecsex-0.0.5/PKG-INFO +35 -0
  5. pycricodecsex-0.0.5/PyCriCodecsEx/__init__.py +1 -0
  6. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/acb.py +67 -29
  7. pycricodecsex-0.0.5/PyCriCodecsEx/adx.py +158 -0
  8. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/awb.py +3 -3
  9. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/chunk.py +0 -5
  10. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/cpk.py +56 -50
  11. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/hca.py +170 -27
  12. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/usm.py +32 -266
  13. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/utf.py +8 -24
  14. pycricodecsex-0.0.5/PyCriCodecsEx.egg-info/PKG-INFO +35 -0
  15. pycricodecsex-0.0.5/README.md +13 -0
  16. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/setup.py +2 -2
  17. pycricodecsex-0.0.2/PKG-INFO +0 -86
  18. pycricodecsex-0.0.2/PyCriCodecsEx/__init__.py +0 -1
  19. pycricodecsex-0.0.2/PyCriCodecsEx/adx.py +0 -16
  20. pycricodecsex-0.0.2/PyCriCodecsEx.egg-info/PKG-INFO +0 -86
  21. pycricodecsex-0.0.2/README.md +0 -64
  22. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/CriCodecsEx.cpp +0 -0
  23. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/IO.cpp +0 -0
  24. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/IO.hpp +0 -0
  25. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/hca.h +0 -0
  26. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/pcm.cpp +0 -0
  27. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/LICENSE +0 -0
  28. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/SOURCES.txt +0 -0
  29. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/dependency_links.txt +0 -0
  30. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/requires.txt +0 -0
  31. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/top_level.txt +0 -0
  32. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/pyproject.toml +0 -0
  33. {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/setup.cfg +0 -0
@@ -542,7 +542,7 @@ static PyObject* AdxEncode(PyObject* self, PyObject* args){
542
542
  PyAdxSetError(AdxErrorCode);
543
543
  return NULL;
544
544
  }
545
- unsigned int len = adx.size;
545
+ Py_ssize_t len = adx.size;
546
546
  return Py_BuildValue("y#", adxdata, len);
547
547
  }
548
548
 
@@ -557,6 +557,6 @@ static PyObject* AdxDecode(PyObject* self, PyObject* args){
557
557
  return NULL;
558
558
  }
559
559
  unsigned char *out = wav.WAVEBuffer;
560
- unsigned int len = wav.wav.size+8;
560
+ Py_ssize_t len = wav.wav.size+8;
561
561
  return Py_BuildValue("y#", out, len);
562
562
  }
@@ -1,13 +1,19 @@
1
- /*
2
- CRI layla decompression.
3
- written by tpu. (https://forum.xentax.com/viewtopic.php?f=21&t=5137&p=44220&hilit=CRILAYLA#p44220)
4
- Python wrapper by me (and modification).
1
+ /* CRILAYLA Encoder/Decoder
5
2
 
6
- CRIcompress method by KenTse
7
- Taken from wmltogether's fork of CriPakTools.
8
- Python wrapper by me.
9
3
 
10
- Note: I have no idea how this compression technique works. I just made the wrapper.
4
+ CRI layla decompression.
5
+ written by tpu. (https://forum.xentax.com/viewtopic.php?f=21&t=5137&p=44220&hilit=CRILAYLA#p44220)
6
+ Python wrapper by https://github.com/Youjose/PyCriCodecs (and modification).
7
+
8
+ CRIcompress method by KenTse
9
+ Taken from wmltogether's fork of CriPakTools.
10
+ Python wrapper by https://github.com/Youjose/PyCriCodecs.
11
+ TODO: This implementation may produce larger output - which shouldn't be
12
+ possible with LZ-based compression. Investigate and fix.
13
+ For now, if compression fails, the original data is returned.
14
+ See also:
15
+ - https://github.com/FanTranslatorsInternational/Kuriimu2/blob/imgui/src/lib/Kompression/Encoder/CrilaylaEncoder.cs
16
+ - https://glinscott.github.io/lz/index.html
11
17
  */
12
18
  #define PY_SSIZE_T_CLEAN
13
19
  #pragma once
@@ -16,38 +22,43 @@
16
22
  #include <stdlib.h>
17
23
  #include <string.h>
18
24
 
25
+ unsigned fourCC(const char a, const char b, const char c, const char d) {
26
+ return (a << 0) | (b << 8) | (c << 16) | (d << 24);
27
+ };
28
+ const unsigned CRILAYLA_LO = fourCC('C', 'R', 'I', 'L');
29
+ const unsigned CRILAYLA_HI = fourCC('A', 'Y', 'L', 'A');
30
+ const unsigned long long CRILAYLA_MAGIC = CRILAYLA_LO | (unsigned long long)CRILAYLA_HI << 32uLL;
31
+
19
32
  struct crilayla_header{
20
33
  unsigned long long crilayla;
21
34
  unsigned int decompress_size;
22
35
  unsigned int compressed_size;
23
36
  };
24
37
 
25
-
26
- unsigned char *sbuf;
27
- unsigned int bitcnt;
28
- unsigned int bitdat;
29
- unsigned int get_bits(unsigned int n){
30
- unsigned int data, mask;
31
-
32
- if (bitcnt<n){
33
- data = ((24-bitcnt)>>3)+1;
34
- bitcnt += data*8;
35
- while(data) {
36
- bitdat = (bitdat<<8) | (*sbuf--);
37
- data--;
38
- }
39
- }
40
-
41
- data = bitdat>>(bitcnt-n);
42
- bitcnt -= n;
43
- mask = (1<<n)-1;
44
- data &= mask;
45
- return data;
46
- }
47
-
48
38
  unsigned int llcp_dec(unsigned char *src, unsigned int src_len, unsigned char *dst, unsigned int dst_len){
49
- unsigned char *dbuf, *pbuf;
39
+ unsigned char *dbuf, *pbuf;
50
40
  unsigned int plen, poffset, byte;
41
+ unsigned char *sbuf;
42
+ unsigned int bitcnt;
43
+ unsigned int bitdat;
44
+ auto get_bits = [&](unsigned int n){
45
+ unsigned int data, mask;
46
+
47
+ if (bitcnt<n){
48
+ data = ((24-bitcnt)>>3)+1;
49
+ bitcnt += data*8;
50
+ while(data) {
51
+ bitdat = (bitdat<<8) | (*sbuf--);
52
+ data--;
53
+ }
54
+ }
55
+
56
+ data = bitdat>>(bitcnt-n);
57
+ bitcnt -= n;
58
+ mask = (1<<n)-1;
59
+ data &= mask;
60
+ return data;
61
+ };
51
62
 
52
63
  sbuf = src+src_len-1;
53
64
  dbuf = dst+dst_len-1;
@@ -108,40 +119,41 @@ unsigned char* layla_decomp(unsigned char* data, crilayla_header header){
108
119
  return dst;
109
120
  }
110
121
 
111
- unsigned int layla_comp(unsigned char* dest, unsigned int* destLen, unsigned char* src, unsigned int srcLen){
112
- unsigned int n = srcLen - 1, m = *destLen - 0x1, T = 0, d = 0, p, q, i, j, k;
113
- unsigned char* odest = dest;
122
+ unsigned int layla_comp(unsigned char *dest, int *destLen, unsigned char *src, int srcLen)
123
+ {
124
+ int n = srcLen - 1, m = *destLen - 0x1, T = 0, d = 0, p, q, i, j, k;
125
+ unsigned char *odest = dest;
114
126
  for (; n >= 0x100;)
115
127
  {
116
128
  j = n + 3 + 0x2000;
117
- if (j > srcLen) j = srcLen;
118
- for (i = n + 3, p = 0; i < j; i++)
129
+ if (j>srcLen) j = srcLen;
130
+ for (i = n + 3, p = 0; i<j; i++)
119
131
  {
120
132
  for (k = 0; k <= n - 0x100; k++)
121
133
  {
122
134
  if (*(src + n - k) != *(src + i - k)) break;
123
135
  }
124
- if (k > p)
136
+ if (k>p)
125
137
  {
126
138
  q = i - n - 3; p = k;
127
139
  }
128
140
  }
129
- if (p < 3)
141
+ if (p<3)
130
142
  {
131
143
  d = (d << 9) | (*(src + n--)); T += 9;
132
144
  }
133
145
  else
134
146
  {
135
147
  d = (((d << 1) | 1) << 13) | q; T += 14; n -= p;
136
- if (p < 6)
148
+ if (p<6)
137
149
  {
138
150
  d = (d << 2) | (p - 3); T += 2;
139
151
  }
140
- else if (p < 13)
152
+ else if (p<13)
141
153
  {
142
154
  d = (((d << 2) | 3) << 3) | (p - 6); T += 5;
143
155
  }
144
- else if (p < 44)
156
+ else if (p<44)
145
157
  {
146
158
  d = (((d << 5) | 0x1f) << 5) | (p - 13); T += 10;
147
159
  }
@@ -150,45 +162,50 @@ unsigned int layla_comp(unsigned char* dest, unsigned int* destLen, unsigned cha
150
162
  d = ((d << 10) | 0x3ff); T += 10; p -= 44;
151
163
  for (;;)
152
164
  {
153
- for (; T >= 8;)
165
+ for (; m > 0 && T >= 8;)
154
166
  {
155
- *(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d & ((1 << T) - 1);
167
+ *(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d&((1 << T) - 1);
156
168
  }
157
- if (p < 255) break;
169
+ if (p<255) break;
158
170
  d = (d << 8) | 0xff; T += 8; p = p - 0xff;
159
171
  }
160
172
  d = (d << 8) | p; T += 8;
161
173
  }
162
174
  }
163
- for (; T >= 8;)
175
+ for (; m > 0 && T >= 8;)
164
176
  {
165
- *(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d & ((1 << T) - 1);
177
+ *(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d&((1 << T) - 1);
166
178
  }
167
179
  }
168
- if (T != 0)
180
+ if (m > 0 && T != 0)
169
181
  {
170
182
  *(dest + m--) = d << (8 - T);
171
183
  }
172
- *(dest + m--) = 0; *(dest + m) = 0;
173
- for (;;)
174
- {
175
- if (((*destLen - m) & 3) == 0) break;
176
- *(dest + m--) = 0;
184
+ if (m > 0) {
185
+ *(dest + m--) = 0; *(dest + m) = 0;
186
+ for (;;)
187
+ {
188
+ if (((*destLen - m) & 3) == 0) break;
189
+ *(dest + m--) = 0;
190
+ }
177
191
  }
192
+ if (m <= 0)
193
+ return 0; // Underflow
178
194
  *destLen = *destLen - m; dest += m;
179
- unsigned int l[] = { 0x4c495243,0x414c5941,srcLen - 0x100,*destLen };
180
- for (j = 0; j < 4; j++)
195
+ // CRIL AYLA srcLen-0x100 destLen
196
+ int l[] = { (int)CRILAYLA_LO,(int)CRILAYLA_HI,srcLen - 0x100,*destLen };
197
+ for (j = 0; j<4; j++)
181
198
  {
182
- for (i = 0; i < 4; i++)
199
+ for (i = 0; i<4; i++)
183
200
  {
184
201
  *(odest + i + j * 4) = l[j] & 0xff; l[j] >>= 8;
185
202
  }
186
203
  }
187
- for (j = 0, odest += 0x10; j < *destLen; j++)
204
+ for (j = 0, odest += 0x10; j<*destLen; j++)
188
205
  {
189
206
  *(odest++) = *(dest + j);
190
207
  }
191
- for (j = 0; j < 0x100; j++)
208
+ for (j = 0; j<0x100; j++)
192
209
  {
193
210
  *(odest++) = *(src + j);
194
211
  }
@@ -199,7 +216,17 @@ unsigned int layla_comp(unsigned char* dest, unsigned int* destLen, unsigned cha
199
216
  PyObject* CriLaylaDecompress(PyObject* self, PyObject* d){
200
217
  unsigned char *data = (unsigned char *)PyBytes_AsString(d);
201
218
  crilayla_header header = *(crilayla_header*)data;
202
- unsigned char *out = layla_decomp((data+16), header);
219
+
220
+ if (header.crilayla != CRILAYLA_MAGIC) {
221
+ PyErr_SetString(PyExc_ValueError, "Invalid CRILAYLA header.");
222
+ return NULL;
223
+ }
224
+
225
+ unsigned char *out;
226
+ Py_BEGIN_ALLOW_THREADS
227
+ out = layla_decomp((data+16), header);
228
+ Py_END_ALLOW_THREADS
229
+
203
230
  PyObject *outObj = Py_BuildValue("y#", out, header.decompress_size+256);
204
231
  delete[] out;
205
232
  return outObj;
@@ -213,14 +240,26 @@ unsigned char* CriLaylaDecompress(unsigned char* d){
213
240
 
214
241
  PyObject* CriLaylaCompress(PyObject* self, PyObject* args){
215
242
  unsigned char *data;
216
- unsigned int data_size;
243
+ Py_ssize_t data_size;
217
244
  if(!PyArg_ParseTuple(args, "y#", &data, &data_size)){
218
245
  return NULL;
219
246
  }
220
- unsigned char *buf = new unsigned char[data_size];
221
- memset(buf, 0, data_size);
222
- layla_comp(buf, &data_size, data, data_size);
223
- PyObject* bufObj = Py_BuildValue("y#", buf, data_size);
247
+
248
+ unsigned char *buf = new unsigned char[data_size + 0x200];
249
+
250
+ int compressed_size = data_size;
251
+ int res = 0;
252
+ Py_BEGIN_ALLOW_THREADS
253
+ res = layla_comp(buf, &compressed_size, data, compressed_size);
254
+ Py_END_ALLOW_THREADS
255
+ PyObject* bufObj;
256
+ if (res && res < data_size) {
257
+ bufObj = Py_BuildValue("y#", buf, compressed_size);
258
+ } else {
259
+ // FIXME
260
+ PyErr_SetString(PyExc_RuntimeError, "Compression failure.");
261
+ return NULL;
262
+ }
224
263
  delete[] buf;
225
264
  return bufObj;
226
265
  }
@@ -3296,7 +3296,7 @@ static PyObject* HcaCrypt(PyObject* self, PyObject* args){
3296
3296
  }
3297
3297
 
3298
3298
  unsigned char *buffer = (unsigned char *)view.buf;
3299
- unsigned int length = view.len;
3299
+ Py_ssize_t length = view.len;
3300
3300
  PyBuffer_Release(&view);
3301
3301
 
3302
3302
  clHCA* hca = (clHCA*)malloc(sizeof(clHCA));
@@ -3345,7 +3345,7 @@ static PyObject* HcaDecode(PyObject* self, PyObject* args){
3345
3345
  data *wavData = &wav.wav.chunks.WAVEdata;
3346
3346
 
3347
3347
  unsigned char *data;
3348
- unsigned int data_size;
3348
+ Py_ssize_t data_size;
3349
3349
  unsigned int header_size;
3350
3350
  unsigned long long keycode;
3351
3351
  unsigned short subkey;
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyCriCodecsEx
3
+ Version: 0.0.5
4
+ Summary: Criware formats library for Python
5
+ Home-page: https://mos9527.github.io/PyCriCodecsEx/
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Provides-Extra: usm
13
+ Requires-Dist: ffmpeg-python; extra == "usm"
14
+ Dynamic: classifier
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: license-file
19
+ Dynamic: provides-extra
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ PyCriCodecsEx
24
+ ---
25
+ A continuation of @Youjose's work on Criware formats. Feautres are still in flux and subject to change. When in doubt, Refer to the [original repo](https://github.com/Youjose/PyCriCodecs) for more information.
26
+
27
+ Detailed documentation, installation instructions are available at https://mos9527.com/PyCriCodecsEx
28
+
29
+
30
+ # Credits
31
+ - https://github.com/Youjose/PyCriCodecs
32
+ - https://github.com/Mikewando/PyCriCodecs ([PR#1 on USM](https://github.com/mos9527/PyCriCodecsEx/pull/1))
33
+ - https://github.com/donmai-me/WannaCRI
34
+ - https://github.com/vgmstream/vgmstream
35
+ - https://github.com/K0lb3/UnityPy (For CI script)
@@ -0,0 +1 @@
1
+ __version__ = "0.0.5"
@@ -3,22 +3,27 @@
3
3
  # - Original work by https://github.com/Youjose/PyCriCodecs
4
4
  # See Research/ACBSchema.py for more details.
5
5
 
6
- from typing import Generator, List, Tuple
6
+ from typing import Generator, List, Tuple, BinaryIO
7
7
  from PyCriCodecsEx.chunk import *
8
8
  from PyCriCodecsEx.utf import UTF, UTFBuilder, UTFViewer
9
- from PyCriCodecsEx.usm import HCACodec, ADXCodec
9
+ from PyCriCodecsEx.hca import HCACodec
10
+ from PyCriCodecsEx.adx import ADXCodec
10
11
  from PyCriCodecsEx.awb import AWB, AWBBuilder
11
12
  from dataclasses import dataclass
12
13
  from copy import deepcopy
13
14
 
14
15
  class CueNameTable(UTFViewer):
15
16
  CueIndex: int
17
+ '''Index into CueTable'''
16
18
  CueName: str
19
+ '''Name of the cue'''
17
20
 
18
21
 
19
22
  class CueTable(UTFViewer):
20
23
  CueId: int
24
+ '''Corresponds to the cue index found in CueNameTable'''
21
25
  Length: int
26
+ '''Duration of the cue in milliseconds'''
22
27
  ReferenceIndex: int
23
28
  ReferenceType: int
24
29
 
@@ -51,14 +56,21 @@ class WaveformTable(UTFViewer):
51
56
 
52
57
 
53
58
  class ACBTable(UTFViewer):
59
+ '''ACB Table View'''
60
+
54
61
  AcbGuid: bytes
62
+ '''GUID of the ACB. This SHOULD be different for each ACB file.'''
55
63
  Name: str
56
- Version: int
64
+ '''Name of the ACB. This is usually the name of the sound bank.'''
65
+ Version: int
57
66
  VersionString: str
58
67
 
59
68
  AwbFile: bytes
60
69
  CueNameTable: List[CueNameTable]
70
+ '''A list of cue names with their corresponding indices into CueTable'''
61
71
  CueTable: List[CueTable]
72
+ '''A list of cues with their corresponding references'''
73
+
62
74
  SequenceTable: List[SequenceTable]
63
75
  SynthTable: List[SynthTable]
64
76
  TrackEventTable: List[TrackEventTable]
@@ -66,7 +78,7 @@ class ACBTable(UTFViewer):
66
78
  WaveformTable: List[WaveformTable]
67
79
 
68
80
  @staticmethod
69
- def decode_tlv(data : bytes):
81
+ def _decode_tlv(data : bytes):
70
82
  pos = 0
71
83
  while pos < len(data):
72
84
  tag = data[pos : pos + 2]
@@ -75,16 +87,16 @@ class ACBTable(UTFViewer):
75
87
  pos += 3 + length
76
88
  yield (tag, value)
77
89
 
78
- def waveform_of_track(self, index: int):
79
- tlv = self.decode_tlv(self.TrackEventTable[index])
90
+ def _waveform_of_track(self, index: int):
91
+ tlv = self._decode_tlv(self.TrackEventTable[index])
80
92
  def noteOn(data: bytes):
81
93
  # Handle note on event
82
94
  tlv_type, tlv_index = AcbTrackCommandNoteOnStruct.unpack(data[:AcbTrackCommandNoteOnStruct.size])
83
95
  match tlv_type:
84
96
  case 0x02: # Synth
85
- yield from self.waveform_of_synth(tlv_index)
97
+ yield from self._waveform_of_synth(tlv_index)
86
98
  case 0x03: # Sequence
87
- yield from self.waveform_of_sequence(tlv_index)
99
+ yield from self._waveform_of_sequence(tlv_index)
88
100
  # Ignore others silently
89
101
  for code, data in tlv:
90
102
  match code:
@@ -93,13 +105,13 @@ class ACBTable(UTFViewer):
93
105
  case 2003:
94
106
  yield from noteOn(data)
95
107
 
96
- def waveform_of_sequence(self, index : int):
108
+ def _waveform_of_sequence(self, index : int):
97
109
  seq = self.SequenceTable[index]
98
110
  for i in range(seq.NumTracks):
99
111
  track_index = int.from_bytes(seq.TrackIndex[i*2:i*2+2], 'big')
100
112
  yield self.WaveformTable[track_index]
101
113
 
102
- def waveform_of_synth(self, index: int):
114
+ def _waveform_of_synth(self, index: int):
103
115
  item_type, item_index = AcbSynthReferenceStruct.unpack(self.SynthTable[index].ReferenceItems)
104
116
  match item_type:
105
117
  case 0x00: # No audio
@@ -107,38 +119,52 @@ class ACBTable(UTFViewer):
107
119
  case 0x01: # Waveform
108
120
  yield self.WaveformTable[item_index]
109
121
  case 0x02: # Yet another synth...
110
- yield from self.waveform_of_synth(item_index)
122
+ yield from self._waveform_of_synth(item_index)
111
123
  case 0x03: # Sequence
112
- yield from self.waveform_of_sequence(item_index)
124
+ yield from self._waveform_of_sequence(item_index)
113
125
  case _:
114
126
  raise NotImplementedError(f"Unknown synth reference type: {item_type} at index {index}")
115
127
 
116
128
  def waveform_of(self, index : int) -> List["WaveformTable"]:
129
+ """Retrieves the waveform(s) associated with a cue.
130
+
131
+ Cues may reference multiple waveforms, which could also be reused."""
117
132
  cue = next(filter(lambda c: c.CueId == index, self.CueTable), None)
118
133
  assert cue, "cue of index %d not found" % index
119
134
  match cue.ReferenceType:
120
135
  case 0x01:
121
136
  return [self.WaveformTable[index]]
122
137
  case 0x02:
123
- return list(self.waveform_of_synth(index))
138
+ return list(self._waveform_of_synth(index))
124
139
  case 0x03:
125
- return list(self.waveform_of_sequence(index))
140
+ return list(self._waveform_of_sequence(index))
126
141
  case 0x08:
127
142
  raise NotImplementedError("BlockSequence type not implemented yet")
128
143
  case _:
129
144
  raise NotImplementedError(f"Unknown cue reference type: {cue.ReferenceType}")
130
145
 
131
146
  @dataclass(frozen=True)
132
- class CueItem:
147
+ class PackedCueItem:
148
+ '''Helper class for read-only cue information'''
149
+
133
150
  CueId: int
151
+ '''Cue ID'''
134
152
  CueName: str
153
+ '''Cue name'''
135
154
  Length: float
136
- Waveforms: list[int] # List of waveform IDs
155
+ '''Duration in seconds'''
156
+ Waveforms: list[int]
157
+ '''List of waveform IDs, corresponds to ACB.get_waveforms()'''
137
158
 
138
159
  class ACB(UTF):
139
- """An ACB is basically a giant @UTF table. Use this class to extract any ACB, and potentially modifiy it in place."""
140
- def __init__(self, filename) -> None:
141
- super().__init__(filename,recursive=True)
160
+ """Use this class to read, and modify ACB files in memory."""
161
+ def __init__(self, stream : str | BinaryIO) -> None:
162
+ """Loads an ACB file from the given stream.
163
+
164
+ Args:
165
+ stream (str | BinaryIO): The path to the ACB file or a BinaryIO stream containing the ACB data.
166
+ """
167
+ super().__init__(stream, recursive=True)
142
168
 
143
169
  @property
144
170
  def payload(self) -> dict:
@@ -162,10 +188,17 @@ class ACB(UTF):
162
188
  """Returns the AWB object associated with the ACB."""
163
189
  return AWB(self.view.AwbFile)
164
190
 
165
- def get_waveforms(self) -> List[HCACodec | ADXCodec | Tuple[AcbEncodeTypes, int, int, int, bytes]]:
191
+ def get_waveforms(self, **kwargs) -> List[HCACodec | ADXCodec | Tuple[AcbEncodeTypes, int, int, int, bytes]]:
166
192
  """Returns a list of decoded waveforms.
167
193
 
168
194
  Item may be a codec (if known), or a tuple of (Codec ID, Channel Count, Sample Count, Sample Rate, Raw data).
195
+
196
+ Additional keyword arguments are passed to the codec constructors. e.g. for encrypted HCA payloads,
197
+ you may do the following:
198
+ ```python
199
+ get_waveforms(key=..., subkey=...)
200
+ ```
201
+ See also the respective docs (ADXCodec, HCACodec) for more details.
169
202
  """
170
203
  CODEC_TABLE = {
171
204
  AcbEncodeTypes.ADX: ADXCodec,
@@ -178,7 +211,7 @@ class ACB(UTF):
178
211
  encode = AcbEncodeTypes(wav.EncodeType)
179
212
  codec = (CODEC_TABLE.get(encode, None))
180
213
  if codec:
181
- wavs.append(codec(awb.get_file_at(wav.MemoryAwbId)))
214
+ wavs.append(codec(awb.get_file_at(wav.MemoryAwbId), **kwargs))
182
215
  else:
183
216
  wavs.append((encode, wav.NumChannels, wav.NumSamples, wav.SamplingRate, awb.get_file_at(wav.MemoryAwbId)))
184
217
  return wavs
@@ -188,7 +221,7 @@ class ACB(UTF):
188
221
 
189
222
  Input item may be a codec (if known), or a tuple of (Codec ID, Channel Count, Sample Count, Sample Rate, Raw data).
190
223
 
191
- NOTE: Cue duration is not set. You need to change that manually.
224
+ NOTE: Cue duration is not set. You need to change that manually - this is usually unecessary as the player will just play until the end of the waveform.
192
225
  """
193
226
  WAVEFORM = self.view.WaveformTable[0]._payload.copy()
194
227
  encoded = []
@@ -228,7 +261,7 @@ class ACB(UTF):
228
261
  pass
229
262
 
230
263
  @property
231
- def cues(self) -> Generator[CueItem, None, None]:
264
+ def cues(self) -> Generator[PackedCueItem, None, None]:
232
265
  """Returns a generator of **read-only** Cues.
233
266
 
234
267
  Cues reference waveform bytes by their AWB IDs, which can be accessed via `waveforms`.
@@ -236,20 +269,25 @@ class ACB(UTF):
236
269
  """
237
270
  for name, cue in zip(self.view.CueNameTable, self.view.CueTable):
238
271
  waveforms = self.view.waveform_of(cue.CueId)
239
- yield CueItem(cue.CueId, name.CueName, cue.Length / 1000.0, [waveform.MemoryAwbId for waveform in waveforms])
272
+ yield PackedCueItem(cue.CueId, name.CueName, cue.Length / 1000.0, [waveform.MemoryAwbId for waveform in waveforms])
240
273
 
241
274
  class ACBBuilder:
275
+ """Use this class to build ACB files from an existing ACB object."""
242
276
  acb: ACB
243
277
 
244
278
  def __init__(self, acb: ACB) -> None:
245
279
  """Initializes the ACBBuilder with an existing ACB object.
246
280
 
281
+ Args:
282
+ acb (ACB): The ACB object to build from.
283
+
247
284
  Building ACB from scratch isn't planned for now since:
248
- * We don't know how SeqCommandTable TLVs work. This is the biggest issue.
249
- * Many fields are unknown or not well understood
250
- - Games may expect AcfReferenceTable, Asiac stuff etc to be present for their own assets in conjunction
251
- with their own ACF table. Missing these is not a fun debugging experience.
252
- * ACB tables differ a LOT from game to game (e.g. Lipsync info), contary to USM formats.
285
+
286
+ * We don't know how SeqCommandTable TLVs work. This is the biggest issue.
287
+ * Many fields are unknown or not well understood
288
+ - Games may expect AcfReferenceTable, Asiac stuff etc to be present for their own assets in conjunction
289
+ with their own ACF table. Missing these is not a fun debugging experience.
290
+ * ACB tables differ a LOT from game to game (e.g. Lipsync info), contary to USM formats.
253
291
 
254
292
  Maybe one day I'll get around to this. But otherwise starting from nothing is a WONTFIX for now.
255
293
  """