Skip to content

Commit

Permalink
Fixes for recovery on extracted folder
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitalita committed Jun 19, 2022
1 parent 2d10978 commit 8e74c1b
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 115 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ This module has support for decompiling Godot 4.x, 3.x, and 2.x projects.

- To perform full project recovery from the command line:
```bash
gdre_tools --recover=game.pck [--output-dir=game-extract/]
gdre_tools --recover=game.pck
```
Optional arguments:
-`--output-dir=<out_dir>` : Output directory, defaults to `<NAME>_extracted`, or the project directory if one is specified
-`--key=<key>` : The Key to use if PAK/EXE is encrypted (hex string)

Use the same Godot tools version that the original game was compiled in to edit the project. In order to detect this, see [To detect Godot version](#to-detect-godot-version)

Expand Down
141 changes: 52 additions & 89 deletions standalone/gdre_main.gd
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
extends Control

var ver_major = 0;
var ver_minor = 0;
var ver_major = 0
var ver_minor = 0
var main : GDRECLIMain

func _ready():
$menu_background/version_lbl.text = $re_editor_standalone.get_version()
# test_functions()
Expand All @@ -26,8 +28,8 @@ func get_arg_value(arg):
func export_imports(output_dir:String):
var importer:ImportExporter = ImportExporter.new()
importer.load_import_files(output_dir, ver_major, ver_minor)
importer.decompile_scripts(output_dir)
var arr = importer.get_import_files()
var failed_files = []
print("Number of import files: " + str(arr.size()))
importer.export_imports(output_dir)
importer.reset()
Expand All @@ -54,13 +56,13 @@ func test_decomp(fname):
print("error failed to save "+ f)


func dump_files(exe_file:String, output_dir:String, enc_key:String = "") -> int:
func dump_files(input_file:String, output_dir:String, enc_key:String = "") -> int:
var err:int = OK;
var pckdump = PckDumper.new()
print(exe_file)
print(input_file)
if (enc_key != ""):
pckdump.set_key(enc_key)
err = pckdump.load_pck(exe_file)
err = pckdump.load_pck(input_file)
if err == OK:
print("Successfully loaded PCK!")
ver_major = pckdump.get_engine_version().split(".")[0].to_int()
Expand All @@ -77,57 +79,6 @@ func dump_files(exe_file:String, output_dir:String, enc_key:String = "") -> int:
print("error dumping to dir")
pckdump.clear_data()
return err

var decomp;
# TODO: instead of doing this, run the detect bytecode script
if version.begins_with("1.0"):
decomp = GDScriptDecomp_e82dc40.new()
elif version.begins_with("1.1"):
decomp = GDScriptDecomp_65d48d6.new()
elif version.begins_with("2.0"):
decomp = GDScriptDecomp_23441ec.new()
elif version.begins_with("2.1"):
decomp = GDScriptDecomp_ed80f45.new()
elif version.begins_with("3.0"):
decomp = GDScriptDecomp_054a2ac.new()
elif version.begins_with("3.1"):
decomp = GDScriptDecomp_514a3fb.new()
elif version.begins_with("3.2"):
decomp = GDScriptDecomp_5565f55.new()
elif version.begins_with("3.3"):
decomp = GDScriptDecomp_5565f55.new()
elif version.begins_with("3.4"):
decomp = GDScriptDecomp_5565f55.new()
elif version.begins_with("3.5"):
decomp = GDScriptDecomp_5565f55.new()
else:
print("Unknown version, no decomp")
pckdump.clear_data()
return err

print("Script version "+ version.substr(0,3)+".x detected")

for f in pckdump.get_loaded_files():
var da:Directory = Directory.new()
da.open(output_dir)
f = f.replace("res://", "")
if f.get_extension() == "gdc":
print("decompiling " + f)
if decomp.decompile_byte_code(output_dir.plus_file(f)) != OK:
print("error decompiling " + f)
else:
var text = decomp.get_script_text()
var gdfile:File = File.new()
if gdfile.open(output_dir.plus_file(f.replace(".gdc",".gd")), File.WRITE) == OK:
gdfile.store_string(text)
gdfile.close()
da.remove(f)
if da.file_exists(f.replace(".gdc",".gd.remap")):
da.remove(f.replace(".gdc",".gd.remap"))
print("successfully decompiled " + f)
else:
print("error failed to save "+ f)
decomp.free()
else:
print("ERROR: failed to load exe")
pckdump.clear_data()
Expand Down Expand Up @@ -178,18 +129,55 @@ func print_usage():
print("\nGeneral options:")
print(" -h, --help: Display this help message")
print("\nFull Project Recovery options:")
print("Usage: GDRE_Tools.exe --headless --recover=<PAK_OR_EXE_OR_EXTRACTED_ASSETS_DIR> --output-dir=<DIR> [options]")
print("Usage: GDRE_Tools.exe --headless --recover=<PAK_OR_EXE_OR_EXTRACTED_ASSETS_DIR> [options]")
print("")
print("--recover=<PAK_OR_EXE_OR_EXTRACTED_ASSETS_DIR>\t\tThe Pak, EXE, or extracted project directory to perform full project recovery on")
print("--output-dir=<DIR>\t\tOutput directory")
print("\nOptions:\n")
print("--key=<KEY>\t\tThe Key to use if PAK/EXE is encrypted (hex string)")
print("--output-dir=<DIR>\t\tOutput directory, defaults to <NAME_extracted>, or the project directory if one of specified")

#print("View Godot assets, extract Godot PAK files, and export Godot projects")
func recovery(input_file:String, output_dir:String, enc_key:String):
var da:Directory = Directory.new()
input_file = main.get_cli_abs_path(input_file)
print(input_file)
if output_dir == "":
output_dir = input_file.get_basename()
if output_dir.get_extension():
output_dir += "_recovery"
else:
output_dir = main.get_cli_abs_path(output_dir)
#debugging
#print_import_info(output_dir)
#print_import_info_from_pak(input_file)
#actually an directory, just run export_imports
if da.dir_exists(input_file):

if !da.dir_exists(input_file.plus_file(".import")):
print("Error: This does not appear to be a project directory")
else:
if output_dir != input_file:
if main.copy_dir(input_file, output_dir) != OK:
print("Error: failed to copy " + input_file + " to " + output_dir)
return
main.open_log(output_dir)
export_imports(output_dir)
elif da.file_exists(input_file):
main.open_log(output_dir)
var err = dump_files(input_file, output_dir, enc_key)
if (err == OK):
export_imports(output_dir)
else:
print("Error: failed to extract PAK file, not exporting assets")
else:
print("Error: failed to load " + input_file)



func handle_cli():
var args = OS.get_cmdline_args()

var exe_file:String = ""
var input_file:String = ""
var output_dir: String = ""
var enc_key: String = ""
for i in range(args.size()):
Expand All @@ -198,39 +186,14 @@ func handle_cli():
print_usage()
get_tree().quit()
if arg.begins_with("--recover"):
exe_file = normalize_path(get_arg_value(arg))
input_file = normalize_path(get_arg_value(arg))
elif arg.begins_with("--output-dir"):
output_dir = normalize_path(get_arg_value(arg))
elif arg.begins_with("--key"):
enc_key = get_arg_value(arg)
if exe_file != "":
var main = GDRECLIMain.new()
var da:Directory = Directory.new()
exe_file = main.get_cli_abs_path(exe_file)
print(exe_file)
if output_dir == "":
output_dir = exe_file.get_basename() + "_recovery"
else:
output_dir = main.get_cli_abs_path(output_dir)
main.open_log(output_dir)
#debugging
#print_import_info(output_dir)
#print_import_info_from_pak(exe_file)
#actually an directory, just run export_imports
if da.dir_exists(exe_file):
if !da.dir_exists(exe_file.plus_file(".import")):
print("Error: This does not appear to be a project directory")
else:
export_imports(exe_file)
elif da.file_exists(exe_file):
var err = dump_files(exe_file, output_dir, enc_key)
if (err == OK):
export_imports(output_dir)
else:
print("Error: failed to extract PAK file, not exporting assets")
else:
print("Error: failed to load " + exe_file)

if input_file != "":
main = GDRECLIMain.new()
recovery(input_file, output_dir, enc_key)
main.close_log()
get_tree().quit()
else:
Expand Down
14 changes: 14 additions & 0 deletions utility/gdre_cli_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ Error GDRECLIMain::open_log(const String &path) {
}

Error GDRECLIMain::close_log() {
String path = GDRESettings::get_singleton()->get_log_file_path();
if (path == "") {
return OK;
}
GDRESettings::get_singleton()->close_log_file();
print_line("Log file written to " + path);
print_line("Please include this file when reporting an issue!");
return GDRESettings::get_singleton()->close_log_file();
}

Expand All @@ -15,8 +22,15 @@ String GDRECLIMain::get_cli_abs_path(const String &path) {
String exec_path = GDRESettings::get_singleton()->get_exec_dir();
return exec_path.plus_file(path).replace("\\", "/");
}
Error GDRECLIMain::copy_dir(const String &src_path, const String dst_path) {
Ref<DirAccess> f = DirAccess::open(src_path);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Can't open " + src_path);
Error err = f->copy_dir(src_path, dst_path);
return err;
}

void GDRECLIMain::_bind_methods() {
ClassDB::bind_method(D_METHOD("copy_dir"), &GDRECLIMain::copy_dir);
ClassDB::bind_method(D_METHOD("open_log"), &GDRECLIMain::open_log);
ClassDB::bind_method(D_METHOD("close_log"), &GDRECLIMain::close_log);
ClassDB::bind_method(D_METHOD("get_cli_abs_path"), &GDRECLIMain::get_cli_abs_path);
Expand Down
2 changes: 2 additions & 0 deletions utility/gdre_cli_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class GDRECLIMain : public RefCounted {
Error open_log(const String &path);
Error close_log();
String get_cli_abs_path(const String &path);
Error copy_dir(const String &src_path, const String dst_path);

GDRECLIMain() {
gdres_singleton = memnew(GDRESettings);
}
Expand Down
14 changes: 10 additions & 4 deletions utility/gdre_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,10 @@ void GDRELogger::logv(const char *p_format, va_list p_list, bool p_err) {
}
}

String GDRESettings::get_log_file_path() {
return logger->get_path();
}

Error GDRESettings::open_log_file(const String &output_dir) {
String logfile = output_dir.plus_file("gdre_export.log");
Error err = logger->open_file(logfile);
Expand All @@ -423,24 +427,26 @@ Error GDRESettings::close_log_file() {
return OK;
}

Error GDRELogger::open_file(const String &base_path) {
Error GDRELogger::open_file(const String &p_base_path) {
if (file.is_valid()) {
return ERR_ALREADY_IN_USE;
}
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_USERDATA);
if (da.is_valid()) {
da->make_dir_recursive(base_path.get_base_dir());
da->make_dir_recursive(p_base_path.get_base_dir());
}
Error err;
file = FileAccess::open(base_path, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(file.is_null(), err, "Failed to open log file " + base_path + " for writing.");
file = FileAccess::open(p_base_path, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(file.is_null(), err, "Failed to open log file " + p_base_path + " for writing.");
base_path = p_base_path;
return OK;
}

void GDRELogger::close_file() {
if (file.is_valid()) {
file->flush();
file = Ref<FileAccess>();
base_path = "";
}
}

Expand Down
6 changes: 4 additions & 2 deletions utility/gdre_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ class GDREOS : public T {
};

class GDRELogger : public Logger {
String base_path;

Ref<FileAccess> file;
String base_path;

public:
String get_path() { return base_path; };
GDRELogger();
Error open_file(const String &p_base_path);
void close_file();
Expand Down Expand Up @@ -134,6 +134,8 @@ class GDRESettings : public Object {
String get_res_path(const String &p_path, const String &resource_dir = "");
bool has_res_path(const String &p_path, const String &resource_dir = "");
Error open_log_file(const String &output_dir);
String get_log_file_path();

bool is_fs_path(const String &p_path);
Error close_log_file();
String get_cwd() { return GDRESettings::_get_cwd(); };
Expand Down
Loading

0 comments on commit 8e74c1b

Please sign in to comment.