Skip to content

Instantly share code, notes, and snippets.

@WindowDump
Last active June 16, 2019 15:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WindowDump/76ee68f05b539c970e079d3ff52718d0 to your computer and use it in GitHub Desktop.
Save WindowDump/76ee68f05b539c970e079d3ff52718d0 to your computer and use it in GitHub Desktop.
thcrap patch configuration randomizer (Python 3.5+)
#!/usr/bin/env python3
# thcrap patch configuration randomizer by Window Dump, version 190430
# Changelog
# 190430
# * Added more patches to the default blacklist which probably corrupt your scorefiles
# 190314-2
# * Don't download files.js - it's managed by thcrap :nmlgcgod:
# * If patch.js had to be downloaded, keep it in memory
# 190314
# * Use requests to download patch.js for patches that aren't downloaded yet
# * Remove _custom launch targets before randomly chosing a game
# * Default blacklist
# - lang_en dependencies (since they're added by default)
# - rEoSD (trashes scorefiles, not nice to happen randomly)
# * Prevent exception if a very large number of patches is selected
# * Ignore .git directories
# * Only remove blacklisted patches that have actually been discovered
# 190313-2
# * Use Path.exists() (may result in cross-platform support)
# * Use proper encoding settings
# 190313
# * Proof of concept
# Configuration - edit as desired. An empty Python list is list() or [].
# How many random patches to select
num_patches = 5
# Include remote patches not yet downloaded (requires requests)
download_new_patches = True
# Patches to add before and after randomly selected patches
# These must not end in a /
pre_patches = ['thpatch/lang_en']
post_patches = ['nmlgc/aero'] # You can remove 'nmlgc/aero' if you're not on Windows 7
# Do not randomly select these patches
# These must not end in a /
blacklist = ['dass7/reosd',
'SMB3Memes/LoLKECL',
'MoriyaFaith/rEoSD-EX',
'MoriyaFaith/rEoSD-EX_alphes',
'nmlgc/base_tsa',
'nmlgc/base_tasofro',
'nmlgc/script_latin',
'nmlgc/western_name_order']
# Extra spice: launch a random game
launch_random_game = True
# End of configuration.
from pathlib import Path
from random import sample, choice
import json
import subprocess
if download_new_patches:
import requests
# Modified from scripts/util.py for an authentic thcrap JSON experience
json_dump_params = {
'ensure_ascii': False,
'indent': ' ',
'separators': (',', ': '),
'sort_keys': True
}
patches = list()
selected = list()
def add_patch(patch):
"""If the provided patch isn't already added,
adds its dependencies to the list of selected patches,
then adds this patch.
If download_new_patches is True, this will also download the files needed
for thcrap if they do not already exist."""
global selected
if patch in selected: return
# Download the meta files for this patch if needed
if download_new_patches and not Path(patch + '/patch.js').exists():
patch_info = download_patch(patch)
else:
with open(patch + '/patch.js', 'r', encoding='utf-8') as f:
patch_info = json.load(f)
if 'dependencies' in patch_info:
for dependency in patch_info['dependencies']:
if '/' not in dependency: # Depends on something in same repo
dependency = patch.split('/')[0] + '/' + dependency
if dependency not in selected:
add_patch(dependency)
selected.append(patch)
def download_patch(patch):
"""Downloads the necessary meta files for this patch,
allowing any necessary files to be downloaded by thcrap at launch.
Returns a parsed JSON dictionary of patch.js"""
Path(patch).mkdir(exist_ok=True)
# Get the remote servers
repo, target_patch = patch.split('/')
with open(repo + '/repo.js', 'r', encoding='utf-8') as f:
repo_info = json.load(f)
# TODO: Try every server in case one is down, for robustness
server = repo_info['servers'][0]
# Download, save, and parse patch.js
r = requests.get(server + '/' + target_patch + '/patch.js')
with open(patch + '/patch.js', 'w', encoding='utf-8') as f:
f.write(r.text)
return r.json()
# Find local repositories, then discover patches based on user preference
dirs = Path('.')
for repo in [x for x in dirs.iterdir() if x.is_dir()]:
if Path(str(repo) + '/repo.js').exists(): # This is actually a repository
if download_new_patches: # Parse repo.js for patches
with open(str(repo) + '/repo.js', 'r', encoding='utf-8') as f:
repo_info = json.load(f)
for patch in repo_info['patches'].keys():
patches.append(str(repo) + '/' + patch)
else: # Look for locally downloaded patches, ignore .git
for patch in [x for x in repo.iterdir() if x.is_dir() and '.git' not in str(x)]:
patches.append(str(patch).replace('\\', '/')) # Bill Gates Was Here
# Create our patch configuration
# Don't randomly choose patches we're adding or don't want
for patch in pre_patches:
add_patch(patch)
patches.remove(patch)
for patch in post_patches:
patches.remove(patch)
for patch in blacklist:
if patch in patches:
patches.remove(patch)
# Choose random patches
for patch in sample(patches, min(num_patches, len(patches))):
add_patch(patch)
for patch in post_patches:
add_patch(patch)
# Export patch configuration to random.js
patch_config = {'console': False, 'dat_dump': False,
'patches': [{'archive': x + '/'} for x in selected]}
with open('random.js', 'w', encoding='utf-8') as f:
json.dump(patch_config, f, **json_dump_params)
# Launch a random game if requested
if launch_random_game:
with open('games.js', 'r', encoding='utf-8') as f:
games = json.load(f)
target = choice([x for x in games.keys() if '_custom' not in x])
subprocess.run('thcrap_loader "random.js" ' + target)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment