diff --git a/bsp_tool/__init__.py b/bsp_tool/__init__.py index 359d4ec6..f983a188 100644 --- a/bsp_tool/__init__.py +++ b/bsp_tool/__init__.py @@ -1,6 +1,6 @@ """A library for .bsp file analysis & modification""" __all__ = ["base", "branches", "load_bsp", "lumps", "tools", - "ArkaneBsp", "GoldSrcBsp", "IdTechBsp", "InfinityWardBsp", + "ArkaneBsp", "D3DBsp", "GoldSrcBsp", "IdTechBsp", "InfinityWardBsp", "QuakeBsp", "RavenBsp", "RespawnBsp", "RitualBsp", "ValveBsp"] import os @@ -21,19 +21,22 @@ BspVariant_from_file_magic = {b"2015": RitualBsp, b"EF2!": RitualBsp, b"FAKK": RitualBsp, - b"IBSP": IdTechBsp, # or InfinityWardBsp + b"IBSP": IdTechBsp, # + InfinityWardBsp + D3DBsp b"rBSP": RespawnBsp, b"RBSP": RavenBsp, - b"VBSP": ValveBsp} # and ArkaneBsp -# NOTE: if no file_magic is present, options are: -# - GoldSrcBsp + b"VBSP": ValveBsp} # + ArkaneBsp +# NOTE: if no file_magic is present: # - QuakeBsp +# - GoldSrcBsp # - 256-bit XOR encoded Tactical Intervention .bsp +# detect GoldSrcBsp GoldSrc_versions = {*branches.valve.goldsrc.GAME_VERSIONS.values(), *branches.gearbox.blue_shift.GAME_VERSIONS.values(), *branches.gearbox.nightfire.GAME_VERSIONS.values()} +# detect InfinityWardBsp / D3DBsp InfinityWard_versions = {v for s in branches.infinity_ward.scripts for v in s.GAME_VERSIONS.values()} +# detect QuakeBsp Quake_versions = {*branches.id_software.quake.GAME_VERSIONS.values()} diff --git a/bsp_tool/branches/infinity_ward/call_of_duty4.py b/bsp_tool/branches/infinity_ward/call_of_duty4.py index 03ac1170..f79175cc 100644 --- a/bsp_tool/branches/infinity_ward/call_of_duty4.py +++ b/bsp_tool/branches/infinity_ward/call_of_duty4.py @@ -35,7 +35,7 @@ class LUMP(enum.Enum): NODES = 0x1B LEAVES = 0x1C LEAF_BRUSHES = 0x1D - LEAF_SURFACES = 0x1E # unused? + LEAF_SURFACES = 0x1E COLLISION_VERTICES = 0x1F COLLISION_TRIANGLES = 0x20 COLLISION_EDGE_WALK = 0x21 @@ -91,7 +91,7 @@ class LUMP(enum.Enum): LUMP_CLASSES = {} -SPECIAL_LUMP_CLASSES = {} # "ENTITIES": shared.Entities +SPECIAL_LUMP_CLASSES = {"ENTITIES": shared.Entities} methods = [shared.worldspawn_volume] diff --git a/bsp_tool/infinity_ward.py b/bsp_tool/infinity_ward.py index 8341885c..78ff190e 100644 --- a/bsp_tool/infinity_ward.py +++ b/bsp_tool/infinity_ward.py @@ -83,8 +83,9 @@ def _read_header(self, LUMP: enum.Enum) -> LumpHeader: return header -CoD4LumpHeader = collections.namedtuple("LumpHeader", ["id", "length", "offset"]) +CoD4LumpHeader = collections.namedtuple("LumpHeader", ["id", "length", "offset", "name"]) # NOTE: offset is calculated from the sum of preceding lump's lengths (+ padding) +# NOTE: name is calculated from id, just for human-readability class D3DBsp(base.Bsp): @@ -124,24 +125,24 @@ def is_related(f): return f.startswith(os.path.splitext(self.filename)[0]) self.lump_count = int.from_bytes(self.file.read(4), "little") self.file.seek(0, 2) # move cursor to end of file self.bsp_file_size = self.file.tell() - + # load headers & lumps self.headers = list() # order matters self.loading_errors: Dict[str, Exception] = dict() - cursor, offset = 0, 0 + cursor = 12 + (self.lump_count * 8) # end of headers; for "reading" lumps for i in range(self.lump_count): # read header self.file.seek(12 + 8 * i) _id, length = struct.unpack("2i", self.file.read(8)) assert length != 0, "cursed, idk how you got this error" - offset = cursor + (4 - cursor % 4) if cursor % 4 != 0 else cursor + if _id != 0x07: # UNKNOWN_7 is padded to every 2nd byte? + cursor = cursor + (4 - cursor & 3) + offset = cursor + # NOTE: could be wrong cursor += length - lump_header = CoD4LumpHeader(_id, length, offset) - # NOTE: offset finding could be very incorrect - self.headers.append(lump_header) - # identify lump - LUMP_enum = self.branch.LUMP(lump_header.id) + LUMP_enum = self.branch.LUMP(_id) LUMP_NAME = LUMP_enum.name - # NOTE: very new to this format, may be collecting the wrong data + lump_header = CoD4LumpHeader(_id, length, offset, LUMP_NAME) + self.headers.append(lump_header) try: if LUMP_NAME in self.branch.LUMP_CLASSES: LumpClass = self.branch.LUMP_CLASSES[LUMP_NAME] @@ -164,6 +165,12 @@ def is_related(f): return f.startswith(os.path.splitext(self.filename)[0]) def _read_header(self, LUMP: enum.Enum) -> CoD4LumpHeader: raise NotImplementedError("CoD4LumpHeaders aren't ordered") + def print_headers(self): + print("LUMP_NAME", " " * 14, "OFFSET", "LENGTH") + print("-" * 38) + for header in self.headers: + print(f"{header.name:<24} {header.offset:06X} {header.length:06X}") + # NOTE: XenonBsp also exists (named after the XBox360 processor) # -- however we aren't supporting console *.bsp