1
0
mirror of synced 2025-01-18 22:24:04 +01:00

Implement super support for IFS extraction.

This commit is contained in:
Jennifer Taylor 2021-05-06 19:36:24 +00:00
parent b81d2aeaae
commit 74e3160588
2 changed files with 113 additions and 13 deletions

View File

@ -3,7 +3,7 @@ import io
import os
import struct
from PIL import Image # type: ignore
from typing import Dict, List, Tuple
from typing import Callable, Dict, List, Optional, Tuple
from bemani.protocol.binary import BinaryEncoding
from bemani.protocol.xml import XmlEncoding
@ -18,14 +18,23 @@ class IFS:
the games out there including non-rhythm games that use this format.
"""
def __init__(self, data: bytes, decode_binxml: bool=False, decode_textures: bool=False) -> None:
def __init__(
self,
data: bytes,
decode_binxml: bool=False,
decode_textures: bool=False,
keep_hex_names: bool=False,
reference_loader: Optional[Callable[[str], Optional["IFS"]]]=None,
) -> None:
self.__files: Dict[str, bytes] = {}
self.__formats: Dict[str, str] = {}
self.__compressed: Dict[str, bool] = {}
self.__imgsize: Dict[str, Tuple[int, int, int, int]] = {}
self.__uvsize: Dict[str, Tuple[int, int, int, int]] = {}
self.__decode_binxml = decode_binxml
self.__keep_hex_names = keep_hex_names
self.__decode_textures = decode_textures
self.__loader = reference_loader
self.__parse_file(data)
def __fix_name(self, filename: str) -> str:
@ -68,16 +77,39 @@ class IFS:
if header is None:
raise Exception('Invalid IFS file!')
files: Dict[str, Tuple[int, int, int]] = {}
files: Dict[str, Tuple[int, int, int, Optional[str]]] = {}
if header.name != 'imgfs':
raise Exception('Unknown IFS format!')
# Grab any super-files that this file might reference.
header_md5: Optional[int] = None
header_size: Optional[int] = None
supers: List[Tuple[str, bytes]] = [('__INVALID__', b'')]
for child in header.children:
if child.name == "_info_":
header_md5 = child.child_value('md5') # NOQA
header_size = child.child_value('size') # NOQA
elif child.name == "_super_":
super_name = child.value
super_md5 = child.child_value('md5')
supers.append((super_name, super_md5))
def get_children(parent: str, node: Node) -> None:
real_name = self.__fix_name(node.name)
if node.data_type == '3s32':
node_name = os.path.join(parent, real_name).replace(f'{os.sep}imgfs{os.sep}', '')
files[node_name] = (node.value[0] + data_index, node.value[1], node.value[2])
ref = None
for subnode in node.children:
if subnode.name == 'i':
super_ref = subnode.value
if super_ref > 0 or super_ref < len(supers):
ref = supers[super_ref][0]
else:
ref = supers[0][0]
files[node_name] = (node.value[0] + data_index, node.value[1], node.value[2], ref)
else:
for subchild in node.children:
get_children(os.path.join(parent, f"{real_name}{os.sep}"), subchild)
@ -85,9 +117,31 @@ class IFS:
# Recursively walk the entire filesystem extracting files and their locations.
get_children(os.sep, header)
# Cache of other file data.
otherdata: Dict[str, IFS] = {}
for fn in files:
(start, size, pack_time) = files[fn]
(start, size, pack_time, external_file) = files[fn]
if external_file is not None:
if external_file not in otherdata:
if self.__loader is None:
ifsdata = None
else:
ifsdata = self.__loader(external_file)
if ifsdata is None:
raise Exception(f"Couldn't extract file data for {fn} referencing IFS file {external_file}!")
else:
otherdata[external_file] = ifsdata
if fn in otherdata[external_file].filenames:
filedata = otherdata[external_file].read_file(fn)
else:
raise Exception(f"{fn} not found in {external_file} IFS!")
else:
filedata = data[start:(start + size)]
if len(filedata) != size:
raise Exception(f"Couldn't extract file data for {fn}!")
self.__files[fn] = filedata
# Now, find all of the index files that are available.
@ -101,8 +155,20 @@ class IFS:
benc = BinaryEncoding()
texdata = benc.decode(self.__files[filename])
if texdata is None:
# Now, try as XML
xenc = XmlEncoding()
texdata = xenc.decode(
b'<?xml encoding="ascii"?>' +
self.__files[filename]
)
if texdata is None:
continue
if texdata.name != 'texturelist':
raise Exception(f"Unexpected name {texdata.name} in texture list!")
if texdata.attribute('compress') == 'avslz':
compressed = True
else:
@ -131,6 +197,7 @@ class IFS:
# Remove old index, update file to new index.
self.__files[newname] = self.__files[oldname]
if not self.__keep_hex_names:
del self.__files[oldname]
# Remember the attributes for this file so we can extract it later.
@ -165,6 +232,17 @@ class IFS:
benc = BinaryEncoding()
afpdata = benc.decode(self.__files[filename])
if afpdata is None:
# Now, try as XML
xenc = XmlEncoding()
afpdata = xenc.decode(
b'<?xml encoding="ascii"?>' +
self.__files[filename]
)
if afpdata is None:
continue
if afpdata.name != 'afplist':
raise Exception(f"Unexpected name {afpdata.name} in afp list!")
@ -183,6 +261,7 @@ class IFS:
if oldname in self.__files:
# Remove old index, update file to new index.
self.__files[newname] = self.__files[oldname]
if not self.__keep_hex_names:
del self.__files[oldname]
# Now, fix up the shape files as well.
@ -198,6 +277,7 @@ class IFS:
if oldname in self.__files:
# Remove old index, update file to new index.
self.__files[newname] = self.__files[oldname]
if not self.__keep_hex_names:
del self.__files[oldname]
@property

View File

@ -1,6 +1,8 @@
import argparse
import os
from typing import Optional
from bemani.format import IFS
@ -34,11 +36,29 @@ def main() -> None:
root = root + '/'
root = os.path.realpath(root)
fp = open(args.file, 'rb')
fileroot = os.path.dirname(os.path.realpath(args.file))
def load_ifs(fname: str, root: bool=False) -> Optional[IFS]:
fname = os.path.join(fileroot, fname)
if os.path.isfile(fname):
fp = open(fname, 'rb')
data = fp.read()
fp.close()
ifs = IFS(data, args.convert_xml_files, args.convert_texture_files)
return IFS(
data,
decode_binxml=root and args.convert_xml_files,
decode_textures=root and args.convert_texture_files,
keep_hex_names=not root,
reference_loader=load_ifs,
)
else:
return None
ifs = load_ifs(args.file, root=True)
if ifs is None:
raise Exception(f"Couldn't locate file {args.file}!")
for fn in ifs.filenames:
print(f'Extracting {fn} to disk...')
realfn = os.path.join(root, fn)