From 09b4eff32631f779ae215f71c24efc41a16334df Mon Sep 17 00:00:00 2001
From: Joe Rayhawk <jrayhawk@fairlystable.org>
Date: Sat, 11 Jul 2026 03:32:48 +0000
Subject: [PATCH] feat: add remove_unused_wads CLI command

Co-authored-by: aider (openrouter/anthropic/claude-opus-4.8) <aider@aider.chat>
---
 src/cli/CommandLine.cpp | 12 ++++++
 src/cli/CommandLine.h   |  1 +
 src/main.cpp            | 86 ++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 98 insertions(+), 1 deletion(-)

diff --git a/src/cli/CommandLine.cpp b/src/cli/CommandLine.cpp
index 17ad41a..e98355a 100644
--- a/src/cli/CommandLine.cpp
+++ b/src/cli/CommandLine.cpp
@@ -64,6 +64,18 @@ string CommandLine::getOption(string optionName) {
 	return optionVals[optionName];
 }
 
+vector<string> CommandLine::getOptionMulti(string optionName) {
+	vector<string> vals;
+
+	for (int i = 0; i + 1 < options.size(); i++) {
+		if (toLowerCase(options[i]) == optionName) {
+			vals.push_back(options[i + 1]);
+		}
+	}
+
+	return vals;
+}
+
 int CommandLine::getOptionInt(string optionName) {
 	return atoi( optionVals[optionName].c_str() );
 }
diff --git a/src/cli/CommandLine.h b/src/cli/CommandLine.h
index 7e2a8f3..7b02cf4 100644
--- a/src/cli/CommandLine.h
+++ b/src/cli/CommandLine.h
@@ -16,6 +16,7 @@ public:
 	bool hasOptionVector(string optionName);
 
 	string getOption(string optionName);
+	vector<string> getOptionMulti(string optionName);
 	int getOptionInt(string optionName);
 	vec3 getOptionVector(string optionName);
 	vector<string> getOptionList(string optionName);
diff --git a/src/main.cpp b/src/main.cpp
index 8c5a7b8..4ed0bc4 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,6 +4,7 @@
 #include "CommandLine.h"
 #include "Renderer.h"
 #include "globals.h"
+#include "Wad.h"
 
 // fix v6:
 // - force rotate not refreshing entities anymore
@@ -613,6 +614,70 @@ int rename_texture(CommandLine& cli) {
 	return 0;
 }
 
+int remove_unused_wads_cmd(CommandLine& cli) {
+	Bsp* map = new Bsp(cli.bspfile);
+	if (!map->valid)
+		return 1;
+
+	// determine the directories to search for WAD files
+	vector<string> wadDirs = cli.getOptionMulti("-waddir");
+	for (int i = 0; i < wadDirs.size(); i++) {
+		string& wadDir = wadDirs[i];
+		if (wadDir.size() && wadDir[wadDir.size() - 1] != '/' && wadDir[wadDir.size() - 1] != '\\') {
+			wadDir += "/";
+		}
+	}
+
+	vector<string> wadNames = map->get_wad_names();
+	vector<Wad*> wads;
+
+	for (int i = 0; i < wadNames.size(); i++) {
+		string wadName = basename(wadNames[i]);
+
+		vector<string> tryPaths;
+		for (int k = 0; k < wadDirs.size(); k++) {
+			tryPaths.push_back(wadDirs[k] + wadName);
+		}
+		tryPaths.push_back(wadName);
+
+		Wad* wad = NULL;
+		for (int k = 0; k < tryPaths.size(); k++) {
+			if (fileExists(tryPaths[k])) {
+				wad = new Wad(tryPaths[k]);
+				if (wad->readInfo()) {
+					break;
+				}
+				else {
+					delete wad;
+					wad = NULL;
+				}
+			}
+		}
+
+		if (wad) {
+			wads.push_back(wad);
+		}
+		else {
+			logf("WARNING: Failed to load WAD '%s'\n", wadName.c_str());
+		}
+	}
+
+	map->remove_unused_wads(wads);
+
+	map->update_ent_lump();
+
+	if (map->isValid()) map->write(cli.hasOption("-o") ? cli.getOption("-o") : map->path);
+	logf("\n");
+
+	for (int i = 0; i < wads.size(); i++) {
+		delete wads[i];
+	}
+
+	delete map;
+
+	return 0;
+}
+
 
 void print_help(string command) {
 	if (command == "merge") {
@@ -736,6 +801,22 @@ void print_help(string command) {
 		"Example: bspguy rename c1a0.bsp -old aaatrigger -new aaadigger\n"
 	);
 	}
+	else if (command == "remove_unused_wads") {
+	logf(
+		"remove_unused_wads - Removes unused WADs from the worldspawn 'wad' keyvalue\n"
+		"                     and strips folder paths from the remaining WAD references.\n\n"
+
+		"Usage:   bspguy remove_unused_wads <mapname> [options]\n"
+		"Example: bspguy remove_unused_wads c1a0.bsp -waddir \"C:/Steam/steamapps/common/Half-Life/valve\"\n"
+
+		"\n[Options]\n"
+		"  -waddir <dir> : Directory to search for the WAD files referenced by the map.\n"
+		"                  May be specified multiple times to search several directories.\n"
+		"                  If a WAD can't be found, it is left in the list to avoid\n"
+		"                  removing a WAD that might actually be required.\n"
+		"  -o <file>     : Output file. By default, <mapname> is overwritten.\n"
+	);
+	}
 	else {
 		logf("%s\n\n", g_version_string);
 		logf(
@@ -751,6 +832,7 @@ void print_help(string command) {
 			"  transform          : Apply 3D transformations to the BSP\n"
 			"  unembed            : Deletes embedded texture data\n"
 			"  renametex          : Renames/replaces a texture in the BSP\n"
+			"  remove_unused_wads : Removes unused WADs from the worldspawn 'wad' key\n"
 
 			"\nRun 'bspguy <command> help' to read about a specific command.\n"
 			"\nTo launch the 3D editor, run this program without any arguments."
@@ -874,6 +956,9 @@ int main(int argc, char* argv[])
 		else if (cli.command == "renametex") {
 			return rename_texture(cli);
 		}
+		else if (cli.command == "remove_unused_wads") {
+			return remove_unused_wads_cmd(cli);
+		}
 		else {
 			logf("unrecognized command: %d\n", cli.command.c_str());
 		}
@@ -881,4 +966,3 @@ int main(int argc, char* argv[])
 
 	return 0;
 }
-
-- 
2.47.3

