mirror of
https://github.com/ptitSeb/box64.git
synced 2025-07-06 01:59:45 +08:00
331 lines
11 KiB
Python
331 lines
11 KiB
Python
# 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("""<!--- This file is generated by gen.py, do not edit it directly. -->
|
|
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
|
|
|
|
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`.
|
|
While in WowBox64, the configuration file is checked at `%USERPROFILE%/.box64rc` only.
|
|
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)
|
|
|
|
# Put "Performance" at the top
|
|
sorted_categories = sorted(categories.items(), key=lambda x: x[0] != "Performance")
|
|
for category, entries in sorted_categories:
|
|
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<box64> [B<--help>] [B<--version>] I<executable>
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
B<Box64> 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<Box64>
|
|
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<Box64> 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<Box64>'s behaviour. In
|
|
addition to environment variables, B<Box64> also looks for 2 places for rcfile:
|
|
F</etc/box64.box64rc> and F<~/.box64rc>, in the format of .ini files.
|
|
Settings priority: F<~/.box64rc> > F</etc/box64.box64rc> > 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<define>\w+), (?P<name>\w+), (?P<default>\w+), (?P<min>\w+), (?P<max>\w+), (?P<wine>\w+)\)"
|
|
),
|
|
"INTEGER64": re.compile(
|
|
r"^\s*INTEGER64\((?P<define>\w+), (?P<name>\w+), (?P<default>\w+), (?P<wine>\w+)\)"
|
|
),
|
|
"BOOLEAN": re.compile(
|
|
r"^\s*BOOLEAN\((?P<define>\w+), (?P<name>\w+), (?P<default>\w+), (?P<wine>\w+)\)"
|
|
),
|
|
"ADDRESS": re.compile(r"^\s*ADDRESS\((?P<define>\w+), (?P<name>\w+), (?P<wine>\w+)\)"),
|
|
"STRING": re.compile(r"^\s*STRING\((?P<define>\w+), (?P<name>\w+), (?P<wine>\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")
|