Skip to content

Instantly share code, notes, and snippets.

@Rainyan
Last active March 8, 2021 00:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rainyan/6b365e488a95998bf6d28ed50c49272f to your computer and use it in GitHub Desktop.
Save Rainyan/6b365e488a95998bf6d28ed50c49272f to your computer and use it in GitHub Desktop.
Generate an -addlist list for Bspzip, used to pack custom assets into a BSP file. For Bspzip details, see: https://developer.valvesoftware.com/wiki/Bspzip
#!/usr/bin/env python
"""Generate an -addlist list for Bspzip, used to pack custom
assets into a BSP file. To use, see comments/variables in
global scope below.
For Bspzip details, see: https://developer.valvesoftware.com/wiki/Bspzip"""
from enum import Enum
import os.path
import subprocess
# Name of the input BSP, without file extension
BSP_NAME_IN = 'nt_snowfall_ctg_b2'
# Name of the output BSP, without file extension.
# To overwrite the original BSP, set as BSP_NAME_IN
BSP_NAME_OUT = BSP_NAME_IN
# Path to write the generated Bspzip addlist to.
GENERATED_LIST_PATH = './bspzip_files_text.txt'
# Root path to search custom assets from.
# Leave empty if internal path == external path.
ASSETS_ROOT_PATH = 'C:/Program Files (x86)/Steam/steamapps/common/NEOTOKYO/NeotokyoSource'
# Path to search BSP maps from.
BSP_PATH = 'C:/Program Files (x86)/Steam/steamapps/common/NEOTOKYO/NeotokyoSource/maps'
# Location of the Bspzip binary file.
BSPZIP_BIN = 'C:/Program Files (x86)/Steam/steamapps/common/SourceSDK/bin/ep1/bin/bspzip.exe'
# Whether to overwrite the Bspzip addlist if it already exists.
ALWAYS_OVERWRITE_ADDLIST = True
# Whether to automatically run Bspzip after generating the addlist.
# This may overwrite your BSP according to the script settings.
RUN_BSPZIP = True
# Custom assets are defined below.
# See comments in each section for details.
# Custom materials go here.
# - Implies path: root/materials/...
# - File extensions must be explicit.
# - For clarity, use cases should be commented for each material!
CUSTOM_MATERIALS = [
# Red version of the caged light illumination decal
'CUSTOM/snowfall/decals/decal_cagedLight_red.vmt',
'CUSTOM/snowfall/decals/decal_cagedLight_red.vtf',
# Monochrome version of the caged light illumination decal
'CUSTOM/snowfall/decals/decal_warehouseLight_bw.vmt',
'CUSTOM/snowfall/decals/decal_warehouseLight_bw.vtf',
# The "human escalator..." sign
'CUSTOM/snowfall/escalator.vmt',
'CUSTOM/snowfall/escalator.vtf',
# Phys debris book model texture
'models/custom/snowfall/BookDebris.vmt',
# Tunnel model metal seam
'models/custom/snowfall/metalfloor005a_modelreuse.vmt',
# Tunnel road model
'models/custom/snowfall/snowfall_asphalt01_reuse.vmt',
# Tunnel walls model
'models/custom/snowfall/snowfall_denseconcrete1_reuse.vmt',
]
# Custom models go here.
# - Implies path: root/models/...
# - Implies file extensions for each: .dx80.vtx, .dx90.vtx, .mdl, etc.
CUSTOM_MODELS = [
'custom/snowfall/tunnel_downcurve',
'custom/snowfall/tunnel_maincurve',
'custom/snowfall/tunnel_seam',
'custom/snowfall/tunnelroad_downcurve',
'custom/snowfall/tunnelroad_maincurve',
'custom/snowfall/book_debris',
]
# Custom sounds go here.
# - Implies path: root/sound/...
CUSTOM_SOUNDS = [
'CUSTOM/snowfall/snowfall_flicker_fx2.wav',
]
# Custom scripts go here.
# - Implies path: root/scripts/...
CUSTOM_SCRIPTS = [
'soundscapes_nt_snowfall_ctg_b2.txt',
]
# TODO/FIXME Unimplemented, do not use!
#CUSTOM_OTHER = []
AssetType = Enum('AssetType', 'Materials Models Sounds Scripts Other')
# Combine all Lists into a Dict to process.
CUSTOM_ASSETS = {
AssetType.Materials: CUSTOM_MATERIALS,
AssetType.Models: CUSTOM_MODELS,
AssetType.Sounds: CUSTOM_SOUNDS,
AssetType.Scripts: CUSTOM_SCRIPTS,
#AssetType.Other: CUSTOM_OTHER, #TODO/FIXME Unimplemented, do not use!
}
class AssetList(list):
def append(self, item):
"""Append according to the Bspzip addlist formatting rules.
See: https://developer.valvesoftware.com/wiki/Bspzip"""
# Internal path
super().append(item + '\n')
# External path
externalPath = ASSETS_ROOT_PATH + '/' + item
if not os.path.isfile(externalPath):
# Bspzip will silently produce incorrect results if this doesn't exist.
raise IOError('External include file must exist: "' +
externalPath + '"')
super().append(externalPath + '\n')
def run_bspzip(binary, addlist, inbsp, outbsp):
"""Run Bspzip with -addlist and related syntax."""
insize = os.path.getsize(inbsp)
args = [binary, '-addlist', inbsp, addlist, outbsp]
subprocess.run(args)
# Bspzip can silently fail on incorrect syntax, so we check.
if insize == os.path.getsize(outbsp):
raise RuntimeError('Output BSP size equals input BSP size; ' +\
'this means Bspzip processing has most likely failed! ' +\
'Make sure the addlist, and all asset and bsp paths are valid.')
def write_to_file(linesToWrite, outFile, overwriteIfExists = True):
"""Take an iteratable and write it to file as lines."""
if os.path.isfile(outFile):
if overwriteIfExists:
print('File "' + outFile + '" exists, overwriting...')
else:
print('File "' + outFile + '" exists, stopping.')
return
f = open(outFile, 'w')
f.writelines(linesToWrite)
f.close()
print('Wrote ' + str(os.path.getsize(outFile)) + ' bytes to "' + outFile + '"')
def get_asset_paths_list(assetDict):
"""Input: Dict of "AssetType.Type enum -> asset string list".
Processing will differ based on AssetType enum type.
Output: AssetList of lines to write for the Bspzip addlist file."""
model_extensions = ['dx80.vtx', 'dx90.vtx', 'mdl', 'phy',
'sw.vtx', 'vvd', 'xbox.vtx']
# This is essentially List with an overridden append method.
al = AssetList()
for k, v in assetDict.items():
if k == AssetType.Models:
for model in v:
for ext in model_extensions:
al.append('models/' + model + '.' + ext)
elif k == AssetType.Materials:
for material in v:
al.append('materials/' + material)
elif k == AssetType.Sounds:
for sound in v:
al.append('sound/' + sound)
elif k == AssetType.Scripts:
for script in v:
al.append('scripts/' + script)
elif k == AssetType.Other:
raise ValueError('NOT IMPLEMENTED!!!') # TODO/FIXME
else:
raise ValueError('Unknown AssetType enum: ' + k)
return al
write_to_file(get_asset_paths_list(CUSTOM_ASSETS),
GENERATED_LIST_PATH,
ALWAYS_OVERWRITE_ADDLIST)
if RUN_BSPZIP:
run_bspzip(BSPZIP_BIN, GENERATED_LIST_PATH,
BSP_PATH + '/' + BSP_NAME_IN + '.bsp',
BSP_PATH + '/' + BSP_NAME_OUT + '.bsp')
print('Script finished.')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment