Skip to content

Instantly share code, notes, and snippets.

@cdwfs
Created September 6, 2025 19:18
Show Gist options
  • Save cdwfs/04e5b1e616e146363108c93455143f8f to your computer and use it in GitHub Desktop.
Save cdwfs/04e5b1e616e146363108c93455143f8f to your computer and use it in GitHub Desktop.
Python 3.x script to backup and restore RPGMaker projects without all the boilerplate files
import argparse
import hashlib
import os
import shutil
import sys
import zipfile
import zlib
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Backup and restore RPGMaker projects without wasting space on default files",
epilog="")
parser.add_argument("mode", choices=["backup", "restore"])
parser.add_argument("-t", "--template", required=True, help="An empty/default project for the appropriate RPGMaker version.")
parser.add_argument("-p", "--project", required=True, help="The location of the project to backup or restore. In 'restore' mode, this directory is the output, and will be overwritten. In 'backup' mode, this directory is the input.")
parser.add_argument("-z", "--zip", required=True, help="The location of the backed-up project, as a ZIP file. In 'restore' mode, this file is the input. In 'backup' mode, this file is the output, and will be overwritten.")
parser.add_argument("-f", dest="overwrite", action="store_true", help="Overwrite existing output file/directory, if it already exists.")
args = parser.parse_args()
# validation
if not os.path.isdir(args.template):
sys.exit(f"ERROR: template project not found at {args.template}")
if args.mode == "backup":
if not os.path.isdir(args.project):
sys.exit(f"ERROR: can't back up non-existent project {args.project}")
if os.path.isfile(args.zip) and not args.overwrite:
sys.exit(f"ERROR: output file {args.zip} already exists; use -f to automatically overwrite it.")
elif args.mode == "restore":
if not os.path.isfile(args.zip):
sys.exit(f"ERROR: can't restore non-existent project {args.zip}")
if os.path.isdir(args.project) and not args.overwrite:
sys.exit(f"ERROR: output project {args.project} already exists; use -f to automatically overwrite it.")
if args.mode == "backup":
# Compute hashes of all files in the template project
template_path_hashes = {}
for root, dirs, files in os.walk(args.template):
for filename in files:
abspath = os.path.abspath(os.path.join(root,filename))
relpath = os.path.relpath(abspath,args.template)
with open(abspath, "rb") as f:
template_path_hashes[relpath] = hashlib.sha256(f.read()).hexdigest()
# Iterate over files in the project directory.
# If they don't exist in the template, or if they exist but their hash value is different, add them to the output zip.
with zipfile.ZipFile(args.zip, "w", compression=zipfile.ZIP_DEFLATED) as z:
for root, dirs, files in os.walk(args.project, topdown=True):
# Filter .git, it's full of noise and is basically already its own backup.
if ".git" in dirs:
del dirs[dirs.index(".git")]
for filename in files:
add_to_zip = False
abspath = os.path.abspath(os.path.join(root,filename))
relpath = os.path.relpath(abspath,args.project)
expected_hash = template_path_hashes.get(relpath, None)
if expected_hash is None:
# files not in the template always get added; no need to compute a hash.
#print(f" {relpath} not found in template")
add_to_zip = True
else:
# files in the template are only added if their hash is different.
with open(abspath, "rb") as f:
if hashlib.sha256(f.read()).hexdigest() != expected_hash:
#print(f" {relpath} has different hash")
add_to_zip = True
if add_to_zip:
print(f" {relpath} ({os.path.getsize(abspath)})")
z.write(abspath, arcname=relpath)
print(f"Wrote {args.zip} (size={os.path.getsize(args.zip)})")
elif args.mode == "restore":
# Copy the template files to the output project dir
print(f"Copying template {args.template} to project {args.template}")
shutil.copytree(args.template, args.project, dirs_exist_ok=True)
# unzip the zipfile into the project dir, overwriting anything already there.
with zipfile.ZipFile(args.zip, "r") as z:
for member_info in z.infolist():
print(f" {member_info.filename} ({member_info.compress_size} -> {member_info.file_size})")
z.extract(member_info, path=args.project)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment