Skip to content

Commit

Permalink
refactor branch lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
snake-biscuits committed Jul 16, 2023
1 parent d6274af commit 23374a9
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 81 deletions.
36 changes: 18 additions & 18 deletions bsp_tool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@
from .valve import GoldSrcBsp, ValveBsp


BspVariant_from_file_magic = {b"2015": RitualBsp,
b"2PSB": ReMakeQuakeBsp,
b"BSP2": ReMakeQuakeBsp,
b"EALA": RitualBsp,
b"EF2!": RitualBsp,
b"FAKK": RitualBsp,
b"FBSP": FusionBsp,
b"IBSP": IdTechBsp, # + InfinityWardBsp + D3DBsp
b"PSBr": RespawnBsp, # Xbox360
b"PSBV": ValveBsp, # Xbox360
b"rBSP": RespawnBsp,
b"RBSP": RavenBsp,
b"VBSP": ValveBsp}
BspVariant_for_magic = {b"2015": RitualBsp,
b"2PSB": ReMakeQuakeBsp,
b"BSP2": ReMakeQuakeBsp,
b"EALA": RitualBsp,
b"EF2!": RitualBsp,
b"FAKK": RitualBsp,
b"FBSP": FusionBsp,
b"IBSP": IdTechBsp, # OR InfinityWardBsp OR D3DBsp
b"PSBr": RespawnBsp, # Xbox360
b"PSBV": ValveBsp, # Xbox360
b"rBSP": RespawnBsp,
b"RBSP": RavenBsp,
b"VBSP": ValveBsp}
# NOTE: if no file_magic is present:
# - QuakeBsp
# - GoldSrcBsp
Expand Down Expand Up @@ -76,7 +76,7 @@ def load_bsp(filename: str, branch_script: ModuleType = None) -> base.Bsp:
else:
BspVariant = InfinityWardBsp
elif filename.lower().endswith(".bsp"):
if file_magic not in BspVariant_from_file_magic: # Quake / GoldSrc
if file_magic not in BspVariant_for_magic: # Quake / GoldSrc
version = int.from_bytes(file_magic, "little")
if version in Quake_versions:
BspVariant = QuakeBsp
Expand All @@ -91,14 +91,14 @@ def load_bsp(filename: str, branch_script: ModuleType = None) -> base.Bsp:
if file_magic == b"IBSP" and version in InfinityWard_versions:
BspVariant = InfinityWardBsp
else:
BspVariant = BspVariant_from_file_magic[file_magic]
BspVariant = BspVariant_for_magic[file_magic]
else: # invalid extension
raise RuntimeError(f"{filename} is not a .bsp file!")
# TODO: ata4's bspsrc uses unique entity classnames to identify branches
# -- need this for identifying variants with overlapping identifiers
# identify branch script
if branch_script is None:
branch_script = branches.script_from_file_magic_and_version[(file_magic, version)]
branch_script = branches.identify[(file_magic, version)]
# TODO: ata4's bspsrc uses unique entity classnames to identify branches
# -- need this for identifying variants with overlapping identifiers
return BspVariant(branch_script, filename, autoload=True) # might raise errors


Expand Down
128 changes: 67 additions & 61 deletions bsp_tool/branches/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Index of all known .bsp format variants"""
__all__ = ["ace_team", "arkane", "gearbox", "id_software", "infinity_ward", "ion_storm", "loiste",
"nexon", "outerlight", "raven", "respawn", "ritual", "strata", "troika", "utoplanet",
"valve", "scripts_from_file_magic", "script_from_file_magic_and_version", "game_name_table"]
"nexon", "outerlight", "raven", "respawn", "ritual", "strata", "troika", "utoplanet", "valve",
"developers", "with_magic", "identify", "game_branch", "quake_based", "source_based"]

from . import ace_team
from . import arkane
Expand All @@ -25,77 +25,83 @@
# -- https://steamdb.info/depot/38431/ lists radiant & compilers in files


# NOTE: this dict can be generated from branch_scripts, but listing it here is more convenient
scripts_from_file_magic = {None: [id_software.quake,
*gearbox.scripts,
raven.hexen2,
valve.goldsrc],
b"2015": [ritual.mohaa, ritual.mohaa_demo],
b"2PSB": [id_software.remake_quake_old],
b"BSP2": [id_software.remake_quake],
b"EF2!": [ritual.star_trek_elite_force2],
b"EALA": [ritual.mohaa_bt],
b"FAKK": [ritual.fakk2,
ritual.star_trek_elite_force2_demo],
b"FBSP": [id_software.qfusion],
b"IBSP": [id_software.quake2,
id_software.quake3,
*infinity_ward.scripts,
ion_storm.daikatana,
raven.soldier_of_fortune,
ritual.sin], # v41
b"PSBr": [respawn.titanfall_x360],
b"PSBV": [valve.orange_box_x360,
valve.sdk_2013_x360],
b"rBSP": [respawn.apex_legends,
respawn.apex_legends13,
respawn.titanfall,
respawn.titanfall2],
b"RBSP": [raven.soldier_of_fortune2,
ritual.sin],
b"VBSP": [ace_team.zeno_clash,
*arkane.scripts,
strata.strata,
loiste.infra,
*nexon.scripts,
outerlight.outerlight,
troika.vampire,
utoplanet.merubasu,
valve.alien_swarm,
valve.left4dead,
valve.left4dead2,
valve.orange_box,
valve.sdk_2013,
valve.source,
valve.source_filmmaker]}
developers = (ace_team, arkane, gearbox, id_software, infinity_ward, ion_storm, loiste,
nexon, outerlight, raven, respawn, ritual, strata, troika, utoplanet, valve)

# NOTE: we could generate this list, but it makes for nice documentation
with_magic = {None: [id_software.quake, *gearbox.scripts, raven.hexen2, valve.goldsrc],
b"2015": [ritual.mohaa, ritual.mohaa_demo],
b"2PSB": [id_software.remake_quake_old],
b"BSP2": [id_software.remake_quake],
b"EF2!": [ritual.star_trek_elite_force2],
b"EALA": [ritual.mohaa_bt],
b"FAKK": [ritual.fakk2, ritual.star_trek_elite_force2_demo],
b"FBSP": [id_software.qfusion],
b"IBSP": [id_software.quake2, id_software.quake3,
*infinity_ward.scripts, ion_storm.daikatana,
raven.soldier_of_fortune, ritual.sin],
b"PSBr": [respawn.titanfall_x360],
b"PSBV": [valve.orange_box_x360,
valve.sdk_2013_x360],
b"rBSP": [respawn.apex_legends,
respawn.apex_legends13,
respawn.titanfall,
respawn.titanfall2],
b"RBSP": [raven.soldier_of_fortune2,
ritual.sin],
b"VBSP": [ace_team.zeno_clash,
*arkane.scripts,
strata.strata,
loiste.infra,
*nexon.scripts,
outerlight.outerlight,
troika.vampire,
utoplanet.merubasu,
valve.alien_swarm,
valve.left4dead,
valve.left4dead2,
valve.orange_box,
valve.sdk_2013,
valve.source,
valve.source_filmmaker]}

script_from_file_magic_and_version = dict()
# TODO: with_magic_version defaultdict(set)
# ^ {(file_magic, version): {branch_script}}

identify = dict()
# ^ {(file_magic, version): branch_script}
for file_magic, branch_scripts in scripts_from_file_magic.items():
for file_magic, branch_scripts in with_magic.items():
for branch_script in branch_scripts:
script_from_file_magic_and_version[(file_magic, branch_script.BSP_VERSION)] = branch_script
identify[(file_magic, branch_script.BSP_VERSION)] = branch_script
for version in branch_script.GAME_VERSIONS.values():
script_from_file_magic_and_version[(file_magic, version)] = branch_script
identify[(file_magic, version)] = branch_script

# FORCED DEFAULTS:
script_from_file_magic_and_version[(None, 29)] = id_software.quake
# edge case: (only found in 1 map, unsure how to list in branch script)
identify[(b"IBSP", 41)] = ritual.sin
# default branches:
identify[(None, 29)] = id_software.quake
# ^ NOT raven.hexen2
script_from_file_magic_and_version[(b"IBSP", 46)] = id_software.quake3
identify[(b"IBSP", 46)] = id_software.quake3
# ^ NOT raven.soldier_of_fortune
script_from_file_magic_and_version[(b"VBSP", 20)] = valve.orange_box
# ^ NOT nexon.vindictus OR nexon.vindictus69 OR outerlight.outerlight OR utoplanet.merubasu OR valve.left4dead
script_from_file_magic_and_version[(b"VBSP", 21)] = valve.sdk_2013
identify[(b"VBSP", 20)] = valve.orange_box
# ^ NOT nexon.vindictus OR nexon.vindictus69 OR outerlight.outerlight
# -- OR utoplanet.merubasu OR valve.left4dead
identify[(b"VBSP", 21)] = valve.sdk_2013
# ^ NOT valve.alien_swarm OR valve.left4dead2 OR valve.source_filmmaker
script_from_file_magic_and_version[(b"VBSP", 100)] = nexon.cso2
identify[(b"VBSP", 100)] = nexon.cso2
# ^ NOT nexon.cso2_2018
script_from_file_magic_and_version[(b"RBSP", 1)] = raven.soldier_of_fortune2
identify[(b"RBSP", 1)] = raven.soldier_of_fortune2
# ^ NOT ritual.sin


game_name_table = dict()
game_branch = dict()
# ^ {"game": (script, version)}
for developer in (arkane, gearbox, id_software, infinity_ward, nexon, raven, respawn, ritual, valve):
for developer in developers:
for script in developer.scripts:
for game_name in script.GAME_VERSIONS:
game_name_table[game_name] = (script, script.GAME_VERSIONS[game_name])
game_branch[game_name] = (script, script.GAME_VERSIONS[game_name])

source_magics = (b"PSBV", b"PSBr", b"VBSP", b"rBSP")
source_based = [bs for magic, bss in with_magic.items() for bs in bss if magic in source_magics]
# NOTE: all source_based branches have "version" in LumpHeader
quake_based = [bs for magic, bss in with_magic.items() for bs in bss if magic not in source_magics]
# NOTE: all quake_based lumps are unversioned
3 changes: 1 addition & 2 deletions tests/branches/test_LumpClasses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""General testing of LumpClasses (consistency & forgotten fields)"""
import inspect
import itertools
import struct

import pytest
Expand Down Expand Up @@ -32,7 +31,7 @@
MappedArray_LumpClasses = dict()
BitField_LumpClasses = dict()
# ^^^ {"dev.game.LumpClass": LumpClass}
for branch_script in itertools.chain(*branches.scripts_from_file_magic.values()):
for branch_script in (*branches.quake_based, *branches.source_based):
script_name = ".".join(branch_script.__name__.split(".")[-2:])
for class_name, LumpClass in inspect.getmembers(branch_script, inspect.isclass):
if issubclass(LumpClass, base.Struct):
Expand Down

0 comments on commit 23374a9

Please sign in to comment.