Last active
March 8, 2021 00:02
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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