Skip to content

Instantly share code, notes, and snippets.

@RoadrunnerWMC
Last active June 9, 2021 15:04
Show Gist options
  • Save RoadrunnerWMC/090dd03b3ba6c6fc99c976eaa0f1edf0 to your computer and use it in GitHub Desktop.
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
# 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