Skip to content

Instantly share code, notes, and snippets.

@nikitalita
Created January 12, 2024 10:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikitalita/39d44e484196bde15b03b546bc0ebff9 to your computer and use it in GitHub Desktop.
Save nikitalita/39d44e484196bde15b03b546bc0ebff9 to your computer and use it in GitHub Desktop.
Dumps the classdb from a running Godot 4.x game
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