PyCriCodecsEx 0.0.1__cp312-cp312-musllinux_1_2_x86_64.whl → 0.0.3__cp312-cp312-musllinux_1_2_x86_64.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.
PyCriCodecsEx/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.1"
1
+ __version__ = "0.0.3"
PyCriCodecsEx/acb.py CHANGED
@@ -1,18 +1,17 @@
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
1
  # Credit:
12
2
  # - github.com/vgmstream/vgmstream which is why this is possible at all
13
3
  # - Original work by https://github.com/Youjose/PyCriCodecs
14
4
  # See Research/ACBSchema.py for more details.
15
5
 
6
+ from typing import Generator, List, Tuple, BinaryIO
7
+ from PyCriCodecsEx.chunk import *
8
+ from PyCriCodecsEx.utf import UTF, UTFBuilder, UTFViewer
9
+ from PyCriCodecsEx.hca import HCACodec
10
+ from PyCriCodecsEx.adx import ADXCodec
11
+ from PyCriCodecsEx.awb import AWB, AWBBuilder
12
+ from dataclasses import dataclass
13
+ from copy import deepcopy
14
+
16
15
  class CueNameTable(UTFViewer):
17
16
  CueIndex: int
18
17
  CueName: str
@@ -20,11 +19,13 @@ class CueNameTable(UTFViewer):
20
19
 
21
20
  class CueTable(UTFViewer):
22
21
  CueId: int
22
+ Length: int
23
23
  ReferenceIndex: int
24
24
  ReferenceType: int
25
25
 
26
26
 
27
27
  class SequenceTable(UTFViewer):
28
+ NumTracks : int
28
29
  TrackIndex: bytes
29
30
  Type: int
30
31
 
@@ -65,29 +66,204 @@ class ACBTable(UTFViewer):
65
66
  TrackTable: List[TrackTable]
66
67
  WaveformTable: List[WaveformTable]
67
68
 
69
+ @staticmethod
70
+ def decode_tlv(data : bytes):
71
+ pos = 0
72
+ while pos < len(data):
73
+ tag = data[pos : pos + 2]
74
+ length = data[pos + 3]
75
+ value = data[pos + 4 : pos + 4 + length]
76
+ pos += 3 + length
77
+ yield (tag, value)
78
+
79
+ def waveform_of_track(self, index: int):
80
+ tlv = self.decode_tlv(self.TrackEventTable[index])
81
+ def noteOn(data: bytes):
82
+ # Handle note on event
83
+ tlv_type, tlv_index = AcbTrackCommandNoteOnStruct.unpack(data[:AcbTrackCommandNoteOnStruct.size])
84
+ match tlv_type:
85
+ case 0x02: # Synth
86
+ yield from self.waveform_of_synth(tlv_index)
87
+ case 0x03: # Sequence
88
+ yield from self.waveform_of_sequence(tlv_index)
89
+ # Ignore others silently
90
+ for code, data in tlv:
91
+ match code:
92
+ case 2000:
93
+ yield from noteOn(data)
94
+ case 2003:
95
+ yield from noteOn(data)
96
+
97
+ def waveform_of_sequence(self, index : int):
98
+ seq = self.SequenceTable[index]
99
+ for i in range(seq.NumTracks):
100
+ track_index = int.from_bytes(seq.TrackIndex[i*2:i*2+2], 'big')
101
+ yield self.WaveformTable[track_index]
102
+
103
+ def waveform_of_synth(self, index: int):
104
+ item_type, item_index = AcbSynthReferenceStruct.unpack(self.SynthTable[index].ReferenceItems)
105
+ match item_type:
106
+ case 0x00: # No audio
107
+ return
108
+ case 0x01: # Waveform
109
+ yield self.WaveformTable[item_index]
110
+ case 0x02: # Yet another synth...
111
+ yield from self.waveform_of_synth(item_index)
112
+ case 0x03: # Sequence
113
+ yield from self.waveform_of_sequence(item_index)
114
+ case _:
115
+ raise NotImplementedError(f"Unknown synth reference type: {item_type} at index {index}")
116
+
117
+ def waveform_of(self, index : int) -> List["WaveformTable"]:
118
+ cue = next(filter(lambda c: c.CueId == index, self.CueTable), None)
119
+ assert cue, "cue of index %d not found" % index
120
+ match cue.ReferenceType:
121
+ case 0x01:
122
+ return [self.WaveformTable[index]]
123
+ case 0x02:
124
+ return list(self.waveform_of_synth(index))
125
+ case 0x03:
126
+ return list(self.waveform_of_sequence(index))
127
+ case 0x08:
128
+ raise NotImplementedError("BlockSequence type not implemented yet")
129
+ case _:
130
+ raise NotImplementedError(f"Unknown cue reference type: {cue.ReferenceType}")
131
+
132
+ @dataclass(frozen=True)
133
+ class CueItem:
134
+ CueId: int
135
+ CueName: str
136
+ Length: float
137
+ Waveforms: list[int] # List of waveform IDs
68
138
 
69
139
  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)
140
+ """Use this class to read, and modify ACB files in memory."""
141
+ def __init__(self, stream : str | BinaryIO) -> None:
142
+ """Loads an ACB file from the given stream.
143
+
144
+ Args:
145
+ stream (str | BinaryIO): The path to the ACB file or a BinaryIO stream containing the ACB data.
146
+ """
147
+ super().__init__(stream, recursive=True)
73
148
 
74
149
  @property
75
150
  def payload(self) -> dict:
76
- """Retrives the only top-level UTF table dict within the ACB file."""
151
+ """Retrives the only UTF table dict within the ACB file."""
77
152
  return self.dictarray[0]
78
153
 
79
154
  @property
80
155
  def view(self) -> ACBTable:
81
- """Returns a view of the ACB file, with all known tables mapped to their respective classes."""
156
+ """Returns a view of the ACB file, with all known tables mapped to their respective classes.
157
+
158
+ * Use this to interact with the ACB payload instead of `payload` for helper functions, etc"""
82
159
  return ACBTable(self.payload)
83
160
 
84
- # TODO: Extraction routines
85
- # See Research/ACBSchema.py. vgmstream presented 4 possible permutations of subsong retrieval.
161
+ @property
162
+ def name(self) -> str:
163
+ """Returns the name of the ACB file."""
164
+ return self.view.Name
165
+
166
+ @property
167
+ def awb(self) -> AWB:
168
+ """Returns the AWB object associated with the ACB."""
169
+ return AWB(self.view.AwbFile)
170
+
171
+ def get_waveforms(self) -> List[HCACodec | ADXCodec | Tuple[AcbEncodeTypes, int, int, int, bytes]]:
172
+ """Returns a list of decoded waveforms.
173
+
174
+ Item may be a codec (if known), or a tuple of (Codec ID, Channel Count, Sample Count, Sample Rate, Raw data).
175
+ """
176
+ CODEC_TABLE = {
177
+ AcbEncodeTypes.ADX: ADXCodec,
178
+ AcbEncodeTypes.HCA: HCACodec,
179
+ AcbEncodeTypes.HCAMX: HCACodec,
180
+ }
181
+ awb = self.awb
182
+ wavs = []
183
+ for wav in self.view.WaveformTable:
184
+ encode = AcbEncodeTypes(wav.EncodeType)
185
+ codec = (CODEC_TABLE.get(encode, None))
186
+ if codec:
187
+ wavs.append(codec(awb.get_file_at(wav.MemoryAwbId)))
188
+ else:
189
+ wavs.append((encode, wav.NumChannels, wav.NumSamples, wav.SamplingRate, awb.get_file_at(wav.MemoryAwbId)))
190
+ return wavs
191
+
192
+ def set_waveforms(self, value: List[HCACodec | ADXCodec | Tuple[AcbEncodeTypes, int, int, int, bytes]]):
193
+ """Sets the waveform data.
194
+
195
+ Input item may be a codec (if known), or a tuple of (Codec ID, Channel Count, Sample Count, Sample Rate, Raw data).
196
+
197
+ NOTE: Cue duration is not set. You need to change that manually.
198
+ """
199
+ WAVEFORM = self.view.WaveformTable[0]._payload.copy()
200
+ encoded = []
201
+ tables = self.view.WaveformTable
202
+ tables.clear()
203
+ for i, codec in enumerate(value):
204
+ if type(codec) == HCACodec:
205
+ encoded.append(codec.get_encoded())
206
+ tables.append(WaveformTable(WAVEFORM.copy()))
207
+ entry = tables[-1]
208
+ entry.EncodeType = AcbEncodeTypes.HCA.value
209
+ entry.NumChannels = codec.chnls
210
+ entry.NumSamples = codec.total_samples
211
+ entry.SamplingRate = codec.sampling_rate
212
+ elif type(codec) == ADXCodec:
213
+ encoded.append(codec.get_encoded())
214
+ tables.append(WaveformTable(WAVEFORM.copy()))
215
+ entry = tables[-1]
216
+ entry.EncodeType = AcbEncodeTypes.ADX.value
217
+ entry.NumChannels = codec.chnls
218
+ entry.NumSamples = codec.total_samples
219
+ entry.SamplingRate = codec.sampling_rate
220
+ elif isinstance(codec, tuple):
221
+ e_type, e_channels, e_samples, e_rate, e_data = codec
222
+ encoded.append(e_data)
223
+ tables.append(WaveformTable(WAVEFORM.copy()))
224
+ entry = tables[-1]
225
+ entry.EncodeType = e_type.value
226
+ entry.NumChannels = e_channels
227
+ entry.NumSamples = e_samples
228
+ entry.SamplingRate = e_rate
229
+ else:
230
+ raise TypeError(f"Unsupported codec type: {type(codec)}")
231
+ tables[-1].MemoryAwbId = i
232
+ awb = self.awb
233
+ self.view.AwbFile = AWBBuilder(encoded, awb.subkey, awb.version, align=awb.align).build()
234
+ pass
235
+
236
+ @property
237
+ def cues(self) -> Generator[CueItem, None, None]:
238
+ """Returns a generator of **read-only** Cues.
239
+
240
+ Cues reference waveform bytes by their AWB IDs, which can be accessed via `waveforms`.
241
+ To modify cues, use the `view` property instead.
242
+ """
243
+ for name, cue in zip(self.view.CueNameTable, self.view.CueTable):
244
+ waveforms = self.view.waveform_of(cue.CueId)
245
+ yield CueItem(cue.CueId, name.CueName, cue.Length / 1000.0, [waveform.MemoryAwbId for waveform in waveforms])
86
246
 
87
247
  class ACBBuilder:
248
+ """Use this class to build ACB files from an existing ACB object."""
88
249
  acb: ACB
89
250
 
90
251
  def __init__(self, acb: ACB) -> None:
252
+ """Initializes the ACBBuilder with an existing ACB object.
253
+
254
+ Args:
255
+ acb (ACB): The ACB object to build from.
256
+
257
+ Building ACB from scratch isn't planned for now since:
258
+
259
+ * We don't know how SeqCommandTable TLVs work. This is the biggest issue.
260
+ * Many fields are unknown or not well understood
261
+ - Games may expect AcfReferenceTable, Asiac stuff etc to be present for their own assets in conjunction
262
+ with their own ACF table. Missing these is not a fun debugging experience.
263
+ * ACB tables differ a LOT from game to game (e.g. Lipsync info), contary to USM formats.
264
+
265
+ Maybe one day I'll get around to this. But otherwise starting from nothing is a WONTFIX for now.
266
+ """
91
267
  self.acb = acb
92
268
 
93
269
  def build(self) -> bytes:
@@ -95,6 +271,9 @@ class ACBBuilder:
95
271
 
96
272
  The object may be modified in place before building, which will be reflected in the output binary.
97
273
  """
98
- payload = deepcopy(self.acb.dictarray)
99
- binary = UTFBuilder(payload, encoding=self.acb.encoding, table_name=self.acb.table_name)
274
+ # Check whether all AWB indices are valid
275
+ assert all(
276
+ waveform.MemoryAwbId < self.acb.awb.numfiles for waveform in self.acb.view.WaveformTable
277
+ ), "one or more AWB indices are out of range"
278
+ binary = UTFBuilder(self.acb.dictarray, encoding=self.acb.encoding, table_name=self.acb.table_name)
100
279
  return binary.bytes()
PyCriCodecsEx/adx.py CHANGED
@@ -1,8 +1,13 @@
1
+ from typing import BinaryIO
2
+ from io import BytesIO
3
+ from PyCriCodecsEx.chunk import *
1
4
  import CriCodecsEx
2
-
3
5
  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
+ """ADX class for decoding and encoding ADX files, pass the either `adx file` or `wav file` in bytes to either `decode` or `encode` respectively.
7
+
8
+ **NOTE:** Direct usage of this class is not recommended, use the `ADXCodec` wrapper instead.
9
+ """
10
+
6
11
  # Decodes ADX to WAV.
7
12
  @staticmethod
8
13
  def decode(data: bytes) -> bytes:
@@ -13,4 +18,138 @@ class ADX:
13
18
  @staticmethod
14
19
  def encode(data: bytes, BitDepth = 0x4, Blocksize = 0x12, Encoding = 3, AdxVersion = 0x4, Highpass_Frequency = 0x1F4, Filter = 0, force_not_looping = False) -> bytes:
15
20
  """ Encodes WAV to ADX. """
16
- return CriCodecsEx.AdxEncode(bytes(data), BitDepth, Blocksize, Encoding, Highpass_Frequency, Filter, AdxVersion, force_not_looping)
21
+ return CriCodecsEx.AdxEncode(bytes(data), BitDepth, Blocksize, Encoding, Highpass_Frequency, Filter, AdxVersion, force_not_looping)
22
+
23
+ class ADXCodec(ADX):
24
+ """Use this class for encoding and decoding ADX files, from and to WAV."""
25
+
26
+ CHUNK_INTERVAL = 99.9
27
+ BASE_FRAMERATE = 2997
28
+ # TODO: Move these to an enum
29
+ AUDIO_CODEC = 2
30
+ METADATA_COUNT = 0
31
+
32
+ filename : str
33
+ filesize : int
34
+
35
+ adx : bytes
36
+ header : bytes
37
+ sfaStream: BinaryIO
38
+
39
+ AdxDataOffset: int
40
+ AdxEncoding: int
41
+ AdxBlocksize: int
42
+ AdxSampleBitdepth: int
43
+ AdxChannelCount: int
44
+ AdxSamplingRate: int
45
+ AdxSampleCount: int
46
+ AdxHighpassFrequency: int
47
+ AdxVersion: int
48
+ AdxFlags: int
49
+
50
+ chnls: int
51
+ sampling_rate: int
52
+ total_samples: int
53
+ avbps: int
54
+
55
+ def __init__(self, stream: str | bytes, filename: str = "default.adx", bitdepth: int = 4, **kwargs):
56
+ """Initializes the ADX encoder/decoder
57
+
58
+ Args:
59
+ stream (str | bytes): Path to the ADX or WAV file, or a BinaryIO stream. WAV files will be automatically encoded with the given settings first.
60
+ filename (str, optional): Output filename. Defaults to "default.adx".
61
+ bitdepth (int, optional): Audio bit depth within [2,15]. Defaults to 4.
62
+ """
63
+ if type(stream) == str:
64
+ self.adx = open(stream, "rb").read()
65
+ else:
66
+ self.adx = stream
67
+ self.filename = filename
68
+ self.filesize = len(self.adx)
69
+ magic = self.adx[:4]
70
+ if magic == b"RIFF":
71
+ self.adx = self.encode(self.adx, bitdepth, force_not_looping=True)
72
+ self.sfaStream = BytesIO(self.adx)
73
+ header = AdxHeaderStruct.unpack(self.sfaStream.read(AdxHeaderStruct.size))
74
+ FourCC, self.AdxDataOffset, self.AdxEncoding, self.AdxBlocksize, self.AdxSampleBitdepth, self.AdxChannelCount, self.AdxSamplingRate, self.AdxSampleCount, self.AdxHighpassFrequency, self.AdxVersion, self.AdxFlags = header
75
+ assert FourCC == 0x8000, "either ADX or WAV is supported"
76
+ assert self.AdxVersion in {3,4}, "unsupported ADX version"
77
+ if self.AdxVersion == 4:
78
+ self.sfaStream.seek(4 + 4 * self.AdxChannelCount, 1) # Padding + Hist values, they always seem to be 0.
79
+ self.sfaStream.seek(0)
80
+ self.chnls = self.AdxChannelCount
81
+ self.sampling_rate = self.AdxSamplingRate
82
+ self.total_samples = self.AdxSampleCount
83
+ self.avbps = int(self.filesize * 8 * self.chnls) - self.filesize
84
+
85
+ def generate_SFA(self, index: int, builder):
86
+ # USMBuilder usage
87
+ current_interval = 0
88
+ stream_size = len(self.adx) - self.AdxBlocksize
89
+ chunk_size = int(self.AdxSamplingRate // (self.BASE_FRAMERATE / 100) // 32) * (self.AdxBlocksize * self.AdxChannelCount)
90
+ self.sfaStream.seek(0)
91
+ res = []
92
+ while self.sfaStream.tell() < stream_size:
93
+ if self.sfaStream.tell() > 0:
94
+ if self.sfaStream.tell() + chunk_size < stream_size:
95
+ datalen = chunk_size
96
+ else:
97
+ datalen = (stream_size - (self.AdxDataOffset + 4) - chunk_size) % chunk_size
98
+ else:
99
+ datalen = self.AdxDataOffset + 4
100
+ if not datalen:
101
+ break
102
+ padding = (0x20 - (datalen % 0x20) if datalen % 0x20 != 0 else 0)
103
+ SFA_chunk = USMChunkHeader.pack(
104
+ USMChunckHeaderType.SFA.value,
105
+ datalen + 0x18 + padding,
106
+ 0,
107
+ 0x18,
108
+ padding,
109
+ index,
110
+ 0,
111
+ 0,
112
+ 0,
113
+ round(current_interval),
114
+ self.BASE_FRAMERATE,
115
+ 0,
116
+ 0
117
+ )
118
+ chunk_data = self.sfaStream.read(datalen)
119
+ if builder.encrypt_audio:
120
+ SFA_chunk = builder.AudioMask(chunk_data)
121
+ SFA_chunk += chunk_data.ljust(datalen + padding, b"\x00")
122
+ current_interval += self.CHUNK_INTERVAL
123
+ res.append(SFA_chunk)
124
+ # ---
125
+ SFA_chunk = USMChunkHeader.pack(
126
+ USMChunckHeaderType.SFA.value,
127
+ 0x38,
128
+ 0,
129
+ 0x18,
130
+ 0,
131
+ index,
132
+ 0,
133
+ 0,
134
+ 2,
135
+ 0,
136
+ 30,
137
+ 0,
138
+ 0
139
+ )
140
+ SFA_chunk += b"#CONTENTS END ===============\x00"
141
+ res[-1] += SFA_chunk
142
+ return res
143
+
144
+ def get_metadata(self):
145
+ return None
146
+
147
+ def get_encoded(self):
148
+ """Gets the encoded ADX audio data."""
149
+ return self.adx
150
+
151
+ def save(self, filepath: str):
152
+ """Saves the encoded ADX audio to filepath"""
153
+ with open(filepath, "wb") as f:
154
+ f.write(self.decode(self.adx))
155
+
PyCriCodecsEx/awb.py CHANGED
@@ -19,16 +19,21 @@ class AWB:
19
19
  headersize: int
20
20
  id_alignment: int
21
21
 
22
- def __init__(self, stream) -> None:
22
+ def __init__(self, stream : str | BinaryIO) -> None:
23
+ """Initializes the AWB object
24
+
25
+ Args:
26
+ stream (str | BinaryIO): Source file path or binary stream
27
+ """
23
28
  if type(stream) == str:
24
29
  self.stream = FileIO(stream)
25
30
  self.filename = stream
26
31
  else:
27
32
  self.stream = BytesIO(stream)
28
33
  self.filename = ""
29
- self.readheader()
34
+ self._readheader()
30
35
 
31
- def readheader(self):
36
+ def _readheader(self):
32
37
  # Reads header.
33
38
  magic, self.version, offset_intsize, self.id_intsize, self.numfiles, self.align, self.subkey = AWBChunkHeader.unpack(
34
39
  self.stream.read(AWBChunkHeader.size)
@@ -39,9 +44,9 @@ class AWB:
39
44
  # Reads data in the header.
40
45
  self.ids = list()
41
46
  self.ofs = list()
42
- for i in iter_unpack(f"<{self.stringtypes(self.id_intsize)}", self.stream.read(self.id_intsize*self.numfiles)):
47
+ for i in iter_unpack(f"<{self._stringtypes(self.id_intsize)}", self.stream.read(self.id_intsize*self.numfiles)):
43
48
  self.ids.append(i[0])
44
- for i in iter_unpack(f"<{self.stringtypes(offset_intsize)}", self.stream.read(offset_intsize*(self.numfiles+1))):
49
+ for i in iter_unpack(f"<{self._stringtypes(offset_intsize)}", self.stream.read(offset_intsize*(self.numfiles+1))):
45
50
  self.ofs.append(i[0] if i[0] % self.align == 0 else (i[0] + (self.align - (i[0] % self.align))))
46
51
 
47
52
  # Seeks to files offset.
@@ -51,21 +56,20 @@ class AWB:
51
56
  self.stream.seek(self.headersize, 0)
52
57
 
53
58
  def get_files(self):
54
- """ Generator function to yield all data blobs from an AWB. """
59
+ """Generator function to yield all data blobs from an AWB. """
60
+ self.stream.seek(self.headersize, 0)
55
61
  for i in range(1, len(self.ofs)):
56
62
  data = self.stream.read((self.ofs[i]-self.ofs[i-1]))
57
63
  self.stream.seek(self.ofs[i], 0)
58
64
  yield data
59
65
 
60
- def get_file_at(self, index):
61
- """ Gets you a file at specific index. """
62
- index += 1
66
+ def get_file_at(self, index) -> bytes:
67
+ """Gets you a file at specific index. """
63
68
  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.
69
+ data = self.stream.read(self.ofs[index + 1]-self.ofs[index])
66
70
  return data
67
71
 
68
- def stringtypes(self, intsize: int) -> str:
72
+ def _stringtypes(self, intsize: int) -> str:
69
73
  if intsize == 1:
70
74
  return "B" # Probably impossible.
71
75
  elif intsize == 2:
@@ -78,7 +82,17 @@ class AWB:
78
82
  raise ValueError("Unknown int size.")
79
83
 
80
84
  class AWBBuilder:
85
+ """Use this class to build AWB files from a list of bytes."""
81
86
  def __init__(self, infiles: list[bytes], subkey: int = 0, version: int = 2, id_intsize = 0x2, align: int = 0x20) -> None:
87
+ """Initializes the AWB builder.
88
+
89
+ Args:
90
+ infiles (list[bytes]): List of bytes to be included in the AWB file.
91
+ subkey (int, optional): AWB subkey. Defaults to 0.
92
+ version (int, optional): AWB version. Defaults to 2.
93
+ id_intsize (hexadecimal, optional): Integer size (in bytes) for string lengths. Defaults to 0x2.
94
+ align (int, optional): Alignment. Defaults to 0x20.
95
+ """
82
96
  if version == 1 and subkey != 0:
83
97
  raise ValueError("Cannot have a subkey with AWB version of 1.")
84
98
  elif id_intsize not in [0x2, 0x4, 0x8]:
@@ -89,7 +103,7 @@ class AWBBuilder:
89
103
  self.subkey = subkey
90
104
  self.id_intsize = id_intsize
91
105
 
92
- def stringtypes(self, intsize: int) -> str:
106
+ def _stringtypes(self, intsize: int) -> str:
93
107
  if intsize == 1:
94
108
  return "B" # Probably impossible.
95
109
  elif intsize == 2:
@@ -102,6 +116,7 @@ class AWBBuilder:
102
116
  raise ValueError("Unknown int size.")
103
117
 
104
118
  def build(self) -> bytes:
119
+ """Builds the AWB file from the provided infiles bytes."""
105
120
  size = 0
106
121
  ofs = []
107
122
  numfiles = 0
@@ -122,7 +137,7 @@ class AWBBuilder:
122
137
  b'AFS2', self.version, intsize, self.id_intsize, numfiles, self.align, self.subkey
123
138
  )
124
139
 
125
- id_strsize = f"<{self.stringtypes(self.id_intsize)}"
140
+ id_strsize = f"<{self._stringtypes(self.id_intsize)}"
126
141
  for i in range(numfiles):
127
142
  header += pack(id_strsize, i)
128
143
 
PyCriCodecsEx/chunk.py CHANGED
@@ -12,7 +12,8 @@ WavNoteHeaderStruct = Struct("<4sII")
12
12
  WavDataHeaderStruct = Struct("<4sI")
13
13
  AdxHeaderStruct = Struct(">HHBBBBIIHBB")
14
14
  AdxLoopHeaderStruct = Struct(">HHHHIIII")
15
-
15
+ AcbSynthReferenceStruct = Struct(">HH")
16
+ AcbTrackCommandNoteOnStruct = Struct(">HH")
16
17
  class USMChunckHeaderType(Enum):
17
18
  CRID = b"CRID" # Header.
18
19
  SFSH = b"SFSH" # SofDec1 Header?
@@ -45,11 +46,6 @@ class HCAType(Enum):
45
46
  HCA = b"HCA\x00" # Header.
46
47
  EHCA = b"\xC8\xC3\xC1\x00" # Encrypted HCA header.
47
48
 
48
- class VideoType(Enum):
49
- IVF = b"DKIF" # Header.
50
- # H264 = b"" # Header.
51
- # MPEG = b"" # Header.
52
-
53
49
  # I saw some devs swap the unsigned/signed indexes. So I am not sure what's correct or not.
54
50
  # In my own experience, swapping those results in an incorrect signed values (should be unsigned) in ACB's/CPK's.
55
51
  # If someone were to change this, they must change 'stringtypes' function in UTF/UTFBuilder classes.
@@ -72,4 +68,25 @@ class CriHcaQuality(Enum):
72
68
  High = 1
73
69
  Middle = 2
74
70
  Low = 3
75
- Lowest = 5
71
+ Lowest = 5
72
+
73
+ class AcbEncodeTypes(Enum):
74
+ ADX = 0
75
+ PCM = 1
76
+ HCA = 2
77
+ ADX_ALT = 3
78
+ WiiDSP = 4
79
+ NDSDSP = 5
80
+ HCAMX = 6
81
+ VAG = 7
82
+ ATRAC3 = 8
83
+ CWAV = 9
84
+ HEVAG = 10
85
+ ATRAC9 = 11
86
+ X360XMA = 12
87
+ DSP = 13
88
+ CWACDSP = 14
89
+ PS4HEVAG = 15
90
+ PS4ATRAC9 = 16
91
+ AACM4A = 17
92
+ SwitchOpus = 18