PyCriCodecsEx 0.0.1__cp313-cp313-win32.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.
- CriCodecsEx.cp313-win32.pyd +0 -0
- PyCriCodecsEx/__init__.py +1 -0
- PyCriCodecsEx/acb.py +100 -0
- PyCriCodecsEx/adx.py +16 -0
- PyCriCodecsEx/awb.py +151 -0
- PyCriCodecsEx/chunk.py +75 -0
- PyCriCodecsEx/cpk.py +732 -0
- PyCriCodecsEx/hca.py +302 -0
- PyCriCodecsEx/usm.py +1266 -0
- PyCriCodecsEx/utf.py +704 -0
- pycricodecsex-0.0.1.dist-info/METADATA +81 -0
- pycricodecsex-0.0.1.dist-info/RECORD +15 -0
- pycricodecsex-0.0.1.dist-info/WHEEL +5 -0
- pycricodecsex-0.0.1.dist-info/licenses/LICENSE +21 -0
- pycricodecsex-0.0.1.dist-info/top_level.txt +2 -0
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
PyCriCodecsEx/acb.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from struct import iter_unpack
|
|
2
|
+
from typing import BinaryIO, List
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from PyCriCodecsEx.chunk import *
|
|
5
|
+
from PyCriCodecsEx.utf import UTF, UTFBuilder, UTFViewer
|
|
6
|
+
from PyCriCodecsEx.awb import AWB, AWBBuilder
|
|
7
|
+
from PyCriCodecsEx.hca import HCA
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
# Credit:
|
|
12
|
+
# - github.com/vgmstream/vgmstream which is why this is possible at all
|
|
13
|
+
# - Original work by https://github.com/Youjose/PyCriCodecs
|
|
14
|
+
# See Research/ACBSchema.py for more details.
|
|
15
|
+
|
|
16
|
+
class CueNameTable(UTFViewer):
|
|
17
|
+
CueIndex: int
|
|
18
|
+
CueName: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CueTable(UTFViewer):
|
|
22
|
+
CueId: int
|
|
23
|
+
ReferenceIndex: int
|
|
24
|
+
ReferenceType: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SequenceTable(UTFViewer):
|
|
28
|
+
TrackIndex: bytes
|
|
29
|
+
Type: int
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SynthTable(UTFViewer):
|
|
33
|
+
ReferenceItems: bytes
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TrackEventTable(UTFViewer):
|
|
37
|
+
Command: bytes
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TrackTable(UTFViewer):
|
|
41
|
+
EventIndex: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class WaveformTable(UTFViewer):
|
|
45
|
+
EncodeType: int
|
|
46
|
+
MemoryAwbId: int
|
|
47
|
+
NumChannels: int
|
|
48
|
+
NumSamples: int
|
|
49
|
+
SamplingRate: int
|
|
50
|
+
Streaming: int
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ACBTable(UTFViewer):
|
|
54
|
+
AcbGuid: bytes
|
|
55
|
+
Name: str
|
|
56
|
+
Version: int
|
|
57
|
+
VersionString: str
|
|
58
|
+
|
|
59
|
+
AwbFile: bytes
|
|
60
|
+
CueNameTable: List[CueNameTable]
|
|
61
|
+
CueTable: List[CueTable]
|
|
62
|
+
SequenceTable: List[SequenceTable]
|
|
63
|
+
SynthTable: List[SynthTable]
|
|
64
|
+
TrackEventTable: List[TrackEventTable]
|
|
65
|
+
TrackTable: List[TrackTable]
|
|
66
|
+
WaveformTable: List[WaveformTable]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ACB(UTF):
|
|
70
|
+
"""An ACB is basically a giant @UTF table. Use this class to extract any ACB, and potentially modifiy it in place."""
|
|
71
|
+
def __init__(self, filename) -> None:
|
|
72
|
+
super().__init__(filename,recursive=True)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def payload(self) -> dict:
|
|
76
|
+
"""Retrives the only top-level UTF table dict within the ACB file."""
|
|
77
|
+
return self.dictarray[0]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def view(self) -> ACBTable:
|
|
81
|
+
"""Returns a view of the ACB file, with all known tables mapped to their respective classes."""
|
|
82
|
+
return ACBTable(self.payload)
|
|
83
|
+
|
|
84
|
+
# TODO: Extraction routines
|
|
85
|
+
# See Research/ACBSchema.py. vgmstream presented 4 possible permutations of subsong retrieval.
|
|
86
|
+
|
|
87
|
+
class ACBBuilder:
|
|
88
|
+
acb: ACB
|
|
89
|
+
|
|
90
|
+
def __init__(self, acb: ACB) -> None:
|
|
91
|
+
self.acb = acb
|
|
92
|
+
|
|
93
|
+
def build(self) -> bytes:
|
|
94
|
+
"""Builds an ACB binary blob from the current ACB object.
|
|
95
|
+
|
|
96
|
+
The object may be modified in place before building, which will be reflected in the output binary.
|
|
97
|
+
"""
|
|
98
|
+
payload = deepcopy(self.acb.dictarray)
|
|
99
|
+
binary = UTFBuilder(payload, encoding=self.acb.encoding, table_name=self.acb.table_name)
|
|
100
|
+
return binary.bytes()
|
PyCriCodecsEx/adx.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import CriCodecsEx
|
|
2
|
+
|
|
3
|
+
class ADX:
|
|
4
|
+
"""ADX Module for decoding and encoding ADX files, pass the either `adx file` or `wav file` in bytes to either `decode` or `encode` respectively."""
|
|
5
|
+
|
|
6
|
+
# Decodes ADX to WAV.
|
|
7
|
+
@staticmethod
|
|
8
|
+
def decode(data: bytes) -> bytes:
|
|
9
|
+
""" Decodes ADX to WAV. """
|
|
10
|
+
return CriCodecsEx.AdxDecode(bytes(data))
|
|
11
|
+
|
|
12
|
+
# Encodes WAV to ADX.
|
|
13
|
+
@staticmethod
|
|
14
|
+
def encode(data: bytes, BitDepth = 0x4, Blocksize = 0x12, Encoding = 3, AdxVersion = 0x4, Highpass_Frequency = 0x1F4, Filter = 0, force_not_looping = False) -> bytes:
|
|
15
|
+
""" Encodes WAV to ADX. """
|
|
16
|
+
return CriCodecsEx.AdxEncode(bytes(data), BitDepth, Blocksize, Encoding, Highpass_Frequency, Filter, AdxVersion, force_not_looping)
|
PyCriCodecsEx/awb.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from io import BytesIO, FileIO
|
|
2
|
+
import os
|
|
3
|
+
from typing import BinaryIO
|
|
4
|
+
from struct import iter_unpack, pack
|
|
5
|
+
from PyCriCodecsEx.chunk import *
|
|
6
|
+
from PyCriCodecsEx.hca import HCA
|
|
7
|
+
|
|
8
|
+
# for AFS2 only.
|
|
9
|
+
class AWB:
|
|
10
|
+
""" Use this class to return any AWB data with the getfiles function. """
|
|
11
|
+
stream: BinaryIO
|
|
12
|
+
numfiles: int
|
|
13
|
+
align: int
|
|
14
|
+
subkey: bytes
|
|
15
|
+
version: int
|
|
16
|
+
ids: list
|
|
17
|
+
ofs: list
|
|
18
|
+
filename: str
|
|
19
|
+
headersize: int
|
|
20
|
+
id_alignment: int
|
|
21
|
+
|
|
22
|
+
def __init__(self, stream) -> None:
|
|
23
|
+
if type(stream) == str:
|
|
24
|
+
self.stream = FileIO(stream)
|
|
25
|
+
self.filename = stream
|
|
26
|
+
else:
|
|
27
|
+
self.stream = BytesIO(stream)
|
|
28
|
+
self.filename = ""
|
|
29
|
+
self.readheader()
|
|
30
|
+
|
|
31
|
+
def readheader(self):
|
|
32
|
+
# Reads header.
|
|
33
|
+
magic, self.version, offset_intsize, self.id_intsize, self.numfiles, self.align, self.subkey = AWBChunkHeader.unpack(
|
|
34
|
+
self.stream.read(AWBChunkHeader.size)
|
|
35
|
+
)
|
|
36
|
+
if magic != b'AFS2':
|
|
37
|
+
raise ValueError("Invalid AWB header.")
|
|
38
|
+
|
|
39
|
+
# Reads data in the header.
|
|
40
|
+
self.ids = list()
|
|
41
|
+
self.ofs = list()
|
|
42
|
+
for i in iter_unpack(f"<{self.stringtypes(self.id_intsize)}", self.stream.read(self.id_intsize*self.numfiles)):
|
|
43
|
+
self.ids.append(i[0])
|
|
44
|
+
for i in iter_unpack(f"<{self.stringtypes(offset_intsize)}", self.stream.read(offset_intsize*(self.numfiles+1))):
|
|
45
|
+
self.ofs.append(i[0] if i[0] % self.align == 0 else (i[0] + (self.align - (i[0] % self.align))))
|
|
46
|
+
|
|
47
|
+
# Seeks to files offset.
|
|
48
|
+
self.headersize = 16 + (offset_intsize*(self.numfiles+1)) + (self.id_intsize*self.numfiles)
|
|
49
|
+
if self.headersize % self.align != 0:
|
|
50
|
+
self.headersize = self.headersize + (self.align - (self.headersize % self.align))
|
|
51
|
+
self.stream.seek(self.headersize, 0)
|
|
52
|
+
|
|
53
|
+
def get_files(self):
|
|
54
|
+
""" Generator function to yield all data blobs from an AWB. """
|
|
55
|
+
for i in range(1, len(self.ofs)):
|
|
56
|
+
data = self.stream.read((self.ofs[i]-self.ofs[i-1]))
|
|
57
|
+
self.stream.seek(self.ofs[i], 0)
|
|
58
|
+
yield data
|
|
59
|
+
|
|
60
|
+
def get_file_at(self, index):
|
|
61
|
+
""" Gets you a file at specific index. """
|
|
62
|
+
index += 1
|
|
63
|
+
self.stream.seek(self.ofs[index], 0)
|
|
64
|
+
data = self.stream.read(self.ofs[index]-self.ofs[index-1])
|
|
65
|
+
self.stream.seek(self.headersize, 0) # Seeks back to headersize for getfiles.
|
|
66
|
+
return data
|
|
67
|
+
|
|
68
|
+
def stringtypes(self, intsize: int) -> str:
|
|
69
|
+
if intsize == 1:
|
|
70
|
+
return "B" # Probably impossible.
|
|
71
|
+
elif intsize == 2:
|
|
72
|
+
return "H"
|
|
73
|
+
elif intsize == 4:
|
|
74
|
+
return "I"
|
|
75
|
+
elif intsize == 8:
|
|
76
|
+
return "Q"
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError("Unknown int size.")
|
|
79
|
+
|
|
80
|
+
class AWBBuilder:
|
|
81
|
+
def __init__(self, infiles: list[bytes], subkey: int = 0, version: int = 2, id_intsize = 0x2, align: int = 0x20) -> None:
|
|
82
|
+
if version == 1 and subkey != 0:
|
|
83
|
+
raise ValueError("Cannot have a subkey with AWB version of 1.")
|
|
84
|
+
elif id_intsize not in [0x2, 0x4, 0x8]:
|
|
85
|
+
raise ValueError("id_intsize must be either 2, 4 or 8.")
|
|
86
|
+
self.infiles = infiles
|
|
87
|
+
self.version = version
|
|
88
|
+
self.align = align
|
|
89
|
+
self.subkey = subkey
|
|
90
|
+
self.id_intsize = id_intsize
|
|
91
|
+
|
|
92
|
+
def stringtypes(self, intsize: int) -> str:
|
|
93
|
+
if intsize == 1:
|
|
94
|
+
return "B" # Probably impossible.
|
|
95
|
+
elif intsize == 2:
|
|
96
|
+
return "H"
|
|
97
|
+
elif intsize == 4:
|
|
98
|
+
return "I"
|
|
99
|
+
elif intsize == 8:
|
|
100
|
+
return "Q"
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError("Unknown int size.")
|
|
103
|
+
|
|
104
|
+
def build(self) -> bytes:
|
|
105
|
+
size = 0
|
|
106
|
+
ofs = []
|
|
107
|
+
numfiles = 0
|
|
108
|
+
for file in self.infiles:
|
|
109
|
+
sz = len(file)
|
|
110
|
+
ofs.append(size+sz)
|
|
111
|
+
size += sz
|
|
112
|
+
numfiles += 1
|
|
113
|
+
|
|
114
|
+
if size > 0xFFFFFFFF:
|
|
115
|
+
intsize = 8 # Unsigned long long.
|
|
116
|
+
strtype = "<Q"
|
|
117
|
+
else:
|
|
118
|
+
intsize = 4 # Unsigned int, but could be a ushort, never saw it as one before though.
|
|
119
|
+
strtype = "<I"
|
|
120
|
+
|
|
121
|
+
header = AWBChunkHeader.pack(
|
|
122
|
+
b'AFS2', self.version, intsize, self.id_intsize, numfiles, self.align, self.subkey
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
id_strsize = f"<{self.stringtypes(self.id_intsize)}"
|
|
126
|
+
for i in range(numfiles):
|
|
127
|
+
header += pack(id_strsize, i)
|
|
128
|
+
|
|
129
|
+
headersize = len(header) + intsize * numfiles + intsize
|
|
130
|
+
aligned_header_size = headersize + (self.align - (headersize % self.align))
|
|
131
|
+
ofs2 = []
|
|
132
|
+
for idx, x in enumerate(ofs):
|
|
133
|
+
if (x+aligned_header_size) % self.align != 0 and idx != len(ofs) - 1:
|
|
134
|
+
ofs2.append((x+aligned_header_size) + (self.align - ((x+aligned_header_size) % self.align)))
|
|
135
|
+
else:
|
|
136
|
+
ofs2.append(x+aligned_header_size)
|
|
137
|
+
ofs = [headersize] + ofs2
|
|
138
|
+
|
|
139
|
+
for i in ofs:
|
|
140
|
+
header += pack(strtype, i)
|
|
141
|
+
|
|
142
|
+
if headersize % self.align != 0:
|
|
143
|
+
header = header.ljust(headersize + (self.align - (headersize % self.align)), b"\x00")
|
|
144
|
+
outfile = BytesIO()
|
|
145
|
+
outfile.write(header)
|
|
146
|
+
for idx, file in enumerate(self.infiles):
|
|
147
|
+
fl = file
|
|
148
|
+
if len(fl) % self.align != 0 and idx != len(self.infiles) - 1:
|
|
149
|
+
fl = fl.ljust(len(fl) + (self.align - (len(fl) % self.align)), b"\x00")
|
|
150
|
+
outfile.write(fl)
|
|
151
|
+
return outfile.getvalue()
|
PyCriCodecsEx/chunk.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from struct import Struct
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
UTFChunkHeader = Struct(">4sIIIIIHHI")
|
|
5
|
+
USMChunkHeader = Struct(">4sIBBHBBBBIIII")
|
|
6
|
+
CPKChunkHeader = Struct("<4sIII")
|
|
7
|
+
AWBChunkHeader = Struct("<4sBBHIHH")
|
|
8
|
+
SBTChunkHeader = Struct("<IIIII")
|
|
9
|
+
WavHeaderStruct = Struct("<4sI4s4sIHHIIHH") # This is wrong, FMT Struct should be on its own, away from RIFF.
|
|
10
|
+
WavSmplHeaderStruct = Struct("<4sIIIIIIIIIIIIIIII") # Supports only 1 looping point.
|
|
11
|
+
WavNoteHeaderStruct = Struct("<4sII")
|
|
12
|
+
WavDataHeaderStruct = Struct("<4sI")
|
|
13
|
+
AdxHeaderStruct = Struct(">HHBBBBIIHBB")
|
|
14
|
+
AdxLoopHeaderStruct = Struct(">HHHHIIII")
|
|
15
|
+
|
|
16
|
+
class USMChunckHeaderType(Enum):
|
|
17
|
+
CRID = b"CRID" # Header.
|
|
18
|
+
SFSH = b"SFSH" # SofDec1 Header?
|
|
19
|
+
SFV = b"@SFV" # Video (VP9/H264/MPEG).
|
|
20
|
+
SFA = b"@SFA" # Audio (HCA/ADX).
|
|
21
|
+
ALP = b"@ALP" # Rare. (Alpha video information).
|
|
22
|
+
CUE = b"@CUE" # Rare. (Cue points).
|
|
23
|
+
SBT = b"@SBT" # Rare. (Subtitle information).
|
|
24
|
+
AHX = b"@AHX" # Rare. (Ahx audio file? Used for SofDec1 only?)
|
|
25
|
+
USR = b"@USR" # Rare. (User data?)
|
|
26
|
+
PST = b"@PST" # Rare. (Unknown).
|
|
27
|
+
|
|
28
|
+
class CPKChunkHeaderType(Enum):
|
|
29
|
+
CPK = b"CPK " # Header.
|
|
30
|
+
TOC = b"TOC " # Cpkmode 1, 2, 3.
|
|
31
|
+
ITOC = b"ITOC" # Cpkmode 0, 2.
|
|
32
|
+
GTOC = b"GTOC" # Cpkmode 3.
|
|
33
|
+
ETOC = b"ETOC" # Any CpkMode. Not important.
|
|
34
|
+
HTOC = b"HTOC" # Unknown.
|
|
35
|
+
HGTOC = b"HGTOC"# Unknown.
|
|
36
|
+
|
|
37
|
+
class UTFType(Enum):
|
|
38
|
+
UTF = b"@UTF" # Header.
|
|
39
|
+
EUTF = b"\x1F\x9E\xF3\xF5" # Encrypted @UTF Header. Very likely exclusive to CPK's @UTF only.
|
|
40
|
+
|
|
41
|
+
class AWBType(Enum):
|
|
42
|
+
AFS2 = b"AFS2" # Header.
|
|
43
|
+
|
|
44
|
+
class HCAType(Enum):
|
|
45
|
+
HCA = b"HCA\x00" # Header.
|
|
46
|
+
EHCA = b"\xC8\xC3\xC1\x00" # Encrypted HCA header.
|
|
47
|
+
|
|
48
|
+
class VideoType(Enum):
|
|
49
|
+
IVF = b"DKIF" # Header.
|
|
50
|
+
# H264 = b"" # Header.
|
|
51
|
+
# MPEG = b"" # Header.
|
|
52
|
+
|
|
53
|
+
# I saw some devs swap the unsigned/signed indexes. So I am not sure what's correct or not.
|
|
54
|
+
# In my own experience, swapping those results in an incorrect signed values (should be unsigned) in ACB's/CPK's.
|
|
55
|
+
# If someone were to change this, they must change 'stringtypes' function in UTF/UTFBuilder classes.
|
|
56
|
+
class UTFTypeValues(Enum):
|
|
57
|
+
uchar = 0
|
|
58
|
+
char = 1
|
|
59
|
+
ushort = 2
|
|
60
|
+
short = 3
|
|
61
|
+
uint = 4
|
|
62
|
+
int = 5
|
|
63
|
+
ullong = 6
|
|
64
|
+
llong = 7
|
|
65
|
+
float = 8
|
|
66
|
+
double = 9 # Does not seem to exist.
|
|
67
|
+
string = 10
|
|
68
|
+
bytes = 11
|
|
69
|
+
|
|
70
|
+
class CriHcaQuality(Enum):
|
|
71
|
+
Highest = 0
|
|
72
|
+
High = 1
|
|
73
|
+
Middle = 2
|
|
74
|
+
Low = 3
|
|
75
|
+
Lowest = 5
|