Created
January 12, 2024 10:07
-
-
Save nikitalita/39d44e484196bde15b03b546bc0ebff9 to your computer and use it in GitHub Desktop.
Dumps the classdb from a running Godot 4.x game
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
extends Node | |
# for Godot 4.x | |
# There are many ways you can inject this into a project, but I've found the easiest way to do it | |
# is to put this into a project with a blank scene with this attached, export it as a PCK, and then copy it to the game | |
# directory and rename it to the name of the main game pck. | |
func get_method_flags(flags): | |
var arr = [] | |
if flags & METHOD_FLAG_NORMAL: | |
arr.push_back("NORMAL") | |
if flags & METHOD_FLAG_EDITOR: | |
arr.push_back("EDITOR") | |
if flags & METHOD_FLAG_CONST: | |
arr.push_back("CONST") | |
if flags & METHOD_FLAG_VIRTUAL: | |
arr.push_back("VIRTUAL") | |
if flags & METHOD_FLAG_VARARG: | |
arr.push_back("VARARG") | |
if flags & METHOD_FLAG_STATIC: | |
arr.push_back("STATIC") | |
if flags & METHOD_FLAG_OBJECT_CORE: | |
arr.push_back("OBJECT_CORE") | |
arr.sort() | |
return arr | |
func get_type(typenum): | |
match typenum: | |
TYPE_NIL: | |
return "NIL" | |
TYPE_BOOL: | |
return "BOOL" | |
TYPE_INT: | |
return "INT" | |
TYPE_FLOAT: | |
return "FLOAT" | |
TYPE_STRING: | |
return "STRING" | |
TYPE_VECTOR2: | |
return "VECTOR2" | |
TYPE_VECTOR2I: | |
return "VECTOR2I" | |
TYPE_RECT2: | |
return "RECT2" | |
TYPE_RECT2I: | |
return "RECT2I" | |
TYPE_VECTOR3: | |
return "VECTOR3" | |
TYPE_VECTOR3I: | |
return "VECTOR3I" | |
TYPE_TRANSFORM2D: | |
return "TRANSFORM2D" | |
TYPE_VECTOR4: | |
return "VECTOR4" | |
TYPE_VECTOR4I: | |
return "VECTOR4I" | |
TYPE_PLANE: | |
return "PLANE" | |
TYPE_QUATERNION: | |
return "QUATERNION" | |
TYPE_AABB: | |
return "AABB" | |
TYPE_BASIS: | |
return "BASIS" | |
TYPE_TRANSFORM3D: | |
return "TRANSFORM3D" | |
TYPE_PROJECTION: | |
return "PROJECTION" | |
TYPE_COLOR: | |
return "COLOR" | |
TYPE_STRING_NAME: | |
return "STRING_NAME" | |
TYPE_NODE_PATH: | |
return "NODE_PATH" | |
TYPE_RID: | |
return "RID" | |
TYPE_OBJECT: | |
return "OBJECT" | |
TYPE_CALLABLE: | |
return "CALLABLE" | |
TYPE_SIGNAL: | |
return "SIGNAL" | |
TYPE_DICTIONARY: | |
return "DICTIONARY" | |
TYPE_ARRAY: | |
return "ARRAY" | |
TYPE_PACKED_BYTE_ARRAY: | |
return "PACKED_BYTE_ARRAY" | |
TYPE_PACKED_INT32_ARRAY: | |
return "PACKED_INT32_ARRAY" | |
TYPE_PACKED_INT64_ARRAY: | |
return "PACKED_INT64_ARRAY" | |
TYPE_PACKED_FLOAT32_ARRAY: | |
return "PACKED_FLOAT32_ARRAY" | |
TYPE_PACKED_FLOAT64_ARRAY: | |
return "PACKED_FLOAT64_ARRAY" | |
TYPE_PACKED_STRING_ARRAY: | |
return "PACKED_STRING_ARRAY" | |
TYPE_PACKED_VECTOR2_ARRAY: | |
return "PACKED_VECTOR2_ARRAY" | |
TYPE_PACKED_VECTOR3_ARRAY: | |
return "PACKED_VECTOR3_ARRAY" | |
TYPE_PACKED_COLOR_ARRAY: | |
return "PACKED_COLOR_ARRAY" | |
return "MAX" | |
func get_hints(hint): | |
match hint: | |
PROPERTY_HINT_NONE: | |
return "NONE" | |
PROPERTY_HINT_RANGE: | |
return "RANGE" | |
PROPERTY_HINT_ENUM: | |
return "ENUM" | |
PROPERTY_HINT_ENUM_SUGGESTION: | |
return "ENUM_SUGGESTION" | |
PROPERTY_HINT_EXP_EASING: | |
return "EXP_EASING" | |
PROPERTY_HINT_LINK: | |
return "LINK" | |
PROPERTY_HINT_FLAGS: | |
return "FLAGS" | |
PROPERTY_HINT_LAYERS_2D_RENDER: | |
return "LAYERS_2D_RENDER" | |
PROPERTY_HINT_LAYERS_2D_PHYSICS: | |
return "LAYERS_2D_PHYSICS" | |
PROPERTY_HINT_LAYERS_2D_NAVIGATION: | |
return "LAYERS_2D_NAVIGATION" | |
PROPERTY_HINT_LAYERS_3D_RENDER: | |
return "LAYERS_3D_RENDER" | |
PROPERTY_HINT_LAYERS_3D_PHYSICS: | |
return "LAYERS_3D_PHYSICS" | |
PROPERTY_HINT_LAYERS_3D_NAVIGATION: | |
return "LAYERS_3D_NAVIGATION" | |
PROPERTY_HINT_FILE: | |
return "FILE" | |
PROPERTY_HINT_DIR: | |
return "DIR" | |
PROPERTY_HINT_GLOBAL_FILE: | |
return "GLOBAL_FILE" | |
PROPERTY_HINT_GLOBAL_DIR: | |
return "GLOBAL_DIR" | |
PROPERTY_HINT_RESOURCE_TYPE: | |
return "RESOURCE_TYPE" | |
PROPERTY_HINT_MULTILINE_TEXT: | |
return "MULTILINE_TEXT" | |
PROPERTY_HINT_EXPRESSION: | |
return "EXPRESSION" | |
PROPERTY_HINT_PLACEHOLDER_TEXT: | |
return "PLACEHOLDER_TEXT" | |
PROPERTY_HINT_COLOR_NO_ALPHA: | |
return "COLOR_NO_ALPHA" | |
PROPERTY_HINT_OBJECT_ID: | |
return "OBJECT_ID" | |
PROPERTY_HINT_TYPE_STRING: | |
return "TYPE_STRING" | |
PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: | |
return "NODE_PATH_TO_EDITED_NODE" | |
PROPERTY_HINT_OBJECT_TOO_BIG: | |
return "OBJECT_TOO_BIG" | |
PROPERTY_HINT_NODE_PATH_VALID_TYPES: | |
return "NODE_PATH_VALID_TYPES" | |
PROPERTY_HINT_SAVE_FILE: | |
return "SAVE_FILE" | |
PROPERTY_HINT_GLOBAL_SAVE_FILE: | |
return "GLOBAL_SAVE_FILE" | |
PROPERTY_HINT_INT_IS_OBJECTID: | |
return "INT_IS_OBJECTID" | |
PROPERTY_HINT_INT_IS_POINTER: | |
return "INT_IS_POINTER" | |
PROPERTY_HINT_ARRAY_TYPE: | |
return "ARRAY_TYPE" | |
PROPERTY_HINT_LOCALE_ID: | |
return "LOCALE_ID" | |
PROPERTY_HINT_LOCALIZABLE_STRING: | |
return "LOCALIZABLE_STRING" | |
PROPERTY_HINT_NODE_TYPE: | |
return "NODE_TYPE" | |
PROPERTY_HINT_HIDE_QUATERNION_EDIT: | |
return "HIDE_QUATERNION_EDIT" | |
PROPERTY_HINT_PASSWORD: | |
return "PASSWORD" | |
PROPERTY_HINT_LAYERS_AVOIDANCE: | |
return "LAYERS_AVOIDANCE" | |
PROPERTY_HINT_MAX: | |
return "MAX" | |
return "<ERROR>" | |
func get_usage(usage): | |
var arr = [] | |
if usage & PROPERTY_USAGE_STORAGE: | |
arr.push_back("STORAGE") | |
if usage & PROPERTY_USAGE_EDITOR: | |
arr.push_back("EDITOR") | |
if usage & PROPERTY_USAGE_INTERNAL: | |
arr.push_back("INTERNAL") | |
if usage & PROPERTY_USAGE_CHECKABLE: | |
arr.push_back("CHECKABLE") | |
if usage & PROPERTY_USAGE_CHECKED: | |
arr.push_back("CHECKED") | |
if usage & PROPERTY_USAGE_GROUP: | |
arr.push_back("GROUP") | |
if usage & PROPERTY_USAGE_CATEGORY: | |
arr.push_back("CATEGORY") | |
if usage & PROPERTY_USAGE_SUBGROUP: | |
arr.push_back("SUBGROUP") | |
if usage & PROPERTY_USAGE_CLASS_IS_BITFIELD: | |
arr.push_back("CLASS_IS_BITFIELD") | |
if usage & PROPERTY_USAGE_NO_INSTANCE_STATE: | |
arr.push_back("NO_INSTANCE_STATE") | |
if usage & PROPERTY_USAGE_RESTART_IF_CHANGED: | |
arr.push_back("RESTART_IF_CHANGED") | |
if usage & PROPERTY_USAGE_SCRIPT_VARIABLE: | |
arr.push_back("SCRIPT_VARIABLE") | |
if usage & PROPERTY_USAGE_STORE_IF_NULL: | |
arr.push_back("STORE_IF_NULL") | |
if usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED: | |
arr.push_back("UPDATE_ALL_IF_MODIFIED") | |
if usage & PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE: | |
arr.push_back("SCRIPT_DEFAULT_VALUE") | |
if usage & PROPERTY_USAGE_CLASS_IS_ENUM: | |
arr.push_back("CLASS_IS_ENUM") | |
if usage & PROPERTY_USAGE_NIL_IS_VARIANT: | |
arr.push_back("NIL_IS_VARIANT") | |
if usage & PROPERTY_USAGE_ARRAY: | |
arr.push_back("ARRAY") | |
if usage & PROPERTY_USAGE_ALWAYS_DUPLICATE: | |
arr.push_back("ALWAYS_DUPLICATE") | |
if usage & PROPERTY_USAGE_NEVER_DUPLICATE: | |
arr.push_back("NEVER_DUPLICATE") | |
if usage & PROPERTY_USAGE_HIGH_END_GFX: | |
arr.push_back("HIGH_END_GFX") | |
if usage & PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT: | |
arr.push_back("NODE_PATH_FROM_SCENE_ROOT") | |
if usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT: | |
arr.push_back("RESOURCE_NOT_PERSISTENT") | |
if usage & PROPERTY_USAGE_KEYING_INCREMENTS: | |
arr.push_back("KEYING_INCREMENTS") | |
if usage & PROPERTY_USAGE_DEFERRED_SET_RESOURCE: | |
arr.push_back("DEFERRED_SET_RESOURCE") | |
if usage & PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT: | |
arr.push_back("EDITOR_INSTANTIATE_OBJECT") | |
if usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING: | |
arr.push_back("EDITOR_BASIC_SETTING") | |
if usage & PROPERTY_USAGE_READ_ONLY: | |
arr.push_back("READ_ONLY") | |
if usage & PROPERTY_USAGE_SECRET: | |
arr.push_back("SECRET") | |
arr.sort() | |
return arr | |
class DictSorter: | |
static func sort(a,b): | |
if a["name"] < b["name"]: | |
return true | |
return false | |
func parse_prop(prop): | |
prop["hint"] = get_hints(prop["hint"]) | |
prop["type"] = get_type(prop["type"]) | |
prop["usage"] = get_usage(prop["usage"]) | |
prop.erase("hint_string") # never of any value, just dupes "class_name" field | |
return prop | |
func parse_props(props): | |
var arr = [] | |
for prop in props: | |
arr.push_back(parse_prop(prop)) | |
arr.sort_custom(DictSorter.sort) | |
return arr | |
func parse_methods(methods): | |
var arr = [] | |
for method in methods: | |
method["args"] = parse_props(method["args"]) | |
method["flags"] = get_method_flags(method["flags"]) | |
method["return"] = parse_prop(method["return"]) | |
method.erase("id") # useless for diffing | |
arr.push_back(method) | |
arr.sort_custom(DictSorter.sort) | |
return arr | |
func get_pretty_json_string(obj, indent_increment : int = 0): | |
var pretty : String = "" | |
var indent : String = " " | |
var quote : String = "\"" | |
var newline : String = "\n" | |
var colon : String = ":" | |
var openbracket : String = "{" | |
var closebracket : String = "}" | |
var space : String = " " | |
var comma : String = "," | |
var arr_open_bracket = "[ " | |
var arr_close_bracket = " ]" | |
var white_space = "" | |
for i in range (indent_increment): | |
white_space += indent | |
var _is_not_last : bool = true | |
var element_counter : int = 0 | |
match typeof(obj): | |
TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY, TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY, TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_COLOR_ARRAY: | |
if len(obj) == 0: | |
return "[]" | |
comma = ", " | |
var _sub_pretty = "" | |
var has_dict = false | |
var total_str_len = 0 | |
var stringified_arr: PackedStringArray = [] | |
for element in obj: | |
if typeof(element) == TYPE_DICTIONARY: | |
has_dict = true | |
var thing = get_pretty_json_string(element, indent_increment + 1) | |
stringified_arr.push_back(thing) | |
total_str_len += len(thing) + (len(stringified_arr) * 2) | |
var arr_join_string = ", " | |
if total_str_len > (40 - (indent_increment * len(indent))) or has_dict: | |
arr_join_string = ",\n" + white_space + indent | |
arr_open_bracket = "[\n" + white_space + indent | |
arr_close_bracket = "\n" + white_space + "]" | |
pretty += arr_open_bracket + arr_join_string.join(stringified_arr) + arr_close_bracket | |
TYPE_BOOL: | |
pretty += str(obj).to_lower() | |
TYPE_NIL: | |
pretty += "null" | |
TYPE_INT, TYPE_FLOAT: | |
pretty += str(obj) | |
TYPE_DICTIONARY: | |
pretty += openbracket | |
for element in obj.keys(): | |
element_counter += 1 | |
if element == "default_args": | |
pass | |
pretty += newline | |
for i in range (indent_increment + 1): | |
pretty += indent | |
pretty += quote + str(element).json_escape() + quote + space + colon + space | |
pretty += self.get_pretty_json_string(obj.get(element), indent_increment + 1) | |
if (not len(obj.keys()) <= 1) and element_counter < len(obj.keys()): | |
pretty += comma | |
pretty += newline | |
for i in range (indent_increment): | |
pretty += indent | |
pretty += closebracket | |
TYPE_STRING, TYPE_STRING_NAME: | |
pretty = quote + str(obj).json_escape() + quote | |
_: | |
if not obj: | |
pretty = "null" | |
else: | |
pretty = quote + str(obj).json_escape() + quote | |
return pretty | |
func dump_classdb(): | |
var _list = Array(ClassDB.get_class_list()) | |
_list.sort() | |
var full_dump_obj: Dictionary = {} | |
var members_only_obj : Dictionary = {} | |
for cls in _list: | |
var full_dict = Dictionary() | |
var name_dict = Dictionary() | |
#dict["category"] = ClassDB.class_get_category(cls) # not present in non-debug builds | |
#dict["class_name"] = cls | |
full_dict["parent"] = ClassDB.get_parent_class(cls) | |
name_dict["parent"] = ClassDB.get_parent_class(cls) | |
var method_list = ClassDB.class_get_method_list(cls, true) | |
method_list.sort_custom(DictSorter.sort) | |
var method_names: PackedStringArray = [] | |
for method in method_list: | |
method_names.push_back(method["name"]) | |
name_dict["methods"] = method_names | |
full_dict["methods"] = parse_methods(method_list) | |
var signal_list = ClassDB.class_get_signal_list(cls, true) | |
signal_list.sort_custom(DictSorter.sort) | |
var signal_names: PackedStringArray = [] | |
for _signl in signal_list: | |
signal_names.push_back(_signl["name"]) | |
name_dict["signals"] = signal_names | |
full_dict["signals"] = parse_methods(signal_list) | |
var props_list = ClassDB.class_get_property_list(cls, true) | |
props_list.sort_custom(DictSorter.sort) | |
var props_names: PackedStringArray = [] | |
for prop in props_list: | |
props_names.push_back(prop["name"]) | |
name_dict["props"] = props_names | |
full_dict["props"] = parse_props(props_list) | |
var enums = Array(ClassDB.class_get_integer_constant_list(cls, true)) | |
enums.sort() | |
full_dict["enums"] = enums | |
# we don't bother with the enums in the minimal dump | |
members_only_obj[cls] = name_dict | |
full_dump_obj[cls] = full_dict | |
var version = Engine.get_version_info()["string"].replace(" ","_").replace("(","").replace(")","") | |
#var text = JSON.print(full_dump_obj, " ") | |
var text = get_pretty_json_string(full_dump_obj, 1) | |
var class_text = get_pretty_json_string(_list, 1) | |
var cm_text = get_pretty_json_string(members_only_obj, 1) | |
# Note: If writing to the filesystem is disabled in the Godot binary this is being tested against, | |
# try using `DisplayServer.clipboard_set(text)` instead | |
var game_name : String = ProjectSettings.get_setting("application/config/name", "") | |
var suffix = version + "_" + game_name + ".json" | |
var f = FileAccess.open("classdb_full_dump_" + suffix, FileAccess.WRITE) | |
f.store_string(text) | |
f.close() | |
f = FileAccess.open("classdb_class_names_only_" + suffix, FileAccess.WRITE) | |
f.store_string(class_text) | |
f.close() | |
f = FileAccess.open("classdb_class_members_only_" + suffix, FileAccess.WRITE) | |
f.store_string(cm_text) | |
f.close() | |
func _ready(): | |
dump_classdb() | |
get_tree().quit() # exit after dumping classdb | |
func _process(_delta): | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment