Module botw.rstb
Functions to estimate RSTB values for complex file types
Expand source code
""" Functions to estimate RSTB values for complex file types """
import math
import sys
from pathlib import Path
from typing import Union, Optional
import oead
from oead import yaz0
class BfresSizeGuesser:
multiplier_map = {
True: { # Big-Endian
True: { # Tex
range(0, 100): 9.0,
range(100, 2_000): 7.0,
range(2_000, 3_000): 5.0,
range(3_000, 4_000): 4.0,
range(4_000, 8_500): 3.0,
range(8_500, 12_000): 2.0,
range(12_000, 17_000): 1.75,
range(17_000, 30_000): 1.5,
range(30_000, 45_000): 1.3,
range(45_000, 100_000): 1.2,
range(100_000, 150_000): 1.1,
range(150_000, 200_000): 1.07,
range(200_000, 250_000): 1.045,
range(250_000, 300_000): 1.035,
range(300_000, 600_000): 1.03,
range(600_000, 1_000_000): 1.015,
range(1_000_000, 1_800_000): 1.009,
range(1_800_000, 4_500_000): 1.005,
range(4_500_000, 6_000_000): 1.002,
range(6_000_000, sys.maxsize): 1.0015,
},
False: { # Model
range(0, 500): 7.0,
range(500, 750): 5.0,
range(750, 1_250): 4.0,
range(1_250, 2_000): 3.5,
range(2_000, 400_000): 2.25,
range(400_000, 600_000): 2.1,
range(600_000, 1_000_000): 1.95,
range(1_000_000, 1_500_000): 1.85,
range(1_500_000, 3_000_000): 1.66,
range(3_000_000, sys.maxsize): 1.45,
},
},
False: { # Little-Endian
True: { # Tex
range(0, 10_000): 2.0,
range(10_000, 30_000): 1.5,
range(30_000, 50_000): 1.3,
range(50_000, sys.maxsize): 1.2,
},
False: { # Model
range(0, 1_250): 9.5,
range(1_250, 2_500): 6.0,
range(2_500, 50_000): 4.25,
range(50_000, 100_000): 3.66,
range(100_000, 800_000): 3.5,
range(800_000, 2_000_000): 3.15,
range(2_000_000, 3_000_000): 2.5,
range(3_000_000, 4_000_000): 1.667,
range(4_000_000, sys.maxsize): 1.5,
},
},
}
@classmethod
def guess(cls, be: bool, tex: bool, size: int):
size = int(size * 1.05)
for k, v in cls.multiplier_map[be][tex].items():
if size in k:
return size * v
class AampSizeGuesser:
multiplier_map = {
".baiprog": {
range(0, 380): 7.0,
range(380, 400): 6.0,
range(400, 450): 5.5,
range(450, 600): 5.0,
range(600, 1_000): 4.0,
range(1_000, 1_750): 3.5,
range(1_750, sys.maxsize): 3.0,
},
".bas": {
range(0, 100): 20.0,
range(100, 200): 12.5,
range(200, 300): 10.0,
range(300, 600): 8.0,
range(600, 1_500): 6.0,
range(1_500, 2_000): 5.5,
range(2_000, 15_000): 5.0,
range(15_000, sys.maxsize): 4.5,
},
".baslist": {
range(0, 100): 15.0,
range(100, 200): 10.0,
range(200, 300): 8.0,
range(300, 500): 6.0,
range(500, 800): 5.0,
range(800, 4_000): 4.0,
range(4_000, sys.maxsize): 3.5,
},
".bdrop": {
range(0, 200): 8.5,
range(200, 250): 7.0,
range(250, 350): 6.0,
range(350, 450): 5.25,
range(450, 850): 4.5,
range(850, sys.maxsize): 4.0,
},
".bgparamlist": {
range(0, 100): 20.0,
range(100, 150): 12.0,
range(150, 250): 10.0,
range(250, 350): 8.0,
range(350, 450): 7.0,
range(450, sys.maxsize): 6.0,
},
".brecipe": {
range(0, 100): 12.5,
range(100, 160): 8.5,
range(160, 200): 7.5,
range(200, 215): 7.0,
range(215, sys.maxsize): 6.5,
},
".bshop": {
range(0, 200): 7.25,
range(200, 400): 6.0,
range(400, 500): 5.0,
range(500, sys.maxsize): 4.05,
},
".bxml": {
range(0, 350): 6.0,
range(350, 450): 5.0,
range(450, 550): 4.5,
range(550, 650): 4.0,
range(650, 800): 3.5,
range(800, sys.maxsize): 3.0,
},
}
@classmethod
def guess(cls, be: bool, ext: str, size: int):
size *= 1.05
if ext == ".bas":
size *= 1.05 # I guess?
size = int(size)
if ext in cls.multiplier_map:
for k, v in cls.multiplier_map[ext].items():
if size in k:
ret = size * v
break
else:
raise NotImplementedError("Cannot happen ever lol")
elif ext == ".bdmgparam":
ret = (((-0.0018 * size) + 6.6273) * size) + 500
elif ext == ".bphysics":
ret = (((int(size) + 32) & -32) + 0x4E + 0x324) * max(
4 * math.floor(size / 1388), 3
)
else:
ret = 0
return int(ret * 1.5 if not be else ret)
def guess_bfres_size(file: Union[Path, bytes], be: bool, name: Optional[str]) -> int:
"""Attempts to estimate a valid RSTB value for a BFRES file. Decompresses first if yaz0 encoded.
Args:
file (Union[Path, bytes]): The file to analyze, either as a Path or bytes.
be (bool): Big-Endian (True) or Little-Endian (False)
name (Optional[str]): The name of the BFRES file, used to detect type when there is no path.
Raises:
ValueError: Raises error if BFRES name is blank when passing file as bytes
Returns:
int: Return an estimated RSTB value
"""
if isinstance(file, bytes) or isinstance(file, memoryview) or isinstance(file, oead.Bytes):
real_size = (
yaz0.get_header(file[0:16]).uncompressed_size
if file[:4] == b"Yaz0"
else len(file)
)
elif isinstance(file, Path):
name = file.name if not name else name
with file.open("rb") as f:
chunk = f.read(16)
real_size = (
yaz0.get_header(chunk).uncompressed_size
if chunk[:4] == b"Yaz0"
else file.stat().st_size
)
else:
raise NotImplementedError()
if not name:
raise ValueError("BFRES name must not be blank if passing file as bytes.")
return int(BfresSizeGuesser.guess(be, ".Tex" in name, real_size))
def guess_aamp_size(file: Union[Path, bytes], be: bool, ext: Optional[str]) -> int:
"""Attempts to estimate a valid RSTB value for an AAMP file. Decompresses first if yaz0 encoded.
Args:
file (Union[Path, bytes]): The file to analyze, either as a Path or bytes.
be (bool): Big-Endian (True) or Little-Endian (False)
ext (Optional[str]): The extension of the AAMP file, used to detect type when there is no path.
Raises:
ValueError: Raises error if extension is blank when passing file as bytes
Returns:
int: Returns an estimated RSTB value, or 0 for unsupported AAMP types.
"""
if isinstance(file, bytes) or isinstance(file, memoryview) or isinstance(file, oead.Bytes):
real_size = (
yaz0.get_header(file[0:16]).uncompressed_size
if file[:4] == b"Yaz0"
else len(file)
)
elif isinstance(file, Path):
ext = file.name if not ext else ext
with file.open("rb") as f:
chunk = f.read(16)
real_size = (
yaz0.get_header(chunk).uncompressed_size
if chunk[:4] == b"Yaz0"
else file.stat().st_size
)
else:
raise NotImplementedError()
if not ext:
raise ValueError("AAMP extension must not be blank if passing file as bytes.")
return int(AampSizeGuesser.guess(be, ext, real_size))
Functions
def guess_aamp_size(file: Union[pathlib.Path, bytes], be: bool, ext: Union[str, NoneType]) ‑> int
-
Attempts to estimate a valid RSTB value for an AAMP file. Decompresses first if yaz0 encoded.
Args
file
:Union[Path, bytes]
- The file to analyze, either as a Path or bytes.
be
:bool
- Big-Endian (True) or Little-Endian (False)
ext
:Optional[str]
- The extension of the AAMP file, used to detect type when there is no path.
Raises
ValueError
- Raises error if extension is blank when passing file as bytes
Returns
int
- Returns an estimated RSTB value, or 0 for unsupported AAMP types.
Expand source code
def guess_aamp_size(file: Union[Path, bytes], be: bool, ext: Optional[str]) -> int: """Attempts to estimate a valid RSTB value for an AAMP file. Decompresses first if yaz0 encoded. Args: file (Union[Path, bytes]): The file to analyze, either as a Path or bytes. be (bool): Big-Endian (True) or Little-Endian (False) ext (Optional[str]): The extension of the AAMP file, used to detect type when there is no path. Raises: ValueError: Raises error if extension is blank when passing file as bytes Returns: int: Returns an estimated RSTB value, or 0 for unsupported AAMP types. """ if isinstance(file, bytes) or isinstance(file, memoryview) or isinstance(file, oead.Bytes): real_size = ( yaz0.get_header(file[0:16]).uncompressed_size if file[:4] == b"Yaz0" else len(file) ) elif isinstance(file, Path): ext = file.name if not ext else ext with file.open("rb") as f: chunk = f.read(16) real_size = ( yaz0.get_header(chunk).uncompressed_size if chunk[:4] == b"Yaz0" else file.stat().st_size ) else: raise NotImplementedError() if not ext: raise ValueError("AAMP extension must not be blank if passing file as bytes.") return int(AampSizeGuesser.guess(be, ext, real_size))
def guess_bfres_size(file: Union[pathlib.Path, bytes], be: bool, name: Union[str, NoneType]) ‑> int
-
Attempts to estimate a valid RSTB value for a BFRES file. Decompresses first if yaz0 encoded.
Args
file
:Union[Path, bytes]
- The file to analyze, either as a Path or bytes.
be
:bool
- Big-Endian (True) or Little-Endian (False)
name
:Optional[str]
- The name of the BFRES file, used to detect type when there is no path.
Raises
ValueError
- Raises error if BFRES name is blank when passing file as bytes
Returns
int
- Return an estimated RSTB value
Expand source code
def guess_bfres_size(file: Union[Path, bytes], be: bool, name: Optional[str]) -> int: """Attempts to estimate a valid RSTB value for a BFRES file. Decompresses first if yaz0 encoded. Args: file (Union[Path, bytes]): The file to analyze, either as a Path or bytes. be (bool): Big-Endian (True) or Little-Endian (False) name (Optional[str]): The name of the BFRES file, used to detect type when there is no path. Raises: ValueError: Raises error if BFRES name is blank when passing file as bytes Returns: int: Return an estimated RSTB value """ if isinstance(file, bytes) or isinstance(file, memoryview) or isinstance(file, oead.Bytes): real_size = ( yaz0.get_header(file[0:16]).uncompressed_size if file[:4] == b"Yaz0" else len(file) ) elif isinstance(file, Path): name = file.name if not name else name with file.open("rb") as f: chunk = f.read(16) real_size = ( yaz0.get_header(chunk).uncompressed_size if chunk[:4] == b"Yaz0" else file.stat().st_size ) else: raise NotImplementedError() if not name: raise ValueError("BFRES name must not be blank if passing file as bytes.") return int(BfresSizeGuesser.guess(be, ".Tex" in name, real_size))
Classes
class AampSizeGuesser
-
Expand source code
class AampSizeGuesser: multiplier_map = { ".baiprog": { range(0, 380): 7.0, range(380, 400): 6.0, range(400, 450): 5.5, range(450, 600): 5.0, range(600, 1_000): 4.0, range(1_000, 1_750): 3.5, range(1_750, sys.maxsize): 3.0, }, ".bas": { range(0, 100): 20.0, range(100, 200): 12.5, range(200, 300): 10.0, range(300, 600): 8.0, range(600, 1_500): 6.0, range(1_500, 2_000): 5.5, range(2_000, 15_000): 5.0, range(15_000, sys.maxsize): 4.5, }, ".baslist": { range(0, 100): 15.0, range(100, 200): 10.0, range(200, 300): 8.0, range(300, 500): 6.0, range(500, 800): 5.0, range(800, 4_000): 4.0, range(4_000, sys.maxsize): 3.5, }, ".bdrop": { range(0, 200): 8.5, range(200, 250): 7.0, range(250, 350): 6.0, range(350, 450): 5.25, range(450, 850): 4.5, range(850, sys.maxsize): 4.0, }, ".bgparamlist": { range(0, 100): 20.0, range(100, 150): 12.0, range(150, 250): 10.0, range(250, 350): 8.0, range(350, 450): 7.0, range(450, sys.maxsize): 6.0, }, ".brecipe": { range(0, 100): 12.5, range(100, 160): 8.5, range(160, 200): 7.5, range(200, 215): 7.0, range(215, sys.maxsize): 6.5, }, ".bshop": { range(0, 200): 7.25, range(200, 400): 6.0, range(400, 500): 5.0, range(500, sys.maxsize): 4.05, }, ".bxml": { range(0, 350): 6.0, range(350, 450): 5.0, range(450, 550): 4.5, range(550, 650): 4.0, range(650, 800): 3.5, range(800, sys.maxsize): 3.0, }, } @classmethod def guess(cls, be: bool, ext: str, size: int): size *= 1.05 if ext == ".bas": size *= 1.05 # I guess? size = int(size) if ext in cls.multiplier_map: for k, v in cls.multiplier_map[ext].items(): if size in k: ret = size * v break else: raise NotImplementedError("Cannot happen ever lol") elif ext == ".bdmgparam": ret = (((-0.0018 * size) + 6.6273) * size) + 500 elif ext == ".bphysics": ret = (((int(size) + 32) & -32) + 0x4E + 0x324) * max( 4 * math.floor(size / 1388), 3 ) else: ret = 0 return int(ret * 1.5 if not be else ret)
Class variables
var multiplier_map
Static methods
def guess(be: bool, ext: str, size: int)
-
Expand source code
@classmethod def guess(cls, be: bool, ext: str, size: int): size *= 1.05 if ext == ".bas": size *= 1.05 # I guess? size = int(size) if ext in cls.multiplier_map: for k, v in cls.multiplier_map[ext].items(): if size in k: ret = size * v break else: raise NotImplementedError("Cannot happen ever lol") elif ext == ".bdmgparam": ret = (((-0.0018 * size) + 6.6273) * size) + 500 elif ext == ".bphysics": ret = (((int(size) + 32) & -32) + 0x4E + 0x324) * max( 4 * math.floor(size / 1388), 3 ) else: ret = 0 return int(ret * 1.5 if not be else ret)
class BfresSizeGuesser
-
Expand source code
class BfresSizeGuesser: multiplier_map = { True: { # Big-Endian True: { # Tex range(0, 100): 9.0, range(100, 2_000): 7.0, range(2_000, 3_000): 5.0, range(3_000, 4_000): 4.0, range(4_000, 8_500): 3.0, range(8_500, 12_000): 2.0, range(12_000, 17_000): 1.75, range(17_000, 30_000): 1.5, range(30_000, 45_000): 1.3, range(45_000, 100_000): 1.2, range(100_000, 150_000): 1.1, range(150_000, 200_000): 1.07, range(200_000, 250_000): 1.045, range(250_000, 300_000): 1.035, range(300_000, 600_000): 1.03, range(600_000, 1_000_000): 1.015, range(1_000_000, 1_800_000): 1.009, range(1_800_000, 4_500_000): 1.005, range(4_500_000, 6_000_000): 1.002, range(6_000_000, sys.maxsize): 1.0015, }, False: { # Model range(0, 500): 7.0, range(500, 750): 5.0, range(750, 1_250): 4.0, range(1_250, 2_000): 3.5, range(2_000, 400_000): 2.25, range(400_000, 600_000): 2.1, range(600_000, 1_000_000): 1.95, range(1_000_000, 1_500_000): 1.85, range(1_500_000, 3_000_000): 1.66, range(3_000_000, sys.maxsize): 1.45, }, }, False: { # Little-Endian True: { # Tex range(0, 10_000): 2.0, range(10_000, 30_000): 1.5, range(30_000, 50_000): 1.3, range(50_000, sys.maxsize): 1.2, }, False: { # Model range(0, 1_250): 9.5, range(1_250, 2_500): 6.0, range(2_500, 50_000): 4.25, range(50_000, 100_000): 3.66, range(100_000, 800_000): 3.5, range(800_000, 2_000_000): 3.15, range(2_000_000, 3_000_000): 2.5, range(3_000_000, 4_000_000): 1.667, range(4_000_000, sys.maxsize): 1.5, }, }, } @classmethod def guess(cls, be: bool, tex: bool, size: int): size = int(size * 1.05) for k, v in cls.multiplier_map[be][tex].items(): if size in k: return size * v
Class variables
var multiplier_map
Static methods
def guess(be: bool, tex: bool, size: int)
-
Expand source code
@classmethod def guess(cls, be: bool, tex: bool, size: int): size = int(size * 1.05) for k, v in cls.multiplier_map[be][tex].items(): if size in k: return size * v