diff --git a/Makefile b/Makefile index 2568d77b..d94a03a1 100644 --- a/Makefile +++ b/Makefile @@ -253,6 +253,23 @@ wad-image-help: wad-image-common @echo " make wad-image-diff WI_VERBOSE=t WI_GIF=t WI_SHOW=t WI_CMD=animate \ WI_COMMIT=\"0c004ce~..0c004ce\"" +# Test targets all of which are a dependency of "test". + +# Test that WAD files have the expected map names. +test-map-names: + scripts/fix-map-names -t levels + +# Run all tests. Add test-* targets above, and then as a dependency here. +test: test-map-names + @echo + @echo "All tests passed." + +# Non-test targets that run scripts in the "scripts" directory. + +# Fix the map names. +fix-map-names: + scripts/fix-map-names levels + %.6: $(MAKE) -C dist man-$* diff --git a/scripts/fix-map-names b/scripts/fix-map-names new file mode 100755 index 00000000..f839d345 --- /dev/null +++ b/scripts/fix-map-names @@ -0,0 +1,206 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: BSD-3-Clause +# +# fix-map-names - Fix map names in WAD files +# +# Fix the map name, which is the name of the first lump. If the "-t" option is +# passed for test mode then incorrect map names are displayed, but not fixed. +# +# This script can be invoked with make target "fix-map-names" (no "-t" option) +# or make target "test-map-names" ("-t" option). Make target "test" +# ("-t" option) will run this and any other test. + +from __future__ import print_function + +# Imports + +import argparse +import os +import re +import struct +import sys + +# Globals + +args = {} # Command line arguments. +error_count = 0 +fixes_needed = 0 +freedoom_1_re = re.compile(r"^C(\d)M(\d)$") # FD #1 maps +freedoom_dm_re = re.compile(r"^DM(\d\d)$") # FD DM maps +header_shown = False +ignored_wads = set(["dummy.wad", "test_levels.wad"]) +last_error = None +map_name_re = re.compile(r"^((E\dM\d)|(MAP\d\d))$") +output_line = "%-17s %-9s %-7s %s" + +# Functions + +# Handle error 'msg'. Pass None to reset 'last_error'. +def error(msg): + global error_count + global last_error + + last_error = msg + if msg: + error_count += 1 + +# Given WAD path 'wad' return the expected map name as a function of the +# filename. +def get_expected_map_name(wad): + # Strip of the directory, upper case, remove ".wad". + name = os.path.basename(wad).upper() + if name.endswith(".WAD"): + name = name[:-4] + + # Convert from Freedoom name to Doom names. + name = freedoom_1_re.sub(r"E\1M\2", name) + name = freedoom_dm_re.sub(r"MAP\1", name) + + if map_name_re.match(name): + return name + else: + return None + +# Parse the command line arguments and store the result in 'args'. +def parse_args(): + global args + + parser = argparse.ArgumentParser( + description="Fix map names in WAD files.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + # The following is sorted by long argument. + + parser.add_argument("-f", "--force", action="store_true", + help="Force. Fix map name regardless of the existing map name.") + parser.add_argument("-q", "--quiet", action="store_true", + help="Quiet (minimum output).") + parser.add_argument("-r", "--recursive", action="store_true", + help="Recurse into directories.") + parser.add_argument("-t", "--test", action="store_true", + help="Test mode. Don't make any changes.") + parser.add_argument("paths", metavar="PATH", nargs="+", + help="WAD paths, files and directories.") + + args = parser.parse_args() + + return args + +# Process path 'path' which is at depth 'depth'. If 'depth' is 0 then this is +# a top level path passed in on the command line. +def process_path(path, depth): + if os.path.isdir(path): + # Directory. If not recursive then only consider this directory if it + # was specified explicitly. + if args.recursive or not depth: + path_list = os.listdir(path) + path_list.sort() + for base in path_list: + process_path(path + "/" + base, depth + 1) + else: + # File. Only process WAD files that were specified explicitly + # (depth 0), or that have the expected suffix. + if (not depth) or path.lower().endswith(".wad"): + process_wad(path) + +# Process the paths passed in on the command line. +def process_paths(): + for path in args.paths: + process_path(path, 0) + +# Process WAD path 'wad'. +def process_wad(wad): + global header_shown + global last_error + global fixes_needed + + if os.path.basename(wad).lower() in ignored_wads: + # A known WAD that should not be processed. + return + + try: + # Reset everything. + error(None) + lump_name = None + fix_needed = False + + expected_name = get_expected_map_name(wad) + if not expected_name: + raise Exception("Unable to get the expected name") + with open(wad, "rb" if args.test else "r+b") as fhand: + magic = fhand.read(4) + if not isinstance(magic, str): + # magic is bytes in Python 3. + magic = magic.decode("UTF-8") + if not magic == "PWAD": + raise Exception("Not a PWAD. magic=" + magic) + # Directory at offset 0x8 in the header. + fhand.seek(0x08) + directory_offset, = struct.unpack("