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.
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/adx.cpp +2 -2
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/crilayla.cpp +103 -64
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/hca.cpp +2 -2
- pycricodecsex-0.0.5/PKG-INFO +35 -0
- pycricodecsex-0.0.5/PyCriCodecsEx/__init__.py +1 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/acb.py +67 -29
- pycricodecsex-0.0.5/PyCriCodecsEx/adx.py +158 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/awb.py +3 -3
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/chunk.py +0 -5
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/cpk.py +56 -50
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/hca.py +170 -27
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/usm.py +32 -266
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx/utf.py +8 -24
- pycricodecsex-0.0.5/PyCriCodecsEx.egg-info/PKG-INFO +35 -0
- pycricodecsex-0.0.5/README.md +13 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/setup.py +2 -2
- pycricodecsex-0.0.2/PKG-INFO +0 -86
- pycricodecsex-0.0.2/PyCriCodecsEx/__init__.py +0 -1
- pycricodecsex-0.0.2/PyCriCodecsEx/adx.py +0 -16
- pycricodecsex-0.0.2/PyCriCodecsEx.egg-info/PKG-INFO +0 -86
- pycricodecsex-0.0.2/README.md +0 -64
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/CriCodecsEx.cpp +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/IO.cpp +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/IO.hpp +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/hca.h +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/CriCodecsEx/pcm.cpp +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/LICENSE +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/SOURCES.txt +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/dependency_links.txt +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/requires.txt +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/PyCriCodecsEx.egg-info/top_level.txt +0 -0
- {pycricodecsex-0.0.2 → pycricodecsex-0.0.5}/pyproject.toml +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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*
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
118
|
-
for (i = n + 3, p = 0; 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
|
|
136
|
+
if (k>p)
|
|
125
137
|
{
|
|
126
138
|
q = i - n - 3; p = k;
|
|
127
139
|
}
|
|
128
140
|
}
|
|
129
|
-
if (p
|
|
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
|
|
148
|
+
if (p<6)
|
|
137
149
|
{
|
|
138
150
|
d = (d << 2) | (p - 3); T += 2;
|
|
139
151
|
}
|
|
140
|
-
else if (p
|
|
152
|
+
else if (p<13)
|
|
141
153
|
{
|
|
142
154
|
d = (((d << 2) | 3) << 3) | (p - 6); T += 5;
|
|
143
155
|
}
|
|
144
|
-
else if (p
|
|
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
|
|
167
|
+
*(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d&((1 << T) - 1);
|
|
156
168
|
}
|
|
157
|
-
if (p
|
|
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
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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
|
|
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
|
|
204
|
+
for (j = 0, odest += 0x10; j<*destLen; j++)
|
|
188
205
|
{
|
|
189
206
|
*(odest++) = *(dest + j);
|
|
190
207
|
}
|
|
191
|
-
for (j = 0; 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
|
-
|
|
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
|
-
|
|
243
|
+
Py_ssize_t data_size;
|
|
217
244
|
if(!PyArg_ParseTuple(args, "y#", &data, &data_size)){
|
|
218
245
|
return NULL;
|
|
219
246
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
79
|
-
tlv = self.
|
|
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.
|
|
97
|
+
yield from self._waveform_of_synth(tlv_index)
|
|
86
98
|
case 0x03: # Sequence
|
|
87
|
-
yield from self.
|
|
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
|
|
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
|
|
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.
|
|
122
|
+
yield from self._waveform_of_synth(item_index)
|
|
111
123
|
case 0x03: # Sequence
|
|
112
|
-
yield from self.
|
|
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.
|
|
138
|
+
return list(self._waveform_of_synth(index))
|
|
124
139
|
case 0x03:
|
|
125
|
-
return list(self.
|
|
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
|
|
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
|
-
|
|
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
|
-
"""
|
|
140
|
-
def __init__(self,
|
|
141
|
-
|
|
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[
|
|
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
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
"""
|