Files
BT412/pack-dist.ps1
T
CydandClaude Opus 4.8 0434585566 Pack: wrap the dist zip in a single BT412/ folder
The zip previously stored files at the root (Compress-Archive -Path dist\*),
so extracting spilled loose files into the extraction directory. Build the
archive with .NET's ZipFile and prefix every entry with BT412/, so it extracts
into one self-contained BT412\ folder. Local staging dir stays dist\.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 11:06:52 -05:00

277 lines
13 KiB
PowerShell

# ============================================================================
# pack-dist.ps1 - assemble a runnable BattleTech 4.12 package into dist\
# ============================================================================
#
# Collects everything the game needs at run time:
# - build-steam\Release\btl4.exe (Steam build; Release so it runs on a
# machine WITHOUT Visual Studio -- the Debug CRT is not redistributable)
# - steam_api.dll + OpenAL32.dll beside the exe
# - game data from content\ (AUDIO, GAUGE, VIDEO, BTL4.RES, BTDPL.INI,
# the VREND manifests, bindings.txt, a few eggs) -- NOT the DOS-era
# arcade launch scripts / 16-bit stubs
# - steam_appid.txt = 480 (Spacewar) so Steam init works for TESTING; a
# real Steam release replaces this with the title's AppID
# - tools\btconsole.py for the classic (non-Steam) console-feed MP path
# - a self-documenting environ.ini + start.bat + README
#
# Usage: powershell -ExecutionPolicy Bypass -File pack-dist.ps1 [-Zip]
#
param(
[switch]$Zip
)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$dist = Join-Path $root 'dist'
$content = Join-Path $root 'content'
$relDir = Join-Path $root 'build-steam\Release'
$exe = Join-Path $relDir 'btl4.exe'
if (-not (Test-Path $exe)) {
throw "build-steam\Release\btl4.exe not found - build the Steam Release first:`n" +
" cmake -S . -B build-steam -G `"Visual Studio 17 2022`" -A Win32 -DBT412_STEAM=ON`n" +
" cmake --build build-steam --config Release --target btl4"
}
# Refuse to touch a dist the game is currently running from.
$running = Get-Process btl4 -ErrorAction SilentlyContinue |
Where-Object { $_.Path -like "$dist\*" }
if ($running) {
throw "btl4.exe is running from $dist (PID $($running.Id)) - close the game first."
}
Write-Host "Packing into $dist"
if (Test-Path $dist) { Remove-Item -Recurse -Force $dist }
New-Item -ItemType Directory -Force $dist | Out-Null
New-Item -ItemType Directory -Force "$dist\tools" | Out-Null
# --- game binary + runtime DLLs --------------------------------------------
Copy-Item $exe $dist
$pdb = Join-Path $relDir 'btl4.pdb'
if (Test-Path $pdb) { Copy-Item $pdb $dist }
# steam_api.dll + OpenAL32.dll are staged beside the exe by the build.
foreach ($dll in 'steam_api.dll', 'OpenAL32.dll') {
Copy-Item (Join-Path $relDir $dll) $dist
}
# --- game data -------------------------------------------------------------
foreach ($dir in 'AUDIO', 'GAUGE', 'VIDEO') {
Write-Host " copying $dir..."
Copy-Item -Recurse (Join-Path $content $dir) $dist
}
# Required loose files: resources, renderer/DPL config, geometry+texture
# manifests (VREND.MNG is loaded by bgfload/L4VIDEO), the input bindings, and
# a handful of eggs (DEV = the -egg dev shortcut, MP = the console-feed target).
$files = 'BTL4.RES', 'BTDPL.INI', 'bindings.txt',
'VREND.MNG', 'VRENDMON.BTL', 'VRNOSTEX.MNG',
'DEV.EGG', 'MP.EGG', 'MP1.EGG'
foreach ($file in $files) {
$src = Join-Path $content $file
if (Test-Path $src) { Copy-Item $src $dist }
else { Write-Warning "content\$file missing - skipped" }
}
# --- OpenAL fallback installer + the console feeder ------------------------
$oalinst = Join-Path $root '..\RP412\assets\RP411\oalinst.exe'
if (Test-Path $oalinst) { Copy-Item $oalinst $dist }
else { Write-Warning "oalinst.exe not found (OpenAL fallback installer) - OpenAL32.dll still ships beside the exe" }
Copy-Item (Join-Path $root 'tools\btconsole.py') "$dist\tools"
# --- steam_appid.txt (TEST ONLY) -------------------------------------------
# 480 = Valve's Spacewar test app. Steamworks (SteamAPI_Init, FakeIP, lobbies)
# works against it so multiplayer can be tested before the title has a real
# AppID. A shipping Steam build replaces this with the real AppID (and Steam
# supplies it, so the file is removed).
Set-Content -Path "$dist\steam_appid.txt" -Encoding ascii -NoNewline -Value "480"
# --- desktop configuration -------------------------------------------------
Set-Content -Path "$dist\environ.ini" -Encoding ascii -Value @"
# ============================================================================
# environ.ini - BattleTech 4.12 configuration
# ============================================================================
# One KEY=VALUE per line, read at game start. Lines starting with # or ;
# are comments; anything without an = is ignored. Delete a line (or comment
# it out) to fall back to the built-in default.
#
# Input bindings live in bindings.txt beside the exe.
# ---- Core (the shipped desktop configuration) ------------------------------
# Control stack: tokens separated by ; or , processed left to right.
# PAD the virtual RIO (XInput controller + keyboard, rebindable)
# KEYBOARD the engine keyboard handler
# RIO real serial cockpit hardware (pod only)
L4CONTROLS=PAD;KEYBOARD
# Renderer bring-up argument (presence checked; any non-empty value works).
DPLARG=1
# DPL (renderer/scene) config file, searched beside the exe.
L4DPLCFG=BTDPL.INI
# Gauge/MFD canvas (a page of GAUGE\L4GAUGE.INI). Needed for the cockpit.
L4GAUGE=640x480x16
# Single-window cockpit: the 3D viewscreen framed by the six MFD surfaces
# (green) with their on-screen RIO button banks, on a 1920x1080 canvas
# auto-scaled to your monitor. 0/unset = the old 800x600 dev view.
L4MFDSPLIT=1
# Plasma glass rendered in-window (rank/ordinal readout). Unset = none.
L4PLASMA=SCREEN
# Simulation/render frame rate (fps). Desktop default 60; the pods ran 30.
TARGETFPS=60
# 1 = Steam networking (the lobby + FakeIP mesh). Needs the Steam client
# running and steam_appid.txt beside the exe; without them the game logs the
# reason and falls back to plain TCP. 0 = TCP only (use this for the classic
# btconsole.py console-feed multiplayer path -- see the README).
BT412STEAM=1
# ---- Optional --------------------------------------------------------------
# The engine also has built-in WASD / 1-4 / M / etc. quick keys that OVERLAP the
# bindings.txt layout, so under L4CONTROLS=PAD they stand down automatically (the
# pad + keyboard bindings own the controls). Set to 1 to force them on ALONGSIDE
# the bindings (hybrid keyboard-drive); 0 to force off. Unset = auto.
#BT_DEVKEYS=1
# Mission-length override, integer seconds (short test missions). The menu's
# LENGTH choice sets this; uncomment to force it.
#BT_MISSION_SECONDS=120
# Anti-aliasing sample count passed to Direct3D 9 (0 = off, else 2..16).
#MULTISAMPLE=0
# Particle budget (default 8192).
#MAXPARTICLES=8192
# Fixed random seed for repeatable runs (any unsigned integer).
#RANDOM=12345
# ---- Classic LAN / console-feed multiplayer (BT412STEAM=0) -----------------
# The host-as-console egg feed for the STEAM lobby is not finished yet (see
# the README). The proven multiplayer path is the external console emulator
# feeding -net pods over TCP -- run tools\btconsole.py (Python 3). Example:
# Machine A: btl4.exe -egg MP.EGG -net 1501
# Machine B: btl4.exe -egg MP.EGG -net 1601
# Feeder: python tools\btconsole.py MP.EGG A_ip:1501 B_ip:1601
# Set BT412STEAM=0 above for this path.
"@
Set-Content -Path "$dist\start.bat" -Encoding ascii -Value @"
@echo off
rem BattleTech 4.12 - desktop build. No cockpit hardware needed.
rem Boots into the in-game mission setup menu (front end). With Steam
rem running (BT412STEAM=1 in environ.ini) the menu shows HOST / JOIN.
rem Add "-egg DEV.EGG" to skip the menu and run the canned solo mission.
cd /d "%~dp0"
start btl4.exe
"@
Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @"
BattleTech 4.12 (desktop / Steam build)
========================================
Run start.bat. No cockpit hardware needed. If there is no sound, run
oalinst.exe once (installs the OpenAL runtime).
You boot into the in-game MISSION SETUP MENU (green-on-black): pick the map,
mech, color, time, weather, and mission length, type a pilot name, and press
LAUNCH. The single-window cockpit comes up -- the 3D viewscreen framed by six
green MFD surfaces with the pod's RIO button banks (click them with the mouse;
they light as the game commands their lamps). At the chosen length the mission
stops and a MISSION COMPLETE scoreboard shows, then it returns to the menu.
Controls -- under L4CONTROLS=PAD (the shipped default) input comes from the
XInput controller + keyboard through bindings.txt; edit that file to rebind.
Controller left stick drive (throttle holds position; steer)
Controller triggers pedals; A / bumpers / face buttons = fire + hat
Keyboard (default profile) NumPad 8/2/4/6 = stick, 7/9 = pedals, 0 or Space =
fire, Shift/Ctrl = throttle up/down; the number and
letter rows are the MFD bank buttons and the F-keys
the Secondary columns (full layout in bindings.txt)
mouse click the on-screen cockpit buttons
Always-live desktop keys (not rebindable):
TAB toggle 1st person (cockpit) / 3rd person (chase cam)
` (backtick) hide / show the secondary displays (MFDs + radar)
V (hold) look behind (rear view; rear-mounted weapons fire)
(The engine's built-in WASD / 1-4 quick keys overlap the bindings, so they
stand down under PAD -- set BT412 env BT_DEVKEYS=1 in environ.ini to force
them on alongside.)
MULTIPLAYER - please read
-------------------------
This build is for testing STEAM multiplayer end to end.
Steam lobby + networked mission. With Steam running and BT412STEAM=1, the
menu shows HOST STEAM MISSION and JOIN STEAM MISSION. HOST creates a room;
JOIN finds and enters one. Everyone in the room sees the member list (name,
mech, color) and exchanges their FakeIP + loadout. The host presses LAUNCH
MISSION: it then acts as the in-process console -- it feeds every member the
mission over the wire, holds the whole mesh until all are staged, launches
them together, and stops the mission at the chosen length. The member pods
mesh with the host and each other; kills/deaths are collected and the same
results screen shows on every machine.
-> Test: two (or three) machines, each signed into a DIFFERENT Steam
account, all running this build. Confirm they find each other in the
room, the launch fires, everyone drops into the SAME mission and can
see/fight each other, and results show everywhere.
-> The host->member mission feed is NEWLY IMPLEMENTED. The console
protocol + launch handshake are loopback-verified here, but the full
multi-machine Steam mesh has not been lab-tested. If a member does not
drop in, grab btl4.log from each machine (grep [marshal], [frontend],
net-tx / net-rx) so it can be diagnosed.
CLASSIC MULTIPLAYER (no Steam, proven): the external console emulator feeding
-net pods over TCP. Needs Python 3.
Set BT412STEAM=0 in environ.ini, then:
Machine A: btl4.exe -egg MP.EGG -net 1501
Machine B: btl4.exe -egg MP.EGG -net 1601
Feeder: python tools\btconsole.py MP.EGG <A_ip>:1501 <B_ip>:1601
Drive, fire, cross-pod kills and respawn all replicate on this path.
Steam notes (for the lobby test):
- steam_appid.txt (480 = Valve's Spacewar test app) ships beside the exe so
Steam init works before the title has its own AppID. Keep it for testing.
- Use a DIFFERENT Steam account per machine (Steam allows one running game
per account). Disable Steam Input for app 480 if the controller misbehaves.
- If Steam is not running, the game logs the reason and stays on TCP.
Logs: the game writes btl4.log beside the exe (set BT_LOG to rename). Grep it
for [frontend], [marshal], [steam], net-tx / net-rx when reporting an issue.
Source: https://gitea.mysticmachines.com/VWE/BT412
"@
# --- summary / optional zip ------------------------------------------------
$size = (Get-ChildItem $dist -Recurse | Measure-Object Length -Sum).Sum
Write-Host ("dist ready: {0:N1} MB -> $dist" -f ($size / 1MB))
if ($Zip) {
$zipPath = Join-Path $root 'BattleTech-4.12.zip'
Write-Host "zipping to $zipPath (wrapped in a BT412\ folder)..."
if (Test-Path $zipPath) { Remove-Item -Force $zipPath }
# Every entry is prefixed BT412/ so the archive extracts into ONE folder
# (rather than spilling loose files into the extraction directory). Built
# with .NET's ZipFile so the prefix is explicit, independent of the local
# staging dir's name.
Add-Type -AssemblyName System.IO.Compression.FileSystem
$base = (Resolve-Path $dist).Path.TrimEnd('\')
$archive = [System.IO.Compression.ZipFile]::Open($zipPath, 'Create')
try {
Get-ChildItem -LiteralPath $dist -Recurse -File | ForEach-Object {
$rel = $_.FullName.Substring($base.Length + 1) -replace '\\', '/'
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
$archive, $_.FullName, "BT412/$rel",
[System.IO.Compression.CompressionLevel]::Optimal) | Out-Null
}
} finally {
$archive.Dispose()
}
Write-Host "zip ready: $zipPath (extracts to a single BT412\ folder)"
}