Created
September 6, 2025 19:18
-
-
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
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
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