Skip to content

Instantly share code, notes, and snippets.

@Kodiologist
Created December 21, 2023 19:33
Show Gist options
  • Save Kodiologist/5d68204e0dfad8f19a3c891375c012b9 to your computer and use it in GitHub Desktop.
Save Kodiologist/5d68204e0dfad8f19a3c891375c012b9 to your computer and use it in GitHub Desktop.
Zephyr Breeze, a Ponyvania cheater
#!/usr/bin/env python3
'Zephyr Breeze, a Ponyvania cheater.'
# This program is in the public domain.
# It's tested on the Windows build of Ponyvania v2.0.2.
# Call it with the option `--help` for usage.
import re, subprocess
from pathlib import Path
from tempfile import TemporaryDirectory
replacers = dict(
disable_save_crypt = dict(
help = 'Disable encryption (and decryption) of `ponySaveFile1`, `ponySaveFile2`, etc. You can then edit your saves easily in a text editor. Before using this replacer, back up your saves, and decrypt each save file by loading it and copying `saveFile.ini`.',
f = lambda x: re.sub(
r'fast_file_key_crypt\(([^,]+), ([^,]+),.+',
r'if (file_exists(\2)) {file_delete(\2)} file_copy(\1, \2)',
x),
scripts = [
'gml_Object_obj_creationMenu_Create_0',
'gml_Object_obj_loadGame_Create_0',
'gml_Object_obj_loadGame_Step_0',
'gml_Script_script_loadSettings',
'gml_Script_script_saveSettings']),
auto_pda = dict(
help = 'Enable the effect of the PDA (Peekaboo + Dowsing Stick + Eye of Decay) regardless of which accessory you have equipped.',
f = lambda x:
re.sub(r'\S+ == "PDA"', 'true', x),
scripts = [
'gml_Object_obj_player_Step_0',
'gml_Object_obj_settings_Draw_0',
'gml_Object_obj_spawnBlueChest_Step_0',
'gml_Object_obj_spawnStash_Step_0']),
print_drop_rates = dict(
help = 'Print the drop rates of items to the console when you kill a monster.',
f = lambda x: re.sub(
r'\{\s+(if \(random\(100\) < (.+)\))',
r'{show_debug_message("Drop rate: 1 in " + string(100 / (\2))); \1',
x),
scripts = [
'gml_Script_script_dropRates']),
super_paw = dict(
help = 'Massively increase the luck bonus from the Black Cat Paw.',
f = lambda x: re.sub(
r'(if \(argument0 == "Black Cat Paw"\)\s+return )\d+;',
lambda m: m.group(1) + '900;',
x),
scripts = [
'gml_Script_script_weaponLCK']),
force_changelings = dict(
help = "Force a changeling to spawn in any room of Canterlot that allows one. (You probably only want to keep this replacer on while you're hunting changelings.)",
f = lambda x:
re.sub(r'\bspawnAnyway == 1\b', 'true', x),
scripts = [
'gml_Object_obj_spawnEnemy_Step_0']))
def replace_datadotwin(umc_path, code_dir, data_target_path, data_orig_path, **kwargs):
# Get the replacers to use.
rs = [r for (name, r) in replacers.items() if kwargs[name]]
# Get the code of each script to be replaced.
scripts = {
name: (code_dir / (name + '.gml')).read_text()
for name in sorted({name
for replacer in rs
for name in replacer['scripts']})}
# Process the replacers.
for replacer in rs:
for name in replacer['scripts']:
scripts[name] = replacer['f'](scripts[name])
with TemporaryDirectory() as td:
td = Path(td)
# Write out the new scripts into their own files.
for name, code in scripts.items():
(td / (name + '.gml')).write_text(code)
# Uncomment the next line to look at the generated files.
#print("Look in this directory to see the new code:"); print(td); import time; time.sleep(1000000)
# Invoke UndertaleModTool.
subprocess.run(cwd = td, check = True, args = map(str, [
umc_path,
'replace', data_orig_path,
'-o', data_target_path,
'-c', *(f'{name}=./{name}.gml' for name in scripts)]))
def main():
import argparse
p = argparse.ArgumentParser(description = __doc__)
p.add_argument('umc_path',
help = "The path to UndertaleModTool's `UndertaleModCli`",
type = Path)
p.add_argument('code_dir',
help = 'The `CodeEntries` directory produced by `UndertaleModCli dump data.win -c UMT_DUMP_ALL`',
type = Path)
p.add_argument('data_target_path',
help = "Ponyvania's `data.win` file to overwrite",
type = Path)
p.add_argument('data_orig_path',
help = "A backup you've made of the original `data.win`",
type = Path)
g = p.add_argument_group('replacers')
for name, d in replacers.items():
g.add_argument('--' + name.replace('_', '-'),
help = d['help'],
action = 'store_true')
replace_datadotwin(**vars(p.parse_args()))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment