1
0
mirror of synced 2024-11-27 23:50:47 +01:00
bemaniutils/bemani/format/arc.py

58 lines
1.8 KiB
Python

import struct
from typing import Dict, List, Tuple
from bemani.protocol.lz77 import Lz77
class ARC:
"""
Class representing an `.arc` file. These are found in DDR Ace, and possibly
other games that use ESS. Given a serires of bytes, this will allow you to
query included filenames as well as read the contents of any file inside the
archive.
"""
def __init__(self, data: bytes) -> None:
self.__files: Dict[str, Tuple[int, int, int]] = {}
self.__data = data
self.__parse_file(data)
def __parse_file(self, data: bytes) -> None:
# Check file header
if data[0:4] != bytes([0x20, 0x11, 0x75, 0x19]):
raise Exception("Unknown file format!")
# Grab header offsets
(_, numfiles, _) = struct.unpack("<III", data[4:16])
for fno in range(numfiles):
start = 16 + (16 * fno)
end = start + 16
(nameoffset, fileoffset, uncompressedsize, compressedsize) = struct.unpack(
"<IIII", data[start:end]
)
name = ""
while data[nameoffset] != 0:
name = name + data[nameoffset : (nameoffset + 1)].decode("ascii")
nameoffset = nameoffset + 1
self.__files[name] = (fileoffset, uncompressedsize, compressedsize)
@property
def filenames(self) -> List[str]:
return [f for f in self.__files]
def read_file(self, filename: str) -> bytes:
(fileoffset, uncompressedsize, compressedsize) = self.__files[filename]
if compressedsize == uncompressedsize:
# Just stored
return self.__data[fileoffset : (fileoffset + compressedsize)]
else:
# Compressed
lz77 = Lz77()
return lz77.decompress(
self.__data[fileoffset : (fileoffset + compressedsize)]
)