Skip to content

Commit

Permalink
adding support for Quake 64
Browse files Browse the repository at this point in the history
  • Loading branch information
snake-biscuits committed Jul 19, 2023
1 parent ee9946f commit 0cd595a
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 38 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Full documentation: [snake-biscuits.github.io/bsp_tool/](https://snake-biscuits.
- [Quake III: Team Arena](https://github.com/snake-biscuits/bsp_tool/tree/master/bsp_tool/branches/id_software/quake3.py)
- Quake Champions :o:
- [Quake Live](https://github.com/snake-biscuits/bsp_tool/tree/master/bsp_tool/branches/id_software/quake3.py)
- [Quake 64 (PC 2021)](https://github.com/snake-biscuits/bsp_tool/tree/master/bsp_tool/branches/id_software/quake64.py)
* [Infinity Ward](https://github.com/snake-biscuits/bsp_tool/tree/master/bsp_tool/branches/infinity_ward)
- [Call of Duty](https://github.com/snake-biscuits/bsp_tool/tree/master/bsp_tool/branches/infinity_ward/call_of_duty1.py)
- [Call of Duty 2](https://github.com/snake-biscuits/bsp_tool/tree/master/bsp_tool/branches/infinity_ward/call_of_duty2.py)
Expand Down
13 changes: 6 additions & 7 deletions bsp_tool/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
"""A library for .bsp file analysis & modification"""
__all__ = ["base", "branches", "load_bsp", "lumps", "D3DBsp", "FusionBsp",
"GoldSrcBsp", "IdTechBsp", "InfinityWardBsp", "QuakeBsp", "RavenBsp",
"ReMakeQuakeBsp", "RespawnBsp", "RitualBsp", "ValveBsp"]
"GoldSrcBsp", "IdTechBsp", "InfinityWardBsp", "QuakeBsp", "Quake64Bsp",
"RavenBsp", "ReMakeQuakeBsp", "RespawnBsp", "RitualBsp", "ValveBsp"]

import os
from types import ModuleType

from . import base # base.Bsp base class
from . import branches # all known .bsp variant definitions
from . import lumps
from .id_software import FusionBsp, IdTechBsp, QuakeBsp, ReMakeQuakeBsp
from .id_software import FusionBsp, IdTechBsp, QuakeBsp, Quake64Bsp, ReMakeQuakeBsp
from .infinity_ward import D3DBsp, InfinityWardBsp
from .raven import RavenBsp
from .respawn import RespawnBsp
from .ritual import RitualBsp
from .valve import GoldSrcBsp, ValveBsp


BspVariant_for_magic = {b"2015": RitualBsp,
BspVariant_for_magic = {b" 46Q": Quake64Bsp,
b"2015": RitualBsp,
b"2PSB": ReMakeQuakeBsp,
b"BSP2": ReMakeQuakeBsp,
b"EALA": RitualBsp,
Expand Down Expand Up @@ -56,10 +57,8 @@ def load_bsp(filename: str, branch_script: ModuleType = None) -> base.Bsp:
# parse header
with open(filename, "rb") as bsp_file:
file_magic = bsp_file.read(4)
if file_magic in (b"2PSB", b"BSP2"):
if file_magic in (b" 46Q", b"2PSB", b"BSP2"):
version = None
# elif file_magic == b"BSP2":
# return ReMakeQuakeBsp(branches.id_software.remake_quake, filename)
# endianness
elif file_magic in (b"PSBr", b"PSBV"): # big endian
version = int.from_bytes(bsp_file.read(4), "big")
Expand Down
1 change: 1 addition & 0 deletions bsp_tool/branches/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

# 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" 46Q": [id_software.quake64],
b"2015": [ritual.mohaa, ritual.mohaa_demo],
b"2PSB": [id_software.remake_quake_old],
b"BSP2": [id_software.remake_quake],
Expand Down
3 changes: 2 additions & 1 deletion bsp_tool/branches/id_software/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from . import quake
from . import quake2
from . import quake3
from . import quake64 # technically by Midway / NightDive, but only available via the re-release
from . import remake_quake # ReMakeQuake is an abandoned mod that created the BSP2 / BSP29a format
from . import remake_quake_old # deprecated, but still supported


scripts = [qfusion, quake, quake2, quake3, remake_quake, remake_quake_old]
scripts = [qfusion, quake, quake2, quake3, quake64, remake_quake, remake_quake_old]
55 changes: 28 additions & 27 deletions bsp_tool/branches/id_software/quake.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,37 @@ class Vertex(base.MappedArray, vector.vec3): # LUMP 3


# special lump classes, in alphabetical order:
class MipTexture(base.Struct): # LUMP 2
# http://www.cs.hut.fi/~andy/T-126.101/2004/texture_prefix.html
# http://www.slackiller.com/tommy14/hltexture.htm
name: str # texture name
# NOTE: may contain multiple values
# -- e.g. b"+buttontex\0\x03\0..."
# leading "*": scroll like water / lava
# leading "+": animate frame-by-frame (first frame must be 0-9)
# leading "sky": scroll like sky (sky textures have 2 parts)
# leading "{": transparent via blue chroma key (palette)
# added in Half-Life:
# leading "!": water, no entity required
# leading "~": emit light
# leading "+a": animated toggle; cycle through up to 10 frames
# leading "-": random tiling; (set of 3 or 4)
size: vector.vec2 # width & height
offsets: List[int] # offset from entry start to texture
__slots__ = ["name", "size", "offsets"]
_format = "16s6I"
_arrays = {"size": ["width", "height"],
"offsets": ["full", "half", "quarter", "eighth"]}
_classes = {"size": vector.renamed_vec2("width", "height")}


class MipTextureLump(list): # LUMP 2
"""see github.com/id-Software/Quake-2/blob/master/ref_soft/r_image.c"""
# packed texture data, kind of like a WAD
# goldsrc just uses this lump to list texture names & mounts an external WAD
MipTextureClass: base.Struct = MipTexture

def __init__(self, iterable: List[(MipTexture, List[bytes])] = tuple()):
def __init__(self, iterable: List[(base.Struct, List[bytes])] = tuple()):
super().__init__(iterable)

def as_bytes(self):
Expand All @@ -274,7 +299,7 @@ def as_bytes(self):
miptex_offset = 4 + len(self) * 4
for miptex, mips in self:
miptex_offsets.append(miptex_offset)
mip_offset = struct.calcsize(MipTexture._format)
mip_offset = struct.calcsize(self.MipTextureClass._format)
for i, mip in enumerate(mips):
miptex.offsets[i] = mip_offset
mip_offset += len(mip)
Expand All @@ -295,7 +320,7 @@ def from_bytes(cls, raw_lump: bytes):
out.append((None, [b""] * 4)) # there is no mip
continue
_buffer.seek(offset)
miptex = MipTexture.from_stream(_buffer)
miptex = cls.MipTextureClass.from_stream(_buffer)
mips = list()
for j, mip_offset in enumerate(miptex.offsets):
if mip_offset == 0: # Half-Life/valve/maps/gasworks.bsp has no embedded mips
Expand All @@ -317,30 +342,6 @@ def from_bytes(cls, raw_lump: bytes):
return cls(out)


class MipTexture(base.Struct): # LUMP 2
# http://www.cs.hut.fi/~andy/T-126.101/2004/texture_prefix.html
# http://www.slackiller.com/tommy14/hltexture.htm
name: str # texture name
# NOTE: may contain multiple values
# -- e.g. b"+buttontex\0\x03\0..."
# leading "*": scroll like water / lava
# leading "+": animate frame-by-frame (first frame must be 0-9)
# leading "sky": scroll like sky (sky textures have 2 parts)
# leading "{": transparent via blue chroma key (palette)
# added in Half-Life:
# leading "!": water, no entity required
# leading "~": emit light
# leading "+a": animated toggle; cycle through up to 10 frames
# leading "-": random tiling; (set of 3 or 4)
size: vector.vec2 # width & height
offsets: List[int] # offset from entry start to texture
__slots__ = ["name", "size", "offsets"]
_format = "16s6I"
_arrays = {"size": ["width", "height"],
"offsets": ["full", "half", "quarter", "eighth"]}
_classes = {"size": vector.renamed_vec2("width", "height")}


# TODO: Visibility
# -- decode RLE
# -- Leaf.vislist/cluster is -1 or byte index into compressed array
Expand Down
70 changes: 70 additions & 0 deletions bsp_tool/branches/id_software/quake64.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""QuakeCon 2021 PC Re-release of Midway's Nintendo 64 port of Quake"""
# Installation: Steam > Quake (Re-release) > Addons > Quake 64
# https://github.com/sezero/quakespasm/blame/master/Quake/bspfile.h
import enum
from typing import List

from .. import base
from .. import vector
from . import quake


FILE_MAGIC = b" 46Q"

BSP_VERSION = None

GAME_PATHS = {"Quake 64": "C://Users/%USERPROFILE%/Saved Games/Nightdive Studios/q64"}

GAME_VERSIONS = {GAME_NAME: BSP_VERSION for GAME_NAME in GAME_PATHS}


class LUMP(enum.Enum):
ENTITIES = 0
PLANES = 1
MIP_TEXTURES = 2
VERTICES = 3
VISIBILITY = 4
NODES = 5
TEXTURE_INFO = 6
FACES = 7
LIGHTING = 8 # 8bpp 0x00-0xFF black-white
CLIP_NODES = 9
LEAVES = 10
LEAF_FACES = 11
EDGES = 12
SURFEDGES = 13
MODELS = 14


class LumpHeader(base.MappedArray):
_mapping = ["offset", "length"]
_format = "2I"


# special lump classes, in alphabetical order:
class MipTexture(base.Struct): # LUMP 2
name: str # texture name
size: vector.vec2 # width & height
shift: int # ...
offsets: List[int] # offset from entry start to texture
__slots__ = ["name", "size", "shift", "offsets"]
_format = "16s7I"
_arrays = {"size": ["width", "height"],
"offsets": ["full", "half", "quarter", "eighth"]}
_classes = {"size": vector.renamed_vec2("width", "height")}


class MipTextureLump(quake.MipTextureLump): # LUMP 2
MipTextureClass = MipTexture


# {"LUMP": LumpClass}
BASIC_LUMP_CLASSES = quake.BASIC_LUMP_CLASSES.copy()

LUMP_CLASSES = quake.LUMP_CLASSES.copy()

SPECIAL_LUMP_CLASSES = quake.SPECIAL_LUMP_CLASSES.copy()
SPECIAL_LUMP_CLASSES.update({"MIP_TEXTURES": MipTextureLump})


methods = [*quake.methods]
9 changes: 8 additions & 1 deletion bsp_tool/id_software.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _preload(self):
# collect metadata
file_magic = self.file.read(4)
if file_magic != self.file_magic and file_magic != bytes(reversed(self.file_magic)):
raise RuntimeError(f"{self.file} is not a RemakeQuakeBsp! file_magic is incorrect")
raise RuntimeError(f"{self.file} is not a {self.__class__.__name__}! file_magic is incorrect")
self.file_magic = file_magic
self.file.seek(0, 2) # move cursor to end of file
self.bsp_file_size = self.file.tell()
Expand All @@ -82,6 +82,13 @@ def _preload(self):
self._get_signature(4 + (8 * len(self.branch.LUMP)))


class Quake64Bsp(ReMakeQuakeBsp):
"""QuakeCon 2021 PC Re-release of Midway's Nintendo 64 Port"""
# https://github.com/sezero/quakespasm/blob/master/Quake/bspfile.h
# https://github.com/sezero/quakespasm/blob/master/Quake/gl_model.c
file_magic = b" 46Q"


class IdTechBsp(base.Bsp):
file_magic = b"IBSP"
# https://www.mralligator.com/q3/
Expand Down
7 changes: 5 additions & 2 deletions docs/generate/supported_games.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
sys.path.insert(0, "../../")
from bsp_tool import branches # noqa: E402
from bsp_tool import D3DBsp, FusionBsp, GoldSrcBsp, IdTechBsp, InfinityWardBsp # noqa: E402
from bsp_tool import RavenBsp, ReMakeQuakeBsp, RespawnBsp, RitualBsp, ValveBsp, QuakeBsp # noqa: E402
from bsp_tool import RavenBsp, ReMakeQuakeBsp, RespawnBsp, RitualBsp, ValveBsp # noqa: E402
from bsp_tool import QuakeBsp, Quake64Bsp # noqa: E402
from bsp_tool.extensions import lightmaps # noqa: E402
from bsp_tool.lumps import DarkMessiahSPGameLump, GameLump # noqa: E402

Expand Down Expand Up @@ -63,6 +64,7 @@
# | InfinityWardBsp | infinity_ware.call_of_duty2 | Y |
# | QuakeBsp | id_software.quake | Y |
# | QuakeBsp | raven.hexen2 | Y |
# | Quake64Bsp | id_software.quake64 | Y |
# | RavenBsp | raven.soldier_of_fortune2 | Y |
# | RavenBsp | ritual.sin | N | # investigate
# | ReMakeQuakeBsp | id_software.remake_quake | Y |
Expand Down Expand Up @@ -137,10 +139,11 @@
ScriptGroup("Left 4 Dead Series", "left4dead.md", "Valve & Turtle Rock Studios", "left4dead.md",
{ValveBsp: [branches.valve.left4dead,
branches.valve.left4dead2]}),
# TODO: present BSP2 & 2PSB (FILE_MAGIC only; no BSP_VERSION) better
# TODO: present BSP2 / 2PSB / Q64 (FILE_MAGIC only; no BSP_VERSION) better
ScriptGroup("Quake Engine", "quake.md", "Id Software", None,
{QuakeBsp: [branches.id_software.quake,
branches.raven.hexen2],
Quake64Bsp: [branches.id_software.quake64],
ReMakeQuakeBsp: [branches.id_software.remake_quake,
branches.id_software.remake_quake_old]}),
ScriptGroup("Quake II Engine", "quake2.md", "Id Software, Ion Storm", None,
Expand Down
3 changes: 3 additions & 0 deletions tests/maplist.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
"hipnotic/pak0/maps", # 18 maps | 30 MB
"mg1/pak0/maps", # 20 maps | 240 MB | .pak
"rogue/pak0/maps"], # 23 maps | 28 MB | .pak
# QuakeCon 2021 PC re-release of Midway's Nintendo 64 port
# Download in-game (Quake Re-release) via addons; installs into %USERPROFILE%
"Quake64": ["q64/pak0/maps"], # 32 maps | 21 MB | .pak
# http://quake.great-site.net/
# ReMakeQuakeBsp
"Alkaline": ["alkaline/pak0/maps", # 23 maps | 132 MB | .pak
Expand Down

0 comments on commit 0cd595a

Please sign in to comment.