Last active
June 9, 2021 15:04
-
-
Save RoadrunnerWMC/090dd03b3ba6c6fc99c976eaa0f1edf0 to your computer and use it in GitHub Desktop.
A script for automatic real-time NSMBW level padding, to make it easier to rapidly test changes in Dolphin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 2021-05-01 | |
# Requirements | |
# ============ | |
# | |
# - Python 3.8+ | |
# - Watchdog (`pip install watchdog`) | |
# Usage | |
# ===== | |
# | |
# "/path/to/Stage/" is the path to the Stage folder in the extracted-NSMBW-disc folder that | |
# you plan to use with Dolphin. In a terminal, run: | |
# | |
# python3 realtime_auto_padder.py /path/to/Stage/ | |
# | |
# (On Windows, use `py -3` instead of `python3`.) This will start the script watching your Stage | |
# folder. You can also optionally add a second CLI argument, to specify the amount of padding you | |
# want. (The default is in the DEFAULT_ALIGNMENT constant below.) | |
# | |
# You can launch Dolphin and Reggie at this point. | |
# | |
# In Reggie, open the level you want to work on, e.g. 01-02.arc, make changes, and resave to | |
# "01-02_unpadded.arc". | |
# | |
# While it's running, the script will detect writes to any file with a name matching | |
# "XX-YY_unpadded.arc", and automatically write a padded version to "XX-YY.arc". | |
import pathlib | |
import re | |
import sys | |
import time | |
import watchdog.events # pip install watchdog | |
import watchdog.observers # pip install watchdog | |
UNPADDED_FILENAME_SUFFIX = '_unpadded' | |
FILENAME_PATTERN = re.compile(f'^(?P<level_name>0\d-\d\d){re.escape(UNPADDED_FILENAME_SUFFIX)}\.arc$') | |
DEFAULT_ALIGNMENT = 0x10000 | |
class Handler(watchdog.events.FileSystemEventHandler): | |
def on_any_event(self, event: watchdog.events.FileSystemEvent): | |
if event.is_directory: return | |
# Sometimes (?), instead of just modifying the file, Python (Reggie) | |
# will opt to create a new temporary file and move it over the original. | |
# So we need to handle that case too. | |
if event.event_type == 'modified': | |
self.handle_file_changed(event, pathlib.Path(event.src_path)) | |
elif event.event_type == 'moved': | |
self.handle_file_changed(event, pathlib.Path(event.dest_path)) | |
def handle_file_changed(self, event: watchdog.events.FileSystemEvent, path: pathlib.Path): | |
match = FILENAME_PATTERN.fullmatch(path.name) | |
if match: | |
level_name = match.group('level_name') | |
in_path = path | |
out_path = in_path.parent / f'{level_name}.arc' | |
data = in_path.read_bytes() | |
orig_len = len(data) | |
new_len = self.alignment * (orig_len // self.alignment + 1) | |
assert new_len >= orig_len | |
print(f'Padding: {level_name}{UNPADDED_FILENAME_SUFFIX}.arc -> {level_name}.arc (0x{orig_len:X} -> 0x{new_len:X}) ({orig_len/new_len:0.3f}%)') | |
data += b'\0' * (new_len - orig_len) | |
out_path.write_bytes(data) | |
def main(): | |
if len(sys.argv) < 2: | |
print(f'Usage: {sys.argv[0]} /path/to/Stage/ [alignment_in_hex]') | |
return | |
if len(sys.argv) >= 3: | |
alignment = int(sys.argv[2], 16) | |
else: | |
alignment = DEFAULT_ALIGNMENT | |
watch_path = pathlib.Path(sys.argv[1]) | |
print(f'Watching {watch_path}...') | |
handler = Handler() | |
handler.alignment = alignment | |
observer = watchdog.observers.Observer() | |
observer.schedule(handler, watch_path) | |
observer.start() | |
try: | |
while True: | |
time.sleep(0.2) | |
except KeyboardInterrupt: | |
observer.stop() | |
observer.join() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment