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

Commit 20de049

Browse files
committed
Merge branch 'main' of github.com:mos9527/PyCriCodecs
2 parents 2456d34 + eaa5f17 commit 20de049

File tree

16 files changed

+214
-92
lines changed

16 files changed

+214
-92
lines changed

.github/workflows/tests.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Tests
2+
on: [push, pull_request, workflow_dispatch]
3+
4+
jobs:
5+
Test:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: FedericoCarboni/setup-ffmpeg@v3
9+
- uses: actions/checkout@v4
10+
- uses: actions/setup-python@v5
11+
with:
12+
python-version: '3.10'
13+
- name: Install dependencies
14+
run: |
15+
pip install -U pytest
16+
pip install .[usm]
17+
- name: Run tests
18+
run: |
19+
pytest -v -s

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ temp_*
77
*.aprof
88
*.pdb
99
dist/
10-
_*
10+
_*
11+
*.so

.vscode/launch.json

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,40 @@
55
"version": "0.2.0",
66
"configurations": [
77
{
8-
"name": "(gdb) Launch USMDecode",
8+
"name": "(gdb) Launch CRILAYLA",
99
"type": "cppdbg",
1010
"request": "launch",
1111
"program": "/opt/miniconda3/bin/python",
1212
"args": [
1313
"-m",
14-
"Tests.test_USMDecode"
14+
"Tests.test_CRILAYLA"
15+
],
16+
"stopAtEntry": false,
17+
"cwd": "${workspaceFolder}",
18+
"environment": [],
19+
"externalConsole": false,
20+
"MIMode": "gdb",
21+
"setupCommands": [
22+
{
23+
"description": "Enable pretty-printing for gdb",
24+
"text": "-enable-pretty-printing",
25+
"ignoreFailures": true
26+
},
27+
{
28+
"description": "Set Disassembly Flavor to Intel",
29+
"text": "-gdb-set disassembly-flavor intel",
30+
"ignoreFailures": true
31+
}
32+
]
33+
},
34+
{
35+
"name": "(gdb) Launch CPK",
36+
"type": "cppdbg",
37+
"request": "launch",
38+
"program": "/opt/miniconda3/bin/python",
39+
"args": [
40+
"-m",
41+
"Tests.test_CPK"
1542
],
1643
"stopAtEntry": false,
1744
"cwd": "${workspaceFolder}",
@@ -30,8 +57,7 @@
3057
"ignoreFailures": true
3158
}
3259
]
33-
},
34-
60+
},
3561
{
3662
"name": "Test: ACB",
3763
"type": "debugpy",

.vscode/settings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"files.associations": {
3+
"format": "cpp",
4+
"__locale": "cpp",
5+
"ios": "cpp",
6+
"array": "cpp",
7+
"string": "cpp",
8+
"string_view": "cpp",
9+
"ranges": "cpp",
10+
"span": "cpp",
11+
"vector": "cpp"
12+
}
13+
}

CriCodecsEx/adx.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ static PyObject* AdxEncode(PyObject* self, PyObject* args){
542542
PyAdxSetError(AdxErrorCode);
543543
return NULL;
544544
}
545-
unsigned int len = adx.size;
545+
Py_ssize_t len = adx.size;
546546
return Py_BuildValue("y#", adxdata, len);
547547
}
548548

@@ -557,6 +557,6 @@ static PyObject* AdxDecode(PyObject* self, PyObject* args){
557557
return NULL;
558558
}
559559
unsigned char *out = wav.WAVEBuffer;
560-
unsigned int len = wav.wav.size+8;
560+
Py_ssize_t len = wav.wav.size+8;
561561
return Py_BuildValue("y#", out, len);
562562
}

CriCodecsEx/crilayla.cpp

Lines changed: 87 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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
52
6-
CRIcompress method by KenTse
7-
Taken from wmltogether's fork of CriPakTools.
8-
Python wrapper by me.
93
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
1117
*/
1218
#define PY_SSIZE_T_CLEAN
1319
#pragma once
@@ -22,32 +28,30 @@ struct crilayla_header{
2228
unsigned int compressed_size;
2329
};
2430

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-
4831
unsigned int llcp_dec(unsigned char *src, unsigned int src_len, unsigned char *dst, unsigned int dst_len){
49-
unsigned char *dbuf, *pbuf;
32+
unsigned char *dbuf, *pbuf;
5033
unsigned int plen, poffset, byte;
34+
unsigned char *sbuf;
35+
unsigned int bitcnt;
36+
unsigned int bitdat;
37+
auto get_bits = [&](unsigned int n){
38+
unsigned int data, mask;
39+
40+
if (bitcnt<n){
41+
data = ((24-bitcnt)>>3)+1;
42+
bitcnt += data*8;
43+
while(data) {
44+
bitdat = (bitdat<<8) | (*sbuf--);
45+
data--;
46+
}
47+
}
48+
49+
data = bitdat>>(bitcnt-n);
50+
bitcnt -= n;
51+
mask = (1<<n)-1;
52+
data &= mask;
53+
return data;
54+
};
5155

5256
sbuf = src+src_len-1;
5357
dbuf = dst+dst_len-1;
@@ -108,40 +112,41 @@ unsigned char* layla_decomp(unsigned char* data, crilayla_header header){
108112
return dst;
109113
}
110114

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;
115+
unsigned int layla_comp(unsigned char *dest, int *destLen, unsigned char *src, int srcLen)
116+
{
117+
int n = srcLen - 1, m = *destLen - 0x1, T = 0, d = 0, p, q, i, j, k;
118+
unsigned char *odest = dest;
114119
for (; n >= 0x100;)
115120
{
116121
j = n + 3 + 0x2000;
117-
if (j > srcLen) j = srcLen;
118-
for (i = n + 3, p = 0; i < j; i++)
122+
if (j>srcLen) j = srcLen;
123+
for (i = n + 3, p = 0; i<j; i++)
119124
{
120125
for (k = 0; k <= n - 0x100; k++)
121126
{
122127
if (*(src + n - k) != *(src + i - k)) break;
123128
}
124-
if (k > p)
129+
if (k>p)
125130
{
126131
q = i - n - 3; p = k;
127132
}
128133
}
129-
if (p < 3)
134+
if (p<3)
130135
{
131136
d = (d << 9) | (*(src + n--)); T += 9;
132137
}
133138
else
134139
{
135140
d = (((d << 1) | 1) << 13) | q; T += 14; n -= p;
136-
if (p < 6)
141+
if (p<6)
137142
{
138143
d = (d << 2) | (p - 3); T += 2;
139144
}
140-
else if (p < 13)
145+
else if (p<13)
141146
{
142147
d = (((d << 2) | 3) << 3) | (p - 6); T += 5;
143148
}
144-
else if (p < 44)
149+
else if (p<44)
145150
{
146151
d = (((d << 5) | 0x1f) << 5) | (p - 13); T += 10;
147152
}
@@ -150,45 +155,50 @@ unsigned int layla_comp(unsigned char* dest, unsigned int* destLen, unsigned cha
150155
d = ((d << 10) | 0x3ff); T += 10; p -= 44;
151156
for (;;)
152157
{
153-
for (; T >= 8;)
158+
for (; m > 0 && T >= 8;)
154159
{
155-
*(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d & ((1 << T) - 1);
160+
*(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d&((1 << T) - 1);
156161
}
157-
if (p < 255) break;
162+
if (p<255) break;
158163
d = (d << 8) | 0xff; T += 8; p = p - 0xff;
159164
}
160165
d = (d << 8) | p; T += 8;
161166
}
162167
}
163-
for (; T >= 8;)
168+
for (; m > 0 && T >= 8;)
164169
{
165-
*(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d & ((1 << T) - 1);
170+
*(dest + m--) = (d >> (T - 8)) & 0xff; T -= 8; d = d&((1 << T) - 1);
166171
}
167172
}
168-
if (T != 0)
173+
if (m > 0 && T != 0)
169174
{
170175
*(dest + m--) = d << (8 - T);
171176
}
172-
*(dest + m--) = 0; *(dest + m) = 0;
173-
for (;;)
174-
{
175-
if (((*destLen - m) & 3) == 0) break;
176-
*(dest + m--) = 0;
177+
if (m > 0) {
178+
*(dest + m--) = 0; *(dest + m) = 0;
179+
for (;;)
180+
{
181+
if (((*destLen - m) & 3) == 0) break;
182+
*(dest + m--) = 0;
183+
}
177184
}
185+
if (m <= 0)
186+
return 0; // Underflow
178187
*destLen = *destLen - m; dest += m;
179-
unsigned int l[] = { 0x4c495243,0x414c5941,srcLen - 0x100,*destLen };
180-
for (j = 0; j < 4; j++)
188+
// CRIL AYLA srcLen-0x100 destLen
189+
int l[] = { 0x4c495243,0x414c5941,srcLen - 0x100,*destLen };
190+
for (j = 0; j<4; j++)
181191
{
182-
for (i = 0; i < 4; i++)
192+
for (i = 0; i<4; i++)
183193
{
184194
*(odest + i + j * 4) = l[j] & 0xff; l[j] >>= 8;
185195
}
186196
}
187-
for (j = 0, odest += 0x10; j < *destLen; j++)
197+
for (j = 0, odest += 0x10; j<*destLen; j++)
188198
{
189199
*(odest++) = *(dest + j);
190200
}
191-
for (j = 0; j < 0x100; j++)
201+
for (j = 0; j<0x100; j++)
192202
{
193203
*(odest++) = *(src + j);
194204
}
@@ -200,6 +210,11 @@ PyObject* CriLaylaDecompress(PyObject* self, PyObject* d){
200210
unsigned char *data = (unsigned char *)PyBytes_AsString(d);
201211
crilayla_header header = *(crilayla_header*)data;
202212

213+
if (header.crilayla != 0x414c59434152494cULL) {
214+
PyErr_SetString(PyExc_ValueError, "Invalid CRILAYLA header.");
215+
return NULL;
216+
}
217+
203218
unsigned char *out;
204219
Py_BEGIN_ALLOW_THREADS
205220
out = layla_decomp((data+16), header);
@@ -218,18 +233,26 @@ unsigned char* CriLaylaDecompress(unsigned char* d){
218233

219234
PyObject* CriLaylaCompress(PyObject* self, PyObject* args){
220235
unsigned char *data;
221-
unsigned int data_size;
236+
Py_ssize_t data_size;
222237
if(!PyArg_ParseTuple(args, "y#", &data, &data_size)){
223238
return NULL;
224239
}
225-
unsigned char *buf = new unsigned char[data_size];
226-
memset(buf, 0, data_size);
227240

241+
unsigned char *buf = new unsigned char[data_size + 0x200];
242+
243+
int compressed_size = data_size;
244+
int res = 0;
228245
Py_BEGIN_ALLOW_THREADS
229-
layla_comp(buf, &data_size, data, data_size);
246+
res = layla_comp(buf, &compressed_size, data, compressed_size);
230247
Py_END_ALLOW_THREADS
231-
232-
PyObject* bufObj = Py_BuildValue("y#", buf, data_size);
248+
PyObject* bufObj;
249+
if (res && res < data_size) {
250+
bufObj = Py_BuildValue("y#", buf, compressed_size);
251+
} else {
252+
// FIXME
253+
PyErr_SetString(PyExc_RuntimeError, "Compression failure.");
254+
return NULL;
255+
}
233256
delete[] buf;
234257
return bufObj;
235258
}

CriCodecsEx/hca.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3296,7 +3296,7 @@ static PyObject* HcaCrypt(PyObject* self, PyObject* args){
32963296
}
32973297

32983298
unsigned char *buffer = (unsigned char *)view.buf;
3299-
unsigned int length = view.len;
3299+
Py_ssize_t length = view.len;
33003300
PyBuffer_Release(&view);
33013301

33023302
clHCA* hca = (clHCA*)malloc(sizeof(clHCA));
@@ -3345,7 +3345,7 @@ static PyObject* HcaDecode(PyObject* self, PyObject* args){
33453345
data *wavData = &wav.wav.chunks.WAVEdata;
33463346

33473347
unsigned char *data;
3348-
unsigned int data_size;
3348+
Py_ssize_t data_size;
33493349
unsigned int header_size;
33503350
unsigned long long keycode;
33513351
unsigned short subkey;

PyCriCodecsEx/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.3"
1+
__version__ = "0.0.4"

PyCriCodecsEx/acb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ def set_waveforms(self, value: List[HCACodec | ADXCodec | Tuple[AcbEncodeTypes,
221221
222222
Input item may be a codec (if known), or a tuple of (Codec ID, Channel Count, Sample Count, Sample Rate, Raw data).
223223
224-
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.
225225
"""
226226
WAVEFORM = self.view.WaveformTable[0]._payload.copy()
227227
encoded = []

0 commit comments

Comments
 (0)