Currently the hook preprocessor simply tries loading a script provided by the path, but that approach doesn't work if the game was exported with compressed scripts (.gdc).
I did some tests and it is possible to decompile the compressed .gdc scripts and patch them into the .pck - adding them as .gd files (not replacing the .gdc).
Doing that makes the mod loader properly generate hooks when launching the game.
It is important to also patch the *.gd.remap files then doing that, otherwise the game will still load the compiled .gdc scripts.
My proof of concept:
from pathlib import Path
import os
import shutil
import subprocess
import sys
pck_path = 'Game.pck'
temp_folder = 'temp'
tool_path = 'gdre_tools.exe'
temp_path = Path(temp_folder)
def recover_scripts_and_remaps():
# recover/decompile all scripts, we do this first because it cleans the temp folder
subprocess.call([tool_path, '--headless', f'--recover={pck_path}', '--include=res://**/*.gdc', f'--output={temp_folder}'])
# extract all the .gd.remap files
subprocess.call([tool_path, '--headless', f'--extract={pck_path}', '--include=res://**/*.gd.remap', f'--output={temp_folder}'])
def fix_remaps():
# go through all .gd.remap files and make them point back to the .gd file
for remap_file_path in temp_path.rglob('*.gd.remap'):
content = ''
with open(remap_file_path, 'rt') as remap_file:
content = remap_file.read()
content = content.replace('.gdc"', '.gd"')
with open(remap_file_path, 'wt') as remap_file:
remap_file.write(content)
def get_max_cmd_length():
if os.name == 'nt':
return 30000
return 100000
MAX_CMD_LENGTH = get_max_cmd_length()
def collect_patch_args(patterns):
patch_args = []
seen_files = set()
for pattern in patterns:
for file_path in temp_path.rglob(pattern):
if file_path in seen_files:
continue
seen_files.add(file_path)
source_rel = file_path.relative_to(temp_path)
source_path = str(temp_path / source_rel)
dest_path = 'res://' + source_rel.as_posix()
patch_args.append(f'--patch-file={source_path}={dest_path}')
return patch_args
def chunk_args(base_args, patch_args):
chunks = []
current = []
base_len = sum(len(a) + 1 for a in base_args)
current_len = base_len
for arg in patch_args:
arg_len = len(arg) + 1
if current_len + arg_len > MAX_CMD_LENGTH:
chunks.append(current)
current = [arg]
current_len = base_len + arg_len
else:
current.append(arg)
current_len += arg_len
if current:
chunks.append(current)
return chunks
def patch_many(patterns):
patch_args = collect_patch_args(patterns)
if not patch_args:
return
base_args = [
tool_path,
'--headless',
f'--pck-patch={pck_path}',
f'--output={pck_path}',
]
# chunking the --patch-file commands to avoid reloading GDRE each file
chunks = chunk_args(base_args, patch_args)
for i, chunk in enumerate(chunks, 1):
args = base_args + chunk
print(f'Running patch batch {i}/{len(chunks)} with {len(chunk)} files')
subprocess.check_call(args)
recover_scripts_and_remaps()
fix_remaps()
patch_many(['*.gd', '*.gd.remap'])
shutil.rmtree(temp_path)
In theory it might be possible to do this without patching the game's PCK, but I haven't tested that:
- while preparing the hooks, check whether there is a compressed script, if it's not then continue processing the current way,
- decompile with GDRE to .gd file, apply patches onto that, put it into the zip,
- also extract .gd.remap file and fix the path inside from .gdc to .gd (or generate a new .gd.remap file), also put into the zip.
Currently the hook preprocessor simply tries loading a script provided by the path, but that approach doesn't work if the game was exported with compressed scripts (.gdc).
I did some tests and it is possible to decompile the compressed .gdc scripts and patch them into the .pck - adding them as .gd files (not replacing the .gdc).
Doing that makes the mod loader properly generate hooks when launching the game.
It is important to also patch the *.gd.remap files then doing that, otherwise the game will still load the compiled .gdc scripts.
My proof of concept:
In theory it might be possible to do this without patching the game's PCK, but I haven't tested that: