# Usage: python gen.py import json import os import re from collections import defaultdict script_dir = os.path.dirname(os.path.abspath(__file__)) usage_file = os.path.join(script_dir, 'usage.json') with open(usage_file, 'r') as file: data = json.load(file) with open(os.path.join(script_dir, '../USAGE.md'), 'w') as md_file: md_file.write(""" Usage ---- There are many environment variables to control Box64's behaviour, which will be listed below by category. There are 2 types of Box64 builds: the Wine WOW64 build (WowBox64) and the regular Linux build. Beware only some of the environment variables are available in WowBox64. ### Configuration files for Linux build In addition to environment variables, if you're using the regular Linux build, Box64 also looks for 2 places for rcfile by default: the system-wide `/etc/box64.box64rc` and user-specific `~/.box64rc`. Settings priority follows this order (from highest to lowest): `~/.box64rc` > `/etc/box64.box64rc` > environment variables. Example configuration: ``` [factorio] BOX64_DYNAREC_SAFEFLAGS=0 BOX64_DYNAREC_BIGBLOCK=2 BOX64_DYNAREC_FORWARD=1024 BOX64_DYNAREC_CALLRET=1 ``` This configuration will apply the specified settings application-wide to any executable named `factorio`. ### Advanced usage for Linux build 1. **Wildcard Matching** Asterisks (`*`) can be used for basic pattern matching in application names. For instance, `[*setup*]` will match any program containing "setup" in its name. Note this implements simple wildcard matching rather than full regex support. 2. **Custom Configuration File** The `BOX64_RCFILE` environment variable can specify an alternative configuration file instead of the default `/etc/box64.box64rc`. 3. **Per-File Settings** Sections starting with `/` apply to specific files. For example: ``` [/d3d9.dll] BOX64_DYNAREC_SAFEFLAGS=0 ``` These settings will only affect the `d3d9.dll` file. This syntax also works for **emulated** Linux libraries, e.g., `[/libstdc++.so.6]`. ---- """) categories = defaultdict(list) for entry in data: categories[entry["category"]].append(entry) for category, entries in categories.items(): md_file.write(f"## {category}\n\n") for entry in entries: md_file.write(f"### {entry['name']}\n\n{entry['description']}{' Availble in WowBox64.' if entry['wine'] else ''}\n\n") for option in entry['options']: md_file.write(f" * {option['key']}: {option['description']} {'[Default]' if option['default'] else ''}\n") md_file.write("\n") with open(os.path.join(script_dir, '../box64.pod'), 'w') as pod_file: pod_file.write("""=head1 NAME box64 - Linux Userspace x86_64 Emulator with a twist =head1 SYNOPSIS B [B<--help>] [B<--version>] I =head1 DESCRIPTION B lets you run x86_64 Linux programs (such as games) on non-x86_64 Linux systems, like ARM (host system needs to be 64-bit little-endian). Since B uses the native versions of some "system" libraries, like libc, libm, SDL, and OpenGL, it's easy to integrate and use with most applications, and performance can be surprisingly high in many cases. B integrates with DynaRec (dynamic recompiler) for the ARM64 platform, providing a speed boost between 5 to 10 times faster than using only the interpreter. =head1 OPTIONS =over 8 =item B<-h,--help> Print box64 help and quit. =item B<-v,--version> Print box64 version and quit. =back =head1 BRIEF USAGE There are many environment variables to control B's behaviour. In addition to environment variables, B also looks for 2 places for rcfile: F and F<~/.box64rc>, in the format of .ini files. Settings priority: F<~/.box64rc> > F > environment variables. Example: [factorio] BOX64_DYNAREC_SAFEFLAGS=0 BOX64_DYNAREC_BIGBLOCK=2 BOX64_DYNAREC_FORWARD=1024 BOX64_DYNAREC_CALLRET=1 =head1 ENVIRONMENT VARIABLES =over 8 """) for entry in data: pod_file.write(f"\n=item B<{entry['name']}> =I<{ '|'.join(option['key'] for option in entry['options']) }>\n\n{entry['description']}{' Availble in WowBox64.' if entry['wine'] else ''}\n\n") for option in entry['options']: pod_file.write(f" * {option['key']} : {option['description']} {'[Default]' if option['default'] else ''}\n") pod_file.write("\n") pod_file.write(""" =back =cut """) #################### # Validation #################### PADDING = 30 INCLUDE_BLANK = False def get_usage_entry(usage, define): entry = list(e for e in usage if e["name"] == define) if len(entry) == 0: print(f"{define:<{PADDING}}: missing usage.json entry") elif len(entry) > 1: print(f"{define:<{PADDING}}: multiple usage entries found", len(entry)) else: return entry[0] return None # check `default` uniqueness for entry in data: i = list(d["default"] for d in entry["options"]).count(True) if i > 1: print(f"{entry['name']:<{PADDING}}: multiple default values usage.json") # regex to match env.h C code regex = { "INTEGER": re.compile( r"^\s*INTEGER\((?P\w+), (?P\w+), (?P\w+), (?P\w+), (?P\w+), (?P\w+)\)" ), "INTEGER64": re.compile( r"^\s*INTEGER64\((?P\w+), (?P\w+), (?P\w+), (?P\w+)\)" ), "BOOLEAN": re.compile( r"^\s*BOOLEAN\((?P\w+), (?P\w+), (?P\w+), (?P\w+)\)" ), "ADDRESS": re.compile(r"^\s*ADDRESS\((?P\w+), (?P\w+), (?P\w+)\)"), "STRING": re.compile(r"^\s*STRING\((?P\w+), (?P\w+), (?P\w+)\)"), } env_file = os.path.join(script_dir, "../../src/include/env.h") with open(env_file, "r") as file: matches = {} for line in file.readlines(): for t, r in regex.items(): m = r.search(line) if m: # filter out comments and other non useful code if m.group("define") == "NAME": continue if t not in matches: matches[t] = {} if (m.group("define") in matches[t]): # multiple definitions, no default matches[t][m.group("define")]['no_default'] = True break matches[t][m.group("define")] = m.groupdict() matches[t][m.group("define")]['no_default'] = False for define, m in matches["INTEGER"].items(): name = m["name"] default = None if m["no_default"] or not m["default"].isdigit() else int(m["default"]) min = int(m["min"]) max = int(m["max"]) # Check default in valid range if default and (default < min or default > max): print(f"{define:<{PADDING}}: default lays outside of min/max range") # Check consistency with usage.json if e := get_usage_entry(data, define): # guess min/max values if possible min2 = max2 = None # blank means that the entry has an 'XXXX' entry which usually indicated that arbitrary values are valid blank = False default2 = None if int(m["wine"]) != int(e["wine"]): print(f"{define:<{PADDING}}: wine mismatch: env.h={int(m['wine'])}, usage.json={int(e['wine'])}") for o in e["options"]: if o["key"] == "XXXX": blank = True continue val = int(o["key"]) # not supposed to fail min2 = min2 if min2 is not None and min2 < val else val max2 = max2 if max2 is not None and max2 > val else val if o["default"]: default2 = val if min2 and min2 != min: if not blank or (blank and INCLUDE_BLANK): print( f"{define:<{PADDING}}: min value mismatch: env.h={min}, usage.json={min2}{(' (possible false positive)' if blank else '')}" ) if max2 and max2 != max: if not blank or (blank and INCLUDE_BLANK): print( f"{define:<{PADDING}}: max value mismatch: env.h={max}, usage.json={max2}{(' (possible false positive)' if blank else '')}" ) if default2 != default: print( f"{define:<{PADDING}}: default value mismatch: env.h={default}, usage.json={default2}" ) for define, m in matches['INTEGER64'].items(): # similar to INTEGER but without min/max name = m["name"] default = None if m["no_default"] or not m["default"].isdigit() else int(m["default"]) # Check consistency with usage.json if e := get_usage_entry(data, define): default2 = None if int(m["wine"]) != int(e["wine"]): print(f"{define:<{PADDING}}: wine mismatch: env.h={int(m['wine'])}, usage.json={int(e['wine'])}") for o in e["options"]: if o["key"] == "XXXX": continue val = int(o["key"]) if o["default"]: default2 = val if default2 != default: print( f"{define:<{PADDING}}: default value mismatch: env.h={default}, usage.json={default2}" ) for define, m in matches["BOOLEAN"].items(): name = m["name"] default = None if m["no_default"] or m["default"] not in ["0", "1"] else bool(m["default"]) # Check consistency with usage.json if e := get_usage_entry(data, define): default2 = None if int(m["wine"]) != int(e["wine"]): print(f"{define:<{PADDING}}: wine mismatch: env.h={int(m['wine'])}, usage.json={int(e['wine'])}") for o in e["options"]: try: val = bool(o["key"]) except ValueError: print(f"{define:<{PADDING}}: failed to parse boolean {o['key']}") if o["default"]: default2 = val if default2 != default: print( f"{define:<{PADDING}}: default value mismatch: env.h={default}, usage.json={default2}" ) # ADDRESS and STRING are not that interesting for define, m in matches["ADDRESS"].items(): if e := get_usage_entry(data, define): if int(m["wine"]) != int(e["wine"]): print(f"{define:<{PADDING}}: wine mismatch: env.h={int(m['wine'])}, usage.json={int(e['wine'])}") for define, m in matches["STRING"].items(): # skip BOX64_ENV[1-5] entries, they mismatch but this is fine if define.startswith("BOX64_ENV"): continue if e := get_usage_entry(data, define): if int(m["wine"]) != int(e["wine"]): print(f"{define:<{PADDING}}: wine mismatch: env.h={int(m['wine'])}, usage.json={int(e['wine'])}") # check that everything from usage.json is in env.h for e in data: define = e["name"] # skip BOX64_ENV[1-5] entries, they mismatch but this is fine if define.startswith("BOX64_ENV"): continue found = False for t in matches: for d in matches[t]: if d == define: found = True if not found: print(f"{define:<{PADDING}}: missing env.h entry")