PyCriCodecsEx 0.0.1__cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.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.
@@ -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