diff --git a/.github/workflows/all_builds.yml b/.github/workflows/all_builds.yml index 7cc6336d6..e4e2f2a06 100644 --- a/.github/workflows/all_builds.yml +++ b/.github/workflows/all_builds.yml @@ -28,8 +28,8 @@ env: # TODO: change this back to godotengine/godot and target master when #109685 and #109475 are merged GODOT_REPOSITORY: nikitalita/godot # Change the README too - GODOT_MAIN_SYNC_REF: gdre-wb-babc272d44e - SCONSFLAGS: verbose=yes warnings=all werror=no module_text_server_fb_enabled=yes minizip=yes deprecated=yes + GODOT_MAIN_SYNC_REF: gdre-wb-a8643700ce8 + SCONSFLAGS: verbose=yes warnings=all werror=no module_text_server_fb_enabled=yes minizip=yes deprecated=yes angle=yes accesskit=no SCONSFLAGS_TEMPLATE: disable_path_overrides=no no_editor_splash=yes module_camera_enabled=no module_mobile_vr_enabled=no module_upnp_enabled=no module_websocket_enabled=no module_csg_enabled=yes module_gridmap_enabled=yes use_static_cpp=yes builtin_freetype=yes builtin_libpng=yes builtin_zlib=yes builtin_libwebp=yes builtin_libvorbis=yes builtin_libogg=yes disable_3d=no SCONS_CACHE_MSVC_CONFIG: true @@ -137,6 +137,11 @@ jobs: bin: ./bin/godot.macos.editor.arm64 steps: + - name: Set git to use LF + if: matrix.platform == 'windows' + run: | + git config --global core.autocrlf false + git config --global core.eol lf - name: checkout-godot uses: actions/checkout@v4 with: @@ -189,10 +194,36 @@ jobs: - name: Setup python and scons uses: ./.github/actions/godot-deps + - name: Download pre-built ANGLE + if: matrix.platform == 'macos' || matrix.platform == 'windows' + shell: sh + id: angle-sdk + run: | + if python ./misc/scripts/install_angle.py; then + echo "ANGLE_ENABLED=yes" >> "$GITHUB_OUTPUT" + else + echo "::warning::ANGLE SDK installation failed, building without ANGLE support." + echo "ANGLE_ENABLED=no" >> "$GITHUB_OUTPUT" + fi + + # TODO: Enable this when we figure out how to resolve linking issues with AccessKit; getting duplicate symbols for `_rust_eh_personality`. + # - name: Download pre-built AccessKit + # shell: sh + # id: accesskit-sdk + # if: matrix.platform == 'macos' || matrix.platform == 'windows' || matrix.platform == 'linux' + # run: | + # if python ./misc/scripts/install_accesskit.py; then + # echo "ACCESSKIT_ENABLED=yes" >> "$GITHUB_OUTPUT" + # else + # echo "::warning::AccessKit SDK installation failed, building without AccessKit support." + # echo "ACCESSKIT_ENABLED=no" >> "$GITHUB_OUTPUT" + # fi + - name: Setup Vulkan SDK if: matrix.platform == 'macos' run: | sh misc/scripts/install_vulkan_sdk_macos.sh + - name: Download Direct3D 12 SDK components if: matrix.platform == 'windows' shell: sh @@ -205,6 +236,7 @@ jobs: echo "D3D12_ENABLED=no" >> "$GITHUB_OUTPUT" fi continue-on-error: true + - name: Show targets shell: sh run: | @@ -228,16 +260,7 @@ jobs: - name: Download pre-built Android Swappy Frame Pacing Library if: matrix.platform == 'android' - uses: dsaltares/fetch-gh-release-asset@1.1.2 - with: - repo: godotengine/godot-swappy - version: tags/from-source-2025-01-31 - file: godot-swappy.7z - target: swappy/godot-swappy.7z - - - name: Extract pre-built Android Swappy Frame Pacing Library - if: matrix.platform == 'android' - run: 7za x -y swappy/godot-swappy.7z -o${{github.workspace}}/thirdparty/swappy-frame-pacing + run: python ./misc/scripts/install_swappy_android.py - name: Compile Editor if: matrix.target == 'editor' @@ -462,8 +485,9 @@ jobs: if: matrix.platform == 'android' && github.event_name != 'pull_request' run: | set -Eeuo pipefail - if [ -z "${{ secrets.ANDROID_RELEASE_KEYSTORE }}" ]; then - echo "ANDROID_RELEASE_KEYSTORE is not set" + if [ -z "${{ secrets.ANDROID_RELEASE_KEYSTORE }}" ] || [ -z "${{ secrets.ANDROID_KEY_ALIAS }}" ] || [ -z "${{ secrets.ANDROID_KEY_PASSWORD }}" ]; then + echo "One or more Android signing secrets are missing (ANDROID_RELEASE_KEYSTORE, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD)" + echo "Android release build will be signed with a generated keystore" exit 1 fi echo "${{ secrets.ANDROID_RELEASE_KEYSTORE }}" > release.keystore.b64 @@ -471,6 +495,7 @@ jobs: echo "GODOT_ANDROID_KEYSTORE_RELEASE_PATH=$HOME/release.keystore" >> "$GITHUB_ENV" echo "GODOT_ANDROID_KEYSTORE_RELEASE_USER=${{ secrets.ANDROID_KEY_ALIAS }}" >> "$GITHUB_ENV" echo "GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}" >> "$GITHUB_ENV" + continue-on-error: true - name: Export standalone GDRE Tools shell: pwsh diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 9a352f2ee..d81bc3e6f 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: static-check-clang-format: - runs-on: "ubuntu-24.04" + runs-on: "ubuntu-26.04" name: Static checks (clang-format) steps: - name: checkout-gdsdecomp @@ -28,7 +28,7 @@ jobs: - name: Install Linux deps run: | sudo apt-get update - sudo apt-get install clang-format-18 - sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-18 100 + sudo apt-get install clang-format-21 + sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-21 100 - name: check clang_format run: bash ./.scripts/clang_format.sh diff --git a/.scripts/rebase_godot.sh b/.scripts/rebase_godot.sh index e74d0f74a..b5782a529 100755 --- a/.scripts/rebase_godot.sh +++ b/.scripts/rebase_godot.sh @@ -31,10 +31,9 @@ BRANCHES_TO_MERGE=( material-fix-deprecated-param fix-pack-error convert-3.x-escn - fix-svg fix-compat-array-shapes fix-diraccess-windows - gltf-fix-value-track-interpolation + fix-cli-parser ) # set fail on error @@ -55,7 +54,7 @@ else sed_in_place() { sed -i "$@"; } fi -git push nikitalita $NEW_BRANCH_NAME --set-upstream +# git push nikitalita $NEW_BRANCH_NAME --set-upstream # change the branch name in .github/workflows/all_builds.yml and the README.md sed_in_place "s/GODOT_MAIN_SYNC_REF: .*/GODOT_MAIN_SYNC_REF: $NEW_BRANCH_NAME/" "$GDRE_PATH/.github/workflows/all_builds.yml" diff --git a/README.md b/README.md index 8caba56a7..d472a2900 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,10 @@ Main commands: --bin-to-txt= Convert binary scene or resource files to text-based format (can be repeated) --patch-translations== Patch translations with the specified CSV file and source path (e.g. "/path/to/translation.csv=res://translations/translation.csv") (can be repeated) ---gdre-help Print the help message and exit ---gdre-version Print the version of GDRE tools and exit +--godot-version Print the version of Godot engine and exit +--godot-help Print the help message of Godot engine and exit +--help, --gdre-help Print this help message and exit +--version, --gdre-version Print this version of GDRE tools and exit Recover/Extract Options: @@ -141,15 +143,19 @@ Support has yet to be implemented for converting the following resources: Clone this repository into Godot's `modules` subfolder as `gdsdecomp`. Rebuild Godot engine as described in https://docs.godotengine.org/en/latest/development/compiling/index.html. -You will also need [rustup](https://rustup.rs) and [dotnet 9 sdk](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). +You will also need [rustup](https://rustup.rs) and [dotnet 10 sdk](https://dotnet.microsoft.com/en-us/download/dotnet/10.0). For ease of bootstrapping development, we have included launch, build, and settings templates for vscode in the .vscode directory. Once you have read the instructions for compiling Godot above and set up your build environment: put these in the .vscode folder in the Godot directory (not gdsdecomp), remove the ".template" from each, and launch vscode from the Godot directory. -Note: Make sure to build the editor build first, and to launch the editor to edit the project in the `standalone` directory at least once so that resources are imported before running. +Make sure to build the editor build first, and to launch the editor to edit the project in the `standalone` directory at least once so that resources are imported before running. + +### Note: + +During SCons configure, the module auto-applies the patches under `modules/gdsdecomp/patches/` to Godot core files (currently just `main/main.cpp`, to hook `gdre::modify_cli_args` into CLI parsing). The application is idempotent: re-running `scons` is a no-op once patched. If you remove the module or want to restore the originals, revert the affected files manually with `git checkout -- main/main.cpp`. ### Requirements -[Our fork of godot](https://github.com/nikitalita/godot) @ branch `gdre-wb-babc272d44e` +[Our fork of godot](https://github.com/nikitalita/godot) @ branch `gdre-wb-a8643700ce8` - Support for building on 3.x has been dropped and no new features are being pushed - Godot RE Tools still retains the ability to decompile 3.x and 2.x projects, however. diff --git a/SCsub b/SCsub index 0a9006809..67fe10eb3 100644 --- a/SCsub +++ b/SCsub @@ -34,6 +34,18 @@ VTRACER_DIR = get_vtracer_dir(MODULE_DIR) VTRACER_BUILD_DIR = get_vtracer_build_dir(MODULE_DIR) GODOT_MONO_DECOMP_DIR = get_godot_mono_decomp_dir(MODULE_DIR) +INCLUDE_DIRS = [ + VTRACER_INCLUDE_DIR, + GODOT_MONO_DECOMP_INCLUDE_DIR, + MODULE_INCLUDE_DIR, + MMP3_THIRDPARTY_DIR, + LIBOGG_THIRDPARTY_DIR, + LIBVORBIS_THIRDPARTY_DIR, + LIBTHEORA_THIRDPARTY_DIR, + MBEDTLS_THIRDPARTY_DIR, + UNSIGNED_HASH_INCLUDE_DIR +] + # TODO: Keep static branch intact for future use. Runtime/dotnet issues still block rollout. mono_native_lib_type = "Shared" is_using_clang = env["CXX"].lower().endswith("clang++") @@ -57,26 +69,12 @@ if env["tests"]: env_gdsdecomp.Append(CPPDEFINES=["TESTS_ENABLED"]) append_cpppaths( env, - [ - VTRACER_INCLUDE_DIR, - GODOT_MONO_DECOMP_INCLUDE_DIR, - MODULE_INCLUDE_DIR, - MMP3_THIRDPARTY_DIR, - LIBOGG_THIRDPARTY_DIR, - LIBVORBIS_THIRDPARTY_DIR, - ], + INCLUDE_DIRS, ) append_cpppaths( env_gdsdecomp, - [ - VTRACER_INCLUDE_DIR, - GODOT_MONO_DECOMP_INCLUDE_DIR, - MODULE_INCLUDE_DIR, - THORSVG_THIRDPARTY_DIR, - LIBTHEORA_THIRDPARTY_DIR, - MBEDTLS_THIRDPARTY_DIR, - ], + INCLUDE_DIRS, ) if env["disable_exceptions"]: @@ -115,11 +113,14 @@ common_sources = [ "crypto/*.cpp", "exporters/*.cpp", "gui/*.cpp", + "main/*.cpp", "plugin_manager/*.cpp", "utility/*.cpp", "external/tga/*.cpp", "external/tinygltf/tiny_gltf.cc", "module_etc_decompress/*.cpp", + f'{UNSIGNED_HASH_SRC_DIR}/*.cpp', + f'{UNSIGNED_HASH_SRC_DIR}/*.c', ] add_source_groups(env_gdsdecomp, module_obj, common_sources) if env["target"] == "editor": diff --git a/build/patches.py b/build/patches.py new file mode 100644 index 000000000..c54b7fafc --- /dev/null +++ b/build/patches.py @@ -0,0 +1,54 @@ +import os +import subprocess + +from SCons.Errors import UserError + + +CORE_PATCHES = [ + { + "patch": "modules/gdsdecomp/patches/main-cli-parse-inject.patch", + "target": "main/main.cpp", + "marker": "gdre::modify_cli_args", + }, +] + + +def _is_applied(repo_root, target, marker): + target_path = os.path.join(repo_root, target) + try: + with open(target_path, "r", encoding="utf-8") as f: + return marker in f.read() + except OSError as e: + raise UserError(f"gdsdecomp: cannot read {target} to verify patch state: {e}") + + +def _git_apply(repo_root, patch_rel_path): + patch_abs_path = os.path.join(repo_root, patch_rel_path) + if not os.path.isfile(patch_abs_path): + raise UserError(f"gdsdecomp: patch file not found at {patch_rel_path}") + result = subprocess.run( + ["git", "-C", repo_root, "apply", "--whitespace=nowarn", patch_rel_path], + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise UserError( + "gdsdecomp: failed to apply " + + patch_rel_path + + "\n" + + (result.stderr or result.stdout or "") + ) + + +def apply_core_patches(env): + repo_root = env.Dir("#").abspath + for entry in CORE_PATCHES: + patch_rel = entry["patch"] + target = entry["target"] + marker = entry["marker"] + patch_name = os.path.basename(patch_rel) + if _is_applied(repo_root, target, marker): + print(f"gdsdecomp: {patch_name} already applied to {target}, skipping") + continue + _git_apply(repo_root, patch_rel) + print(f"gdsdecomp: applied {patch_name} to {target}") diff --git a/build/paths.py b/build/paths.py index cdba94405..c342e5075 100644 --- a/build/paths.py +++ b/build/paths.py @@ -10,6 +10,9 @@ LIBVORBIS_THIRDPARTY_DIR = "#thirdparty/libvorbis/" WEBP_THIRDPARTY_DIR = "#thirdparty/libwebp/" THORSVG_THIRDPARTY_DIR = "#thirdparty/thorsvg/" +UNSIGNED_HASH_DIR = "#modules/gdsdecomp/external/unsigned-hash-cpp" +UNSIGNED_HASH_INCLUDE_DIR = f'{UNSIGNED_HASH_DIR}/include' +UNSIGNED_HASH_SRC_DIR = f'{UNSIGNED_HASH_DIR}/src' MODULE_INCLUDE_DIR = "#modules/gdsdecomp/" VTRACER_INCLUDE_DIR = "#modules/gdsdecomp/external/vtracer/include" GODOT_MONO_DECOMP_INCLUDE_DIR = "#modules/gdsdecomp/godot-mono-decomp/GodotMonoDecompNativeAOT/include" diff --git a/bytecode/bytecode_2b64f73.cpp b/bytecode/bytecode_2b64f73.cpp new file mode 100644 index 000000000..c05d8307f --- /dev/null +++ b/bytecode/bytecode_2b64f73.cpp @@ -0,0 +1,360 @@ +// This file is automatically generated by `bytecode_generator.py` +// Do not edit this file directly, as it will be overwritten. +// Instead, edit `bytecode_generator.py` and run it to generate this file. + +// clang-format off +#include "bytecode_2b64f73.h" + +static const Pair> funcs[] = { + { "sin", Pair(1, 1) }, + { "cos", Pair(1, 1) }, + { "tan", Pair(1, 1) }, + { "sinh", Pair(1, 1) }, + { "cosh", Pair(1, 1) }, + { "tanh", Pair(1, 1) }, + { "asin", Pair(1, 1) }, + { "acos", Pair(1, 1) }, + { "atan", Pair(1, 1) }, + { "atan2", Pair(2, 2) }, + { "sqrt", Pair(1, 1) }, + { "fmod", Pair(2, 2) }, + { "fposmod", Pair(2, 2) }, + { "floor", Pair(1, 1) }, + { "ceil", Pair(1, 1) }, + { "round", Pair(1, 1) }, + { "abs", Pair(1, 1) }, + { "sign", Pair(1, 1) }, + { "pow", Pair(2, 2) }, + { "log", Pair(1, 1) }, + { "exp", Pair(1, 1) }, + { "is_nan", Pair(1, 1) }, + { "is_inf", Pair(1, 1) }, + { "ease", Pair(2, 2) }, + { "decimals", Pair(1, 1) }, + { "stepify", Pair(2, 2) }, + { "lerp", Pair(3, 3) }, + { "dectime", Pair(3, 3) }, + { "randomize", Pair(0, 0) }, + { "randi", Pair(0, 0) }, + { "randf", Pair(0, 0) }, + { "rand_range", Pair(2, 2) }, + { "seed", Pair(1, 1) }, + { "rand_seed", Pair(1, 1) }, + { "deg2rad", Pair(1, 1) }, + { "rad2deg", Pair(1, 1) }, + { "linear2db", Pair(1, 1) }, + { "db2linear", Pair(1, 1) }, + { "max", Pair(2, 2) }, + { "min", Pair(2, 2) }, + { "clamp", Pair(3, 3) }, + { "nearest_po2", Pair(1, 1) }, + { "weakref", Pair(1, 1) }, + { "funcref", Pair(2, 2) }, + { "convert", Pair(2, 2) }, + { "typeof", Pair(1, 1) }, + { "str", Pair(1, INT_MAX) }, + { "print", Pair(0, INT_MAX) }, + { "printt", Pair(0, INT_MAX) }, + { "prints", Pair(0, INT_MAX) }, + { "printerr", Pair(0, INT_MAX) }, + { "printraw", Pair(0, INT_MAX) }, + { "var2str", Pair(1, 1) }, + { "str2var", Pair(1, 1) }, + { "range", Pair(1, 3) }, + { "load", Pair(1, 1) }, + { "inst2dict", Pair(1, 1) }, + { "dict2inst", Pair(1, 1) }, + { "hash", Pair(1, 1) }, + { "print_stack", Pair(0, 0) }, + { "instance_from_id", Pair(1, 1) }, +}; + +static constexpr int num_funcs = sizeof(funcs) / sizeof(Pair>); +enum Token { + TK_EMPTY, + TK_IDENTIFIER, + TK_CONSTANT, + TK_SELF, + TK_BUILT_IN_TYPE, + TK_BUILT_IN_FUNC, + TK_OP_IN, + TK_OP_EQUAL, + TK_OP_NOT_EQUAL, + TK_OP_LESS, + TK_OP_LESS_EQUAL, + TK_OP_GREATER, + TK_OP_GREATER_EQUAL, + TK_OP_AND, + TK_OP_OR, + TK_OP_NOT, + TK_OP_ADD, + TK_OP_SUB, + TK_OP_MUL, + TK_OP_DIV, + TK_OP_MOD, + TK_OP_SHIFT_LEFT, + TK_OP_SHIFT_RIGHT, + TK_OP_ASSIGN, + TK_OP_ASSIGN_ADD, + TK_OP_ASSIGN_SUB, + TK_OP_ASSIGN_MUL, + TK_OP_ASSIGN_DIV, + TK_OP_ASSIGN_MOD, + TK_OP_ASSIGN_SHIFT_LEFT, + TK_OP_ASSIGN_SHIFT_RIGHT, + TK_OP_ASSIGN_BIT_AND, + TK_OP_ASSIGN_BIT_OR, + TK_OP_ASSIGN_BIT_XOR, + TK_OP_BIT_AND, + TK_OP_BIT_OR, + TK_OP_BIT_XOR, + TK_OP_BIT_INVERT, + TK_CF_IF, + TK_CF_ELIF, + TK_CF_ELSE, + TK_CF_FOR, + TK_CF_DO, + TK_CF_WHILE, + TK_CF_SWITCH, + TK_CF_CASE, + TK_CF_BREAK, + TK_CF_CONTINUE, + TK_CF_PASS, + TK_CF_RETURN, + TK_PR_FUNCTION, + TK_PR_CLASS, + TK_PR_EXTENDS, + TK_PR_TOOL, + TK_PR_STATIC, + TK_PR_EXPORT, + TK_PR_SETGET, + TK_PR_CONST, + TK_PR_VAR, + TK_PR_PRELOAD, + TK_PR_ASSERT, + TK_PR_YIELD, + TK_PR_SIGNAL, + TK_BRACKET_OPEN, + TK_BRACKET_CLOSE, + TK_CURLY_BRACKET_OPEN, + TK_CURLY_BRACKET_CLOSE, + TK_PARENTHESIS_OPEN, + TK_PARENTHESIS_CLOSE, + TK_COMMA, + TK_SEMICOLON, + TK_PERIOD, + TK_QUESTION_MARK, + TK_COLON, + TK_NEWLINE, + TK_ERROR, + TK_EOF, + TK_CURSOR, + TK_MAX, +}; + +int GDScriptDecomp_2b64f73::get_token_max() const { + return TK_MAX; +} +String GDScriptDecomp_2b64f73::get_function_name(int p_func) const { + if (p_func < 0 || p_func >= num_funcs) { + return ""; + } + return funcs[p_func].first; +} + +int GDScriptDecomp_2b64f73::get_function_count() const { + return num_funcs; +} +Pair GDScriptDecomp_2b64f73::get_function_arg_count(int p_func) const { + if (p_func < 0 || p_func >= num_funcs) { + return Pair(-1, -1); + } + return funcs[p_func].second; +} + + +int GDScriptDecomp_2b64f73::get_function_index(const String &p_func) const { + for (int i = 0; i < num_funcs; i++) { + if (funcs[i].first == p_func) { + return i; + } + } + return -1; +} + +GDScriptDecomp::GlobalToken GDScriptDecomp_2b64f73::get_global_token(int p_token) const { + p_token = p_token & TOKEN_MASK; + if (p_token < 0 || p_token >= TK_MAX) { + return GDScriptDecomp::GlobalToken::G_TK_MAX; + } + switch(Token(p_token)) { + case TK_EMPTY: return GDScriptDecomp::GlobalToken::G_TK_EMPTY; + case TK_IDENTIFIER: return GDScriptDecomp::GlobalToken::G_TK_IDENTIFIER; + case TK_CONSTANT: return GDScriptDecomp::GlobalToken::G_TK_CONSTANT; + case TK_SELF: return GDScriptDecomp::GlobalToken::G_TK_SELF; + case TK_BUILT_IN_TYPE: return GDScriptDecomp::GlobalToken::G_TK_BUILT_IN_TYPE; + case TK_BUILT_IN_FUNC: return GDScriptDecomp::GlobalToken::G_TK_BUILT_IN_FUNC; + case TK_OP_IN: return GDScriptDecomp::GlobalToken::G_TK_OP_IN; + case TK_OP_EQUAL: return GDScriptDecomp::GlobalToken::G_TK_OP_EQUAL; + case TK_OP_NOT_EQUAL: return GDScriptDecomp::GlobalToken::G_TK_OP_NOT_EQUAL; + case TK_OP_LESS: return GDScriptDecomp::GlobalToken::G_TK_OP_LESS; + case TK_OP_LESS_EQUAL: return GDScriptDecomp::GlobalToken::G_TK_OP_LESS_EQUAL; + case TK_OP_GREATER: return GDScriptDecomp::GlobalToken::G_TK_OP_GREATER; + case TK_OP_GREATER_EQUAL: return GDScriptDecomp::GlobalToken::G_TK_OP_GREATER_EQUAL; + case TK_OP_AND: return GDScriptDecomp::GlobalToken::G_TK_OP_AND; + case TK_OP_OR: return GDScriptDecomp::GlobalToken::G_TK_OP_OR; + case TK_OP_NOT: return GDScriptDecomp::GlobalToken::G_TK_OP_NOT; + case TK_OP_ADD: return GDScriptDecomp::GlobalToken::G_TK_OP_ADD; + case TK_OP_SUB: return GDScriptDecomp::GlobalToken::G_TK_OP_SUB; + case TK_OP_MUL: return GDScriptDecomp::GlobalToken::G_TK_OP_MUL; + case TK_OP_DIV: return GDScriptDecomp::GlobalToken::G_TK_OP_DIV; + case TK_OP_MOD: return GDScriptDecomp::GlobalToken::G_TK_OP_MOD; + case TK_OP_SHIFT_LEFT: return GDScriptDecomp::GlobalToken::G_TK_OP_SHIFT_LEFT; + case TK_OP_SHIFT_RIGHT: return GDScriptDecomp::GlobalToken::G_TK_OP_SHIFT_RIGHT; + case TK_OP_ASSIGN: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN; + case TK_OP_ASSIGN_ADD: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_ADD; + case TK_OP_ASSIGN_SUB: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_SUB; + case TK_OP_ASSIGN_MUL: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_MUL; + case TK_OP_ASSIGN_DIV: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_DIV; + case TK_OP_ASSIGN_MOD: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_MOD; + case TK_OP_ASSIGN_SHIFT_LEFT: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_SHIFT_LEFT; + case TK_OP_ASSIGN_SHIFT_RIGHT: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_SHIFT_RIGHT; + case TK_OP_ASSIGN_BIT_AND: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_BIT_AND; + case TK_OP_ASSIGN_BIT_OR: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_BIT_OR; + case TK_OP_ASSIGN_BIT_XOR: return GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_BIT_XOR; + case TK_OP_BIT_AND: return GDScriptDecomp::GlobalToken::G_TK_OP_BIT_AND; + case TK_OP_BIT_OR: return GDScriptDecomp::GlobalToken::G_TK_OP_BIT_OR; + case TK_OP_BIT_XOR: return GDScriptDecomp::GlobalToken::G_TK_OP_BIT_XOR; + case TK_OP_BIT_INVERT: return GDScriptDecomp::GlobalToken::G_TK_OP_BIT_INVERT; + case TK_CF_IF: return GDScriptDecomp::GlobalToken::G_TK_CF_IF; + case TK_CF_ELIF: return GDScriptDecomp::GlobalToken::G_TK_CF_ELIF; + case TK_CF_ELSE: return GDScriptDecomp::GlobalToken::G_TK_CF_ELSE; + case TK_CF_FOR: return GDScriptDecomp::GlobalToken::G_TK_CF_FOR; + case TK_CF_DO: return GDScriptDecomp::GlobalToken::G_TK_CF_DO; + case TK_CF_WHILE: return GDScriptDecomp::GlobalToken::G_TK_CF_WHILE; + case TK_CF_SWITCH: return GDScriptDecomp::GlobalToken::G_TK_CF_SWITCH; + case TK_CF_CASE: return GDScriptDecomp::GlobalToken::G_TK_CF_CASE; + case TK_CF_BREAK: return GDScriptDecomp::GlobalToken::G_TK_CF_BREAK; + case TK_CF_CONTINUE: return GDScriptDecomp::GlobalToken::G_TK_CF_CONTINUE; + case TK_CF_PASS: return GDScriptDecomp::GlobalToken::G_TK_CF_PASS; + case TK_CF_RETURN: return GDScriptDecomp::GlobalToken::G_TK_CF_RETURN; + case TK_PR_FUNCTION: return GDScriptDecomp::GlobalToken::G_TK_PR_FUNCTION; + case TK_PR_CLASS: return GDScriptDecomp::GlobalToken::G_TK_PR_CLASS; + case TK_PR_EXTENDS: return GDScriptDecomp::GlobalToken::G_TK_PR_EXTENDS; + case TK_PR_TOOL: return GDScriptDecomp::GlobalToken::G_TK_PR_TOOL; + case TK_PR_STATIC: return GDScriptDecomp::GlobalToken::G_TK_PR_STATIC; + case TK_PR_EXPORT: return GDScriptDecomp::GlobalToken::G_TK_PR_EXPORT; + case TK_PR_SETGET: return GDScriptDecomp::GlobalToken::G_TK_PR_SETGET; + case TK_PR_CONST: return GDScriptDecomp::GlobalToken::G_TK_PR_CONST; + case TK_PR_VAR: return GDScriptDecomp::GlobalToken::G_TK_PR_VAR; + case TK_PR_PRELOAD: return GDScriptDecomp::GlobalToken::G_TK_PR_PRELOAD; + case TK_PR_ASSERT: return GDScriptDecomp::GlobalToken::G_TK_PR_ASSERT; + case TK_PR_YIELD: return GDScriptDecomp::GlobalToken::G_TK_PR_YIELD; + case TK_PR_SIGNAL: return GDScriptDecomp::GlobalToken::G_TK_PR_SIGNAL; + case TK_BRACKET_OPEN: return GDScriptDecomp::GlobalToken::G_TK_BRACKET_OPEN; + case TK_BRACKET_CLOSE: return GDScriptDecomp::GlobalToken::G_TK_BRACKET_CLOSE; + case TK_CURLY_BRACKET_OPEN: return GDScriptDecomp::GlobalToken::G_TK_CURLY_BRACKET_OPEN; + case TK_CURLY_BRACKET_CLOSE: return GDScriptDecomp::GlobalToken::G_TK_CURLY_BRACKET_CLOSE; + case TK_PARENTHESIS_OPEN: return GDScriptDecomp::GlobalToken::G_TK_PARENTHESIS_OPEN; + case TK_PARENTHESIS_CLOSE: return GDScriptDecomp::GlobalToken::G_TK_PARENTHESIS_CLOSE; + case TK_COMMA: return GDScriptDecomp::GlobalToken::G_TK_COMMA; + case TK_SEMICOLON: return GDScriptDecomp::GlobalToken::G_TK_SEMICOLON; + case TK_PERIOD: return GDScriptDecomp::GlobalToken::G_TK_PERIOD; + case TK_QUESTION_MARK: return GDScriptDecomp::GlobalToken::G_TK_QUESTION_MARK; + case TK_COLON: return GDScriptDecomp::GlobalToken::G_TK_COLON; + case TK_NEWLINE: return GDScriptDecomp::GlobalToken::G_TK_NEWLINE; + case TK_ERROR: return GDScriptDecomp::GlobalToken::G_TK_ERROR; + case TK_EOF: return GDScriptDecomp::GlobalToken::G_TK_EOF; + case TK_CURSOR: return GDScriptDecomp::GlobalToken::G_TK_CURSOR; + case TK_MAX: return GDScriptDecomp::GlobalToken::G_TK_MAX; + default: return GDScriptDecomp::GlobalToken::G_TK_MAX; + } + return GDScriptDecomp::GlobalToken::G_TK_MAX; +} + +int GDScriptDecomp_2b64f73::get_local_token_val(GDScriptDecomp::GlobalToken p_token) const { + switch(p_token) { + case GDScriptDecomp::GlobalToken::G_TK_EMPTY: return (int) TK_EMPTY; + case GDScriptDecomp::GlobalToken::G_TK_IDENTIFIER: return (int) TK_IDENTIFIER; + case GDScriptDecomp::GlobalToken::G_TK_CONSTANT: return (int) TK_CONSTANT; + case GDScriptDecomp::GlobalToken::G_TK_SELF: return (int) TK_SELF; + case GDScriptDecomp::GlobalToken::G_TK_BUILT_IN_TYPE: return (int) TK_BUILT_IN_TYPE; + case GDScriptDecomp::GlobalToken::G_TK_BUILT_IN_FUNC: return (int) TK_BUILT_IN_FUNC; + case GDScriptDecomp::GlobalToken::G_TK_OP_IN: return (int) TK_OP_IN; + case GDScriptDecomp::GlobalToken::G_TK_OP_EQUAL: return (int) TK_OP_EQUAL; + case GDScriptDecomp::GlobalToken::G_TK_OP_NOT_EQUAL: return (int) TK_OP_NOT_EQUAL; + case GDScriptDecomp::GlobalToken::G_TK_OP_LESS: return (int) TK_OP_LESS; + case GDScriptDecomp::GlobalToken::G_TK_OP_LESS_EQUAL: return (int) TK_OP_LESS_EQUAL; + case GDScriptDecomp::GlobalToken::G_TK_OP_GREATER: return (int) TK_OP_GREATER; + case GDScriptDecomp::GlobalToken::G_TK_OP_GREATER_EQUAL: return (int) TK_OP_GREATER_EQUAL; + case GDScriptDecomp::GlobalToken::G_TK_OP_AND: return (int) TK_OP_AND; + case GDScriptDecomp::GlobalToken::G_TK_OP_OR: return (int) TK_OP_OR; + case GDScriptDecomp::GlobalToken::G_TK_OP_NOT: return (int) TK_OP_NOT; + case GDScriptDecomp::GlobalToken::G_TK_OP_ADD: return (int) TK_OP_ADD; + case GDScriptDecomp::GlobalToken::G_TK_OP_SUB: return (int) TK_OP_SUB; + case GDScriptDecomp::GlobalToken::G_TK_OP_MUL: return (int) TK_OP_MUL; + case GDScriptDecomp::GlobalToken::G_TK_OP_DIV: return (int) TK_OP_DIV; + case GDScriptDecomp::GlobalToken::G_TK_OP_MOD: return (int) TK_OP_MOD; + case GDScriptDecomp::GlobalToken::G_TK_OP_SHIFT_LEFT: return (int) TK_OP_SHIFT_LEFT; + case GDScriptDecomp::GlobalToken::G_TK_OP_SHIFT_RIGHT: return (int) TK_OP_SHIFT_RIGHT; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN: return (int) TK_OP_ASSIGN; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_ADD: return (int) TK_OP_ASSIGN_ADD; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_SUB: return (int) TK_OP_ASSIGN_SUB; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_MUL: return (int) TK_OP_ASSIGN_MUL; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_DIV: return (int) TK_OP_ASSIGN_DIV; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_MOD: return (int) TK_OP_ASSIGN_MOD; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_SHIFT_LEFT: return (int) TK_OP_ASSIGN_SHIFT_LEFT; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_SHIFT_RIGHT: return (int) TK_OP_ASSIGN_SHIFT_RIGHT; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_BIT_AND: return (int) TK_OP_ASSIGN_BIT_AND; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_BIT_OR: return (int) TK_OP_ASSIGN_BIT_OR; + case GDScriptDecomp::GlobalToken::G_TK_OP_ASSIGN_BIT_XOR: return (int) TK_OP_ASSIGN_BIT_XOR; + case GDScriptDecomp::GlobalToken::G_TK_OP_BIT_AND: return (int) TK_OP_BIT_AND; + case GDScriptDecomp::GlobalToken::G_TK_OP_BIT_OR: return (int) TK_OP_BIT_OR; + case GDScriptDecomp::GlobalToken::G_TK_OP_BIT_XOR: return (int) TK_OP_BIT_XOR; + case GDScriptDecomp::GlobalToken::G_TK_OP_BIT_INVERT: return (int) TK_OP_BIT_INVERT; + case GDScriptDecomp::GlobalToken::G_TK_CF_IF: return (int) TK_CF_IF; + case GDScriptDecomp::GlobalToken::G_TK_CF_ELIF: return (int) TK_CF_ELIF; + case GDScriptDecomp::GlobalToken::G_TK_CF_ELSE: return (int) TK_CF_ELSE; + case GDScriptDecomp::GlobalToken::G_TK_CF_FOR: return (int) TK_CF_FOR; + case GDScriptDecomp::GlobalToken::G_TK_CF_DO: return (int) TK_CF_DO; + case GDScriptDecomp::GlobalToken::G_TK_CF_WHILE: return (int) TK_CF_WHILE; + case GDScriptDecomp::GlobalToken::G_TK_CF_SWITCH: return (int) TK_CF_SWITCH; + case GDScriptDecomp::GlobalToken::G_TK_CF_CASE: return (int) TK_CF_CASE; + case GDScriptDecomp::GlobalToken::G_TK_CF_BREAK: return (int) TK_CF_BREAK; + case GDScriptDecomp::GlobalToken::G_TK_CF_CONTINUE: return (int) TK_CF_CONTINUE; + case GDScriptDecomp::GlobalToken::G_TK_CF_PASS: return (int) TK_CF_PASS; + case GDScriptDecomp::GlobalToken::G_TK_CF_RETURN: return (int) TK_CF_RETURN; + case GDScriptDecomp::GlobalToken::G_TK_PR_FUNCTION: return (int) TK_PR_FUNCTION; + case GDScriptDecomp::GlobalToken::G_TK_PR_CLASS: return (int) TK_PR_CLASS; + case GDScriptDecomp::GlobalToken::G_TK_PR_EXTENDS: return (int) TK_PR_EXTENDS; + case GDScriptDecomp::GlobalToken::G_TK_PR_TOOL: return (int) TK_PR_TOOL; + case GDScriptDecomp::GlobalToken::G_TK_PR_STATIC: return (int) TK_PR_STATIC; + case GDScriptDecomp::GlobalToken::G_TK_PR_EXPORT: return (int) TK_PR_EXPORT; + case GDScriptDecomp::GlobalToken::G_TK_PR_SETGET: return (int) TK_PR_SETGET; + case GDScriptDecomp::GlobalToken::G_TK_PR_CONST: return (int) TK_PR_CONST; + case GDScriptDecomp::GlobalToken::G_TK_PR_VAR: return (int) TK_PR_VAR; + case GDScriptDecomp::GlobalToken::G_TK_PR_PRELOAD: return (int) TK_PR_PRELOAD; + case GDScriptDecomp::GlobalToken::G_TK_PR_ASSERT: return (int) TK_PR_ASSERT; + case GDScriptDecomp::GlobalToken::G_TK_PR_YIELD: return (int) TK_PR_YIELD; + case GDScriptDecomp::GlobalToken::G_TK_PR_SIGNAL: return (int) TK_PR_SIGNAL; + case GDScriptDecomp::GlobalToken::G_TK_BRACKET_OPEN: return (int) TK_BRACKET_OPEN; + case GDScriptDecomp::GlobalToken::G_TK_BRACKET_CLOSE: return (int) TK_BRACKET_CLOSE; + case GDScriptDecomp::GlobalToken::G_TK_CURLY_BRACKET_OPEN: return (int) TK_CURLY_BRACKET_OPEN; + case GDScriptDecomp::GlobalToken::G_TK_CURLY_BRACKET_CLOSE: return (int) TK_CURLY_BRACKET_CLOSE; + case GDScriptDecomp::GlobalToken::G_TK_PARENTHESIS_OPEN: return (int) TK_PARENTHESIS_OPEN; + case GDScriptDecomp::GlobalToken::G_TK_PARENTHESIS_CLOSE: return (int) TK_PARENTHESIS_CLOSE; + case GDScriptDecomp::GlobalToken::G_TK_COMMA: return (int) TK_COMMA; + case GDScriptDecomp::GlobalToken::G_TK_SEMICOLON: return (int) TK_SEMICOLON; + case GDScriptDecomp::GlobalToken::G_TK_PERIOD: return (int) TK_PERIOD; + case GDScriptDecomp::GlobalToken::G_TK_QUESTION_MARK: return (int) TK_QUESTION_MARK; + case GDScriptDecomp::GlobalToken::G_TK_COLON: return (int) TK_COLON; + case GDScriptDecomp::GlobalToken::G_TK_NEWLINE: return (int) TK_NEWLINE; + case GDScriptDecomp::GlobalToken::G_TK_ERROR: return (int) TK_ERROR; + case GDScriptDecomp::GlobalToken::G_TK_EOF: return (int) TK_EOF; + case GDScriptDecomp::GlobalToken::G_TK_CURSOR: return (int) TK_CURSOR; + case GDScriptDecomp::GlobalToken::G_TK_MAX: return (int) TK_MAX; + default: return -1; + } + return -1; +} + diff --git a/bytecode/bytecode_2b64f73.h b/bytecode/bytecode_2b64f73.h new file mode 100644 index 000000000..07d4cbb47 --- /dev/null +++ b/bytecode/bytecode_2b64f73.h @@ -0,0 +1,42 @@ +// This file is automatically generated by `bytecode_generator.py` +// Do not edit this file directly, as it will be overwritten. +// Instead, edit `bytecode_generator.py` and run it to generate this file. + +// clang-format off +#pragma once + +#include "bytecode_base.h" + +class GDScriptDecomp_2b64f73 : public GDScriptDecomp { + GDCLASS(GDScriptDecomp_2b64f73, GDScriptDecomp); +protected: + static void _bind_methods(){}; + static constexpr int bytecode_version = 5; + static constexpr int bytecode_rev = 0x2b64f73; + static constexpr int engine_ver_major = 2; + static constexpr int variant_ver_major = 2; + static constexpr const char *bytecode_rev_str = "2b64f73"; + static constexpr const char *engine_version = "2.0-dev1"; + static constexpr const char *max_engine_version = ""; + static constexpr const char *date = "2015-06-27"; + static constexpr int parent = 0x48f1d02; + +public: + virtual String get_function_name(int p_func) const override; + virtual int get_function_count() const override; + virtual Pair get_function_arg_count(int p_func) const override; + virtual int get_token_max() const override; + virtual int get_function_index(const String &p_func) const override; + virtual GDScriptDecomp::GlobalToken get_global_token(int p_token) const override; + virtual int get_local_token_val(GDScriptDecomp::GlobalToken p_token) const override; + virtual int get_bytecode_version() const override { return bytecode_version; } + virtual int get_bytecode_rev() const override { return bytecode_rev; } + virtual int get_engine_ver_major() const override { return engine_ver_major; } + virtual int get_variant_ver_major() const override { return variant_ver_major; } + virtual int get_parent() const override { return parent; } + virtual String get_engine_version() const override { return engine_version; } + virtual String get_max_engine_version() const override { return max_engine_version; } + virtual String get_date() const override { return date; } + GDScriptDecomp_2b64f73() {} +}; + diff --git a/bytecode/bytecode_48f1d02.h b/bytecode/bytecode_48f1d02.h index 2e1edd8b5..3dd485368 100644 --- a/bytecode/bytecode_48f1d02.h +++ b/bytecode/bytecode_48f1d02.h @@ -11,12 +11,12 @@ class GDScriptDecomp_48f1d02 : public GDScriptDecomp { GDCLASS(GDScriptDecomp_48f1d02, GDScriptDecomp); protected: static void _bind_methods(){}; - static constexpr int bytecode_version = 5; + static constexpr int bytecode_version = 4; static constexpr int bytecode_rev = 0x48f1d02; static constexpr int engine_ver_major = 2; static constexpr int variant_ver_major = 2; static constexpr const char *bytecode_rev_str = "48f1d02"; - static constexpr const char *engine_version = "2.0-dev1"; + static constexpr const char *engine_version = "2.0-dev0"; static constexpr const char *max_engine_version = ""; static constexpr const char *date = "2015-06-24"; static constexpr int parent = 0x65d48d6; diff --git a/bytecode/bytecode_base.cpp b/bytecode/bytecode_base.cpp index d91722071..0a1cc6567 100644 --- a/bytecode/bytecode_base.cpp +++ b/bytecode/bytecode_base.cpp @@ -1479,6 +1479,8 @@ int GDScriptDecomp::get_func_arg_count_and_params(int curr_pos, const Vector 0) { r_arguments.push_back(curr_arg); + curr_arg.clear(); } break; } diff --git a/bytecode/bytecode_versions.cpp b/bytecode/bytecode_versions.cpp index 229f37735..037e189ff 100644 --- a/bytecode/bytecode_versions.cpp +++ b/bytecode/bytecode_versions.cpp @@ -55,6 +55,7 @@ #include "bytecode/bytecode_64872ca.h" #include "bytecode/bytecode_7d2d144.h" #include "bytecode/bytecode_30c1229.h" +#include "bytecode/bytecode_2b64f73.h" #include "bytecode/bytecode_48f1d02.h" #include "bytecode/bytecode_65d48d6.h" #include "bytecode/bytecode_be46be7.h" @@ -116,6 +117,7 @@ void register_decomp_versions() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); @@ -180,6 +182,7 @@ Ref GDScriptDecompVersion::create_decomp_for_commit(int p_commit case 0x64872ca: return memnew(GDScriptDecomp_64872ca); case 0x7d2d144: return memnew(GDScriptDecomp_7d2d144); case 0x30c1229: return memnew(GDScriptDecomp_30c1229); + case 0x2b64f73: return memnew(GDScriptDecomp_2b64f73); case 0x48f1d02: return memnew(GDScriptDecomp_48f1d02); case 0x65d48d6: return memnew(GDScriptDecomp_65d48d6); case 0xbe46be7: return memnew(GDScriptDecomp_be46be7); @@ -253,7 +256,8 @@ Vector GDScriptDecompVersion::decomp_versions = { { 0x64872ca, " 2.0-dev4 (64872ca / 2015-12-31 / Bytecode version: 8) - Added `Color8` function.", 8, true, "2.0-dev4", "", 0x7d2d144 }, { 0x7d2d144, " 2.0-dev3 (7d2d144 / 2015-12-29 / Bytecode version: 7) - Added `BREAKPOINT` token.", 7, true, "2.0-dev3", "", 0x30c1229 }, { 0x30c1229, " 2.0-dev2 (30c1229 / 2015-12-28 / Bytecode version: 6) - Added `ONREADY` token.", 6, true, "2.0-dev2", "", 0x48f1d02 }, - { 0x48f1d02, " 2.0-dev1 (48f1d02 / 2015-06-24 / Bytecode version: 5) - Added `SIGNAL` token.", 5, true, "2.0-dev1", "", 0x65d48d6 }, + { 0x2b64f73, " 2.0-dev1 (2b64f73 / 2015-06-27 / Bytecode version: 5) - bytecode version changed", 5, true, "2.0-dev1", "", 0x48f1d02 }, + { 0x48f1d02, " 2.0-dev0 (48f1d02 / 2015-06-24 / Bytecode version: 4) - Added `SIGNAL` token.", 4, true, "2.0-dev0", "", 0x65d48d6 }, { 0x65d48d6, "1.1.0-stable (65d48d6 / 2015-05-09 / Bytecode version: 4) - Added `prints` function.", 4, false, "1.1.0-stable", "", 0xbe46be7 }, { 0xbe46be7, " 1.1-dev3 (be46be7 / 2015-04-18 / Bytecode version: 3) - Renamed function get_inst to instance_from_id.", 3, true, "1.1-dev3", "", 0x97f34a1 }, { 0x97f34a1, " 1.1-dev2 (97f34a1 / 2015-03-25 / Bytecode version: 3) - Added `seed`, `get_inst` functions.", 3, true, "1.1-dev2", "", 0x2185c01 }, diff --git a/bytecode/gdscript_v1_tokenizer_compat.cpp b/bytecode/gdscript_v1_tokenizer_compat.cpp index 5a189b17c..023eec548 100644 --- a/bytecode/gdscript_v1_tokenizer_compat.cpp +++ b/bytecode/gdscript_v1_tokenizer_compat.cpp @@ -1199,6 +1199,9 @@ Vector GDScriptV1TokenizerBufferCompat::parse_code_string(const String RBMap line_map; Vector token_array; + // GDScriptV1TokenizerBufferCompat is only used for GDScript 1.0, (e.g. 3.x and below), which did not support double precision + constexpr bool real_t_is_double = false; + int variant_ver_major = p_decomp->get_variant_ver_major(); // compat: from 3.0 - 3.1.1, the tokenizer defaulted to storing full objects @@ -1320,11 +1323,11 @@ Vector GDScriptV1TokenizerBufferCompat::parse_code_string(const String for (RBMap::Element *E = rev_constant_map.front(); E; E = E->next()) { int len; - Error err = VariantDecoderCompat::encode_variant_compat(variant_ver_major, E->get(), nullptr, len, encode_full_objects); + Error err = VariantDecoderCompat::encode_variant_compat(variant_ver_major, E->get(), nullptr, len, encode_full_objects, real_t_is_double); GDSDECOMP_FAIL_COND_V_MSG(err != OK, Vector(), "Error when trying to encode Variant."); int pos = buf.size(); buf.resize_initialized(pos + len); - VariantDecoderCompat::encode_variant_compat(variant_ver_major, E->get(), &buf.write[pos], len, encode_full_objects); + VariantDecoderCompat::encode_variant_compat(variant_ver_major, E->get(), &buf.write[pos], len, encode_full_objects, real_t_is_double); } for (RBMap::Element *E = rev_line_map.front(); E; E = E->next()) { diff --git a/bytecode/gdscript_v2_tokenizer_buffer.cpp b/bytecode/gdscript_v2_tokenizer_buffer.cpp index ffdd9ffc0..f37b2b9c1 100644 --- a/bytecode/gdscript_v2_tokenizer_buffer.cpp +++ b/bytecode/gdscript_v2_tokenizer_buffer.cpp @@ -30,6 +30,9 @@ #include "gdscript_v2_tokenizer_buffer.h" +#include "compat/variant_decoder_compat.h" +#include "utility/gdre_settings.h" + #include "core/io/compression.h" #include "core/io/marshalls.h" @@ -267,6 +270,10 @@ Vector GDScriptV2TokenizerBufferCompat::parse_code_string(const String HashMap token_lines; HashMap token_columns; + // TODO: This shouldn't actually matter, as no real_t variants (Vector2, Vector3, etc.) are actually encoded as variants; + // Rather, they get turned into their respective constructor tokens (e.g. TK_IDENTIFIER=Vector2, TK_PARENTHESIS_OPEN, TK_CONSTANT=, etc...) + bool real_t_is_double = GDRESettings::get_singleton()->requires_double_precision(); + GDScriptV2TokenizerCompatText tokenizer(p_decomp); tokenizer.set_source_code(p_code); tokenizer.set_multiline_mode(true); // Ignore whitespace tokens. @@ -363,7 +370,7 @@ Vector GDScriptV2TokenizerBufferCompat::parse_code_string(const String Error err = encode_variant(v, nullptr, len, false); ERR_FAIL_COND_V_MSG(err != OK, Vector(), "Error when trying to encode Variant."); contents.resize(buf_pos + len); - encode_variant(v, &contents.write[buf_pos], len, false); + VariantDecoderCompat::encode_variant_compat(p_decomp->get_variant_ver_major(), v, &contents.write[buf_pos], len, false, real_t_is_double); buf_pos += len; } diff --git a/compat/fake_csharp_script.cpp b/compat/fake_csharp_script.cpp index 8c93cae19..102082cf7 100644 --- a/compat/fake_csharp_script.cpp +++ b/compat/fake_csharp_script.cpp @@ -100,11 +100,6 @@ PlaceHolderScriptInstance *FakeCSharpScript::placeholder_instance_create(Object return si; } -bool FakeCSharpScript::instance_has(const Object *p_this) const { - // For now, return false as we don't track instances - return false; -} - PropertyHint string_to_property_hint(const String &p_string) { String name = p_string.to_upper(); if (name == "NONE") { @@ -396,7 +391,7 @@ Error FakeCSharpScript::reload(bool p_keep_state) { icon_path = script_info.get("icon_path", ""); globally_available = script_info.get("is_global_class", false); global_name = globally_available ? local_name : ""; - base_type = base_classes.size() > 0 ? base_classes[0] : "RefCounted"; + base_type = script_info.get("godot_sharp_base_type", base_classes.size() > 0 ? base_classes[0] : "RefCounted"); tool = script_info.get("is_tool", false); abstract = script_info.get("is_abstract", false); TypedArray script_properties = script_info.get("properties", TypedArray()); diff --git a/compat/fake_csharp_script.h b/compat/fake_csharp_script.h index 4652de38d..476cd40e7 100644 --- a/compat/fake_csharp_script.h +++ b/compat/fake_csharp_script.h @@ -64,7 +64,6 @@ class FakeCSharpScript : public FakeScript { // this may not work in all scripts, will return empty if so virtual ScriptInstance *instance_create(Object *p_this) override; virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override; - virtual bool instance_has(const Object *p_this) const override; // virtual bool has_source_code() const override; // virtual String get_source_code() const override; diff --git a/compat/fake_gdscript.cpp b/compat/fake_gdscript.cpp index 6d99b3924..296de4317 100644 --- a/compat/fake_gdscript.cpp +++ b/compat/fake_gdscript.cpp @@ -51,8 +51,11 @@ Error FakeGDScript::_reload_from_file() { FAKEGDSCRIPT_FAIL_COND_V_MSG(err != OK, err, "Error reading file: " + script_path); is_binary = binary_buffer.size() >= 4 && binary_buffer[0] == 'G' && binary_buffer[1] == 'D' && binary_buffer[2] == 'S' && binary_buffer[3] == 'C'; if (!is_binary) { - err = source.append_utf8(reinterpret_cast(binary_buffer.ptr()), binary_buffer.size()); - FAKEGDSCRIPT_FAIL_COND_V_MSG(err != OK, err, "Error reading file: " + script_path); + // Empty text files are valid gdscripts (treated as `extends RefCounted`). + if (binary_buffer.size() > 0) { + err = source.append_utf8(reinterpret_cast(binary_buffer.ptr()), binary_buffer.size()); + FAKEGDSCRIPT_FAIL_COND_V_MSG(err != OK, err, "Error reading file: " + script_path); + } binary_buffer.clear(); } } @@ -121,11 +124,6 @@ PlaceHolderScriptInstance *FakeGDScript::placeholder_instance_create(Object *p_t return si; } -bool FakeGDScript::instance_has(const Object *p_this) const { - // TODO? - return true; -} - void FakeGDScript::set_source_code(const String &p_code) { is_binary = false; source = p_code; diff --git a/compat/fake_gdscript.h b/compat/fake_gdscript.h index 4024c1bc8..99db58639 100644 --- a/compat/fake_gdscript.h +++ b/compat/fake_gdscript.h @@ -75,7 +75,6 @@ class FakeGDScript : public FakeScript { // this may not work in all scripts, will return empty if so virtual ScriptInstance *instance_create(Object *p_this) override; virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override; - virtual bool instance_has(const Object *p_this) const override; // virtual bool has_source_code() const override; // virtual String get_source_code() const override; diff --git a/compat/fake_script.cpp b/compat/fake_script.cpp index 4be44a53f..7b8737423 100644 --- a/compat/fake_script.cpp +++ b/compat/fake_script.cpp @@ -103,10 +103,6 @@ PlaceHolderScriptInstance *FakeScript::placeholder_instance_create(Object *p_thi return si; } -bool FakeScript::instance_has(const Object *p_this) const { - return true; -} - bool FakeScript::has_source_code() const { return !source.is_empty(); } diff --git a/compat/fake_script.h b/compat/fake_script.h index 7ab946a67..c1a59b4ea 100644 --- a/compat/fake_script.h +++ b/compat/fake_script.h @@ -43,7 +43,6 @@ class FakeScript : public Script { virtual StringName get_instance_base_type() const override; virtual ScriptInstance *instance_create(Object *p_this) override; virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override; - virtual bool instance_has(const Object *p_this) const override; virtual bool has_source_code() const override; virtual String get_source_code() const override; diff --git a/compat/resource_compat_binary.cpp b/compat/resource_compat_binary.cpp index 59b3a2222..e80d3fb1f 100644 --- a/compat/resource_compat_binary.cpp +++ b/compat/resource_compat_binary.cpp @@ -50,6 +50,8 @@ #include "utility/gdre_settings.h" #include "utility/resource_info.h" +#include "resource_compat_obdb.h" + //#define print_bl(m_what) print_line(m_what) #define print_bl(m_what) (void)(m_what) @@ -1196,7 +1198,6 @@ void ResourceLoaderCompatBinary::open(Ref p_f, bool p_no_resources, } type = get_unicode_string(); - print_bl("type: " + type); md_at = f->get_position(); importmd_ofs = f->get_64(); @@ -1208,9 +1209,11 @@ void ResourceLoaderCompatBinary::open(Ref p_f, bool p_no_resources, using_uids = true; } f->real_is_double = (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_REAL_T_IS_DOUBLE) != 0; +#if !REAL_T_IS_DOUBLE if (f->real_is_double) { WARN_PRINT_ONCE("ResourceLoaderCompatBinary: Real type is double, this is not unsupported in this version of GDRE tools."); } +#endif using_real_t_double = f->real_is_double; if (using_uids) { @@ -3488,6 +3491,10 @@ Error ResourceFormatLoaderCompatBinary::get_ver_major_minor(const String &p_path if (err != OK || !f.is_valid()) { return err != OK ? err : ERR_FILE_CANT_OPEN; } + // Hack necessary for version detection when loading a project from non-pck + if (ResourceFormatLoaderCompatOBDB::is_obdb_resource_file(f)) { + return ResourceFormatLoaderCompatOBDB::get_ver_major_minor_file(f, r_ver_major, r_ver_minor, r_suspicious); + } ResourceLoaderCompatBinary loader; return loader.get_ver_major_minor(f, r_ver_major, r_ver_minor, r_suspicious) ? OK : loader.error; } @@ -3649,10 +3656,11 @@ String ResourceFormatSaverCompatBinaryInstance::get_local_path(const String &p_p return p_resource.is_valid() ? p_resource->get_path() : ""; } -Error ResourceFormatLoaderCompatBinary::test_writing_parsing_variant(Variant p_v, Variant &r_v, int ver_major, int ver_minor) { +Error ResourceFormatLoaderCompatBinary::test_writing_parsing_variant(Variant p_v, Variant &r_v, int ver_major, int ver_minor, bool using_real_t_double) { ResourceFormatSaverCompatBinaryInstance saver; int format = ResourceFormatSaverCompatBinary::get_default_format_version(ver_major, ver_minor); auto fa = FileAccessBuffer::create(); + fa->real_is_double = using_real_t_double; saver.ver_format = format; saver.ver_major = ver_major; saver.ver_minor = ver_minor; @@ -3675,6 +3683,7 @@ Error ResourceFormatLoaderCompatBinary::test_writing_parsing_variant(Variant p_v loader.ver_major = ver_major; loader.ver_minor = ver_minor; loader.using_named_scene_ids = false; + loader.using_real_t_double = using_real_t_double; for (const auto &[k, v] : resource_map) { loader.internal_index_cache["::" + itos(v)] = k; } diff --git a/compat/resource_compat_binary.h b/compat/resource_compat_binary.h index db8a767ec..7672eea05 100644 --- a/compat/resource_compat_binary.h +++ b/compat/resource_compat_binary.h @@ -174,7 +174,7 @@ class ResourceFormatLoaderCompatBinary : public CompatFormatLoader { static Error rewrite_v2_import_metadata(const String &p_path, const String &p_dst, Ref imd); - static Error test_writing_parsing_variant(Variant p_v, Variant &r_v, int ver_major, int ver_minor); + static Error test_writing_parsing_variant(Variant p_v, Variant &r_v, int ver_major, int ver_minor, bool using_real_t_double); }; class ResourceFormatSaverCompatBinaryInstance { diff --git a/compat/resource_compat_obdb.cpp b/compat/resource_compat_obdb.cpp new file mode 100644 index 000000000..8a7e6bd90 --- /dev/null +++ b/compat/resource_compat_obdb.cpp @@ -0,0 +1,1885 @@ +#include "resource_compat_obdb.h" + +#include "compat/image_enum_compat.h" +#include "compat/image_parser_v2.h" +#include "core/error/error_list.h" +#include "utility/gdre_settings.h" + +#include "core/io/file_access.h" +#include "core/io/missing_resource.h" +#include "core/io/resource_loader.h" +#include "core/object/class_db.h" +#include "core/templates/local_vector.h" +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" + +namespace { + +// Pre-1.x variant identifiers (subset of object_format_binary.cpp). +enum OBDBVariant { + VARIANT_NIL = 1, + VARIANT_BOOL = 2, + VARIANT_INT = 3, + VARIANT_REAL = 4, + VARIANT_STRING = 5, + VARIANT_VECTOR2 = 10, + VARIANT_RECT2 = 11, + VARIANT_VECTOR3 = 12, + VARIANT_PLANE = 13, + VARIANT_QUAT = 14, + VARIANT_AABB = 15, + VARIANT_MATRIX3 = 16, + VARIANT_TRANSFORM = 17, + VARIANT_MATRIX32 = 18, + VARIANT_COLOR = 20, + VARIANT_IMAGE = 21, + VARIANT_NODE_PATH = 22, + VARIANT_RID = 23, + VARIANT_OBJECT = 24, + VARIANT_INPUT_EVENT = 25, + VARIANT_DICTIONARY = 26, + VARIANT_ARRAY = 30, + VARIANT_RAW_ARRAY = 31, + VARIANT_INT_ARRAY = 32, + VARIANT_REAL_ARRAY = 33, + VARIANT_STRING_ARRAY = 34, + VARIANT_VECTOR3_ARRAY = 35, + VARIANT_COLOR_ARRAY = 36, + VARIANT_VECTOR2_ARRAY = 37, + + OBDB_OBJECT_EMPTY = 0, + OBDB_OBJECT_EXTERNAL_RESOURCE = 1, + OBDB_OBJECT_INTERNAL_RESOURCE = 2, +}; + +enum OBDBImageEncoding { + OBDB_IMAGE_ENCODING_EMPTY = 0, + OBDB_IMAGE_ENCODING_RAW = 1, + OBDB_IMAGE_ENCODING_PNG = 2, // Never produced by the pre-1.x saver, but reserved. + OBDB_IMAGE_ENCODING_JPG = 3, // Same. +}; + +// Pre-1.x raw image formats. See `.cursor/object_format_binary.cpp` lines 87-99. +enum OBDBImageFormat { + OBDB_IMAGE_FORMAT_GRAYSCALE = 0, + OBDB_IMAGE_FORMAT_INTENSITY = 1, + OBDB_IMAGE_FORMAT_GRAYSCALE_ALPHA = 2, + OBDB_IMAGE_FORMAT_RGB = 3, + OBDB_IMAGE_FORMAT_RGBA = 4, + OBDB_IMAGE_FORMAT_INDEXED = 5, + OBDB_IMAGE_FORMAT_INDEXED_ALPHA = 6, + OBDB_IMAGE_FORMAT_BC1 = 7, + OBDB_IMAGE_FORMAT_BC2 = 8, + OBDB_IMAGE_FORMAT_BC3 = 9, + OBDB_IMAGE_FORMAT_BC4 = 10, + OBDB_IMAGE_FORMAT_BC5 = 11, + OBDB_IMAGE_FORMAT_CUSTOM = 12, +}; + +constexpr int OBDB_RESERVED_FIELDS = 16; + +// Pre-1.x connection flags. The persist bit is implicit because the saver only +// stores persistent connections. +constexpr int OBDB_CONNECT_DEFERRED = 1; + +// Mirror of the private constants in `SceneState` (packed_scene.h lines 53-55); +// reproduced here because the bundled scene format wire layout uses these but +// the header keeps them private. +constexpr int OBDB_SCENE_NO_PARENT_SAVED = 0x7FFFFFFF; +constexpr int OBDB_SCENE_NAME_INDEX_BITS = 18; +constexpr uint32_t OBDB_SCENE_NAME_MASK = (1u << OBDB_SCENE_NAME_INDEX_BITS) - 1u; + +} // namespace + +String ResourceLoaderCompatOBDB::get_unicode_string() { + int len = f->get_32(); + if (len > str_buf.size()) { + str_buf.resize(len); + } + if (len == 0) { + return String(); + } + f->get_buffer((uint8_t *)&str_buf[0], len); + return String::utf8(&str_buf[0], len); +} + +void ResourceLoaderCompatOBDB::_advance_padding(uint32_t p_len) { + uint32_t extra = 4 - (p_len % 4); + if (extra < 4) { + for (uint32_t i = 0; i < extra; i++) { + f->get_8(); + } + } +} + +Error ResourceLoaderCompatOBDB::_read_reals(real_t *r_dst, size_t p_count) { + if (f->real_is_double) { + if constexpr (sizeof(real_t) == 8) { + f->get_buffer((uint8_t *)r_dst, p_count * sizeof(double)); + } else { + for (size_t i = 0; i < p_count; ++i) { + r_dst[i] = f->get_double(); + } + } + } else { + if constexpr (sizeof(real_t) == 4) { + f->get_buffer((uint8_t *)r_dst, p_count * sizeof(float)); + } else { + for (size_t i = 0; i < p_count; ++i) { + r_dst[i] = f->get_float(); + } + } + } + return OK; +} + +Error ResourceLoaderCompatOBDB::_decode_image(Variant &r_v) { + uint32_t encoding = f->get_32(); + if (encoding == OBDB_IMAGE_ENCODING_EMPTY) { + Ref empty; + empty.instantiate(); + r_v = empty; + return OK; + } + + if (encoding != OBDB_IMAGE_ENCODING_RAW) { + // PNG/JPG codes are reserved but were never emitted by the pre-1.x saver. + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, + vformat("OBDB image uses unsupported encoding %d (only RAW=1 is implemented).", encoding)); + } + + uint32_t width = f->get_32(); + uint32_t height = f->get_32(); + uint32_t mipmaps = f->get_32(); + uint32_t format = f->get_32(); + uint32_t datalen = f->get_32(); + + Vector imgdata; + imgdata.resize(datalen); + if (datalen > 0) { + f->get_buffer(imgdata.ptrw(), datalen); + } + _advance_padding(datalen); + + Ref img; + + switch (format) { + case OBDB_IMAGE_FORMAT_GRAYSCALE: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_L8, imgdata); + } break; + case OBDB_IMAGE_FORMAT_GRAYSCALE_ALPHA: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_LA8, imgdata); + } break; + case OBDB_IMAGE_FORMAT_RGB: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_RGB8, imgdata); + } break; + case OBDB_IMAGE_FORMAT_RGBA: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_RGBA8, imgdata); + } break; + case OBDB_IMAGE_FORMAT_BC1: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_DXT1, imgdata); + } break; + case OBDB_IMAGE_FORMAT_BC2: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_DXT3, imgdata); + } break; + case OBDB_IMAGE_FORMAT_BC3: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_DXT5, imgdata); + } break; + case OBDB_IMAGE_FORMAT_BC4: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_RGTC_R, imgdata); + } break; + case OBDB_IMAGE_FORMAT_BC5: { + img = Image::create_from_data(width, height, mipmaps > 0, Image::FORMAT_RGTC_RG, imgdata); + } break; + case OBDB_IMAGE_FORMAT_INTENSITY: + case OBDB_IMAGE_FORMAT_INDEXED: + case OBDB_IMAGE_FORMAT_INDEXED_ALPHA: { + Error err; + V2Image::Format v2fmt = (format == OBDB_IMAGE_FORMAT_INTENSITY) + ? V2Image::IMAGE_FORMAT_INTENSITY + : ((format == OBDB_IMAGE_FORMAT_INDEXED) + ? V2Image::IMAGE_FORMAT_INDEXED + : V2Image::IMAGE_FORMAT_INDEXED_ALPHA); + img = ImageParserV2::convert_indexed_image(imgdata, width, height, mipmaps, v2fmt, &err); + ERR_FAIL_COND_V_MSG(err != OK, err, + vformat("OBDB image: failed to convert legacy format '%s'.", + ImageEnumCompat::get_v2_format_name(v2fmt))); + } break; + case OBDB_IMAGE_FORMAT_CUSTOM: + default: { + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, + vformat("OBDB image format %d is not supported (no decoder available).", format)); + } + } + + r_v = img; + return OK; +} + +Error ResourceLoaderCompatOBDB::parse_property(int &r_name_idx, Variant &r_v) { + uint32_t section = f->get_32(); + if (section == SECTION_END) { + return ERR_FILE_EOF; + } + ERR_FAIL_COND_V_MSG(section != SECTION_PROPERTY, ERR_FILE_CORRUPT, + vformat("OBDB: expected SECTION_PROPERTY (3) but got %d.", section)); + + r_name_idx = (int)f->get_32(); + return parse_variant(r_v); +} + +Error ResourceLoaderCompatOBDB::parse_variant(Variant &r_v) { + uint32_t type = f->get_32(); + + switch (type) { + case VARIANT_NIL: { + r_v = Variant(); + } break; + case VARIANT_BOOL: { + r_v = bool(f->get_32()); + } break; + case VARIANT_INT: { + r_v = int(f->get_32()); + } break; + case VARIANT_REAL: { + r_v = f->get_real(); + } break; + case VARIANT_STRING: { + r_v = get_unicode_string(); + } break; + case VARIANT_VECTOR2: { + Vector2 v; + v.x = f->get_real(); + v.y = f->get_real(); + r_v = v; + } break; + case VARIANT_RECT2: { + Rect2 v; + v.position.x = f->get_real(); + v.position.y = f->get_real(); + v.size.x = f->get_real(); + v.size.y = f->get_real(); + r_v = v; + } break; + case VARIANT_VECTOR3: { + Vector3 v; + v.x = f->get_real(); + v.y = f->get_real(); + v.z = f->get_real(); + r_v = v; + } break; + case VARIANT_PLANE: { + Plane v; + v.normal.x = f->get_real(); + v.normal.y = f->get_real(); + v.normal.z = f->get_real(); + v.d = f->get_real(); + r_v = v; + } break; + case VARIANT_QUAT: { + Quaternion v; + v.x = f->get_real(); + v.y = f->get_real(); + v.z = f->get_real(); + v.w = f->get_real(); + r_v = v; + } break; + case VARIANT_AABB: { + AABB v; + v.position.x = f->get_real(); + v.position.y = f->get_real(); + v.position.z = f->get_real(); + v.size.x = f->get_real(); + v.size.y = f->get_real(); + v.size.z = f->get_real(); + r_v = v; + } break; + case VARIANT_MATRIX32: { + Transform2D v; + v.columns[0].x = f->get_real(); + v.columns[0].y = f->get_real(); + v.columns[1].x = f->get_real(); + v.columns[1].y = f->get_real(); + v.columns[2].x = f->get_real(); + v.columns[2].y = f->get_real(); + r_v = v; + } break; + case VARIANT_MATRIX3: { + Basis v; + v.rows[0].x = f->get_real(); + v.rows[0].y = f->get_real(); + v.rows[0].z = f->get_real(); + v.rows[1].x = f->get_real(); + v.rows[1].y = f->get_real(); + v.rows[1].z = f->get_real(); + v.rows[2].x = f->get_real(); + v.rows[2].y = f->get_real(); + v.rows[2].z = f->get_real(); + r_v = v; + } break; + case VARIANT_TRANSFORM: { + Transform3D v; + v.basis.rows[0].x = f->get_real(); + v.basis.rows[0].y = f->get_real(); + v.basis.rows[0].z = f->get_real(); + v.basis.rows[1].x = f->get_real(); + v.basis.rows[1].y = f->get_real(); + v.basis.rows[1].z = f->get_real(); + v.basis.rows[2].x = f->get_real(); + v.basis.rows[2].y = f->get_real(); + v.basis.rows[2].z = f->get_real(); + v.origin.x = f->get_real(); + v.origin.y = f->get_real(); + v.origin.z = f->get_real(); + r_v = v; + } break; + case VARIANT_COLOR: { + // Pre-1.x colors are stored as 4 `real_t` (honoring `use_real64`), + // then narrowed to `float` for `Color`. + Color v; + v.r = (float)f->get_real(); + v.g = (float)f->get_real(); + v.b = (float)f->get_real(); + v.a = (float)f->get_real(); + r_v = v; + } break; + case VARIANT_IMAGE: { + Error err = _decode_image(r_v); + if (err != OK) { + return err; + } + } break; + case VARIANT_NODE_PATH: { + r_v = NodePath(get_unicode_string()); + } break; + case VARIANT_RID: { + r_v = (int)f->get_32(); + } break; + case VARIANT_OBJECT: { + uint32_t objtype = f->get_32(); + switch (objtype) { + case OBDB_OBJECT_EMPTY: { + r_v = Ref(); + } break; + case OBDB_OBJECT_INTERNAL_RESOURCE: { + uint32_t index = f->get_32(); + if ((int)index >= internal_resources.size()) { + WARN_PRINT(vformat("OBDB: OBJECT_INTERNAL_RESOURCE refers to missing index %d.", index)); + r_v = Variant(); + break; + } + const String &cache_key = internal_resources[index].path; + if (!internal_index_cache.has(cache_key)) { + WARN_PRINT(vformat("Couldn't load resource (no cache): %s.", cache_key)); + r_v = Variant(); + } else { + r_v = internal_index_cache[cache_key]; + } + } break; + case OBDB_OBJECT_EXTERNAL_RESOURCE: { + String exttype = get_unicode_string(); + String path = get_unicode_string(); + if (!path.contains("://") && path.is_relative_path()) { + path = GDRESettings::get_singleton()->localize_path(res_path.get_base_dir().path_join(path)); + } + if (remaps.find(path)) { + path = remaps[path]; + } + Error err = OK; + Ref res; + if (!is_real_load()) { + res = CompatFormatLoader::create_missing_external_resource(path, exttype, ResourceUID::INVALID_ID); + } else { + res = ResourceCompatLoader::custom_load(path, exttype, load_type, &err, use_sub_threads, cache_mode_for_external); + } + if (res.is_null()) { + WARN_PRINT(vformat("Couldn't load resource: %s.", path)); + } + r_v = res; + } break; + default: { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("OBDB: unrecognized OBJECT subtype %d.", objtype)); + } + } + } break; + case VARIANT_INPUT_EVENT: { + // Pre-1.x InputEvents were never persisted; the type id was reserved but no body was ever written. + WARN_PRINT_ONCE("OBDB: encountered VARIANT_INPUT_EVENT, returning nil (legacy formats never serialized event data)."); + r_v = Variant(); + } break; + case VARIANT_DICTIONARY: { + uint32_t len = f->get_32(); + Dictionary d; + for (uint32_t i = 0; i < len; i++) { + int idx = 0; + Variant key; + Error err = parse_property(idx, key); + if (err == ERR_UNAVAILABLE) { + return err; + } + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CORRUPT, "OBDB: dictionary key parse failed."); + Variant value; + err = parse_property(idx, value); + if (err == ERR_UNAVAILABLE) { + return err; + } + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CORRUPT, "OBDB: dictionary value parse failed."); + d[key] = value; + } + r_v = d; + } break; + case VARIANT_ARRAY: { + uint32_t len = f->get_32(); + Array a; + a.resize(len); + for (uint32_t i = 0; i < len; i++) { + int idx = 0; + Variant val; + Error err = parse_property(idx, val); + if (err == ERR_UNAVAILABLE) { + return err; + } + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CORRUPT, "OBDB: array element parse failed."); + a[i] = val; + } + r_v = a; + } break; + case VARIANT_RAW_ARRAY: { + uint32_t len = f->get_32(); + Vector array; + array.resize(len); + if (len > 0) { + f->get_buffer(array.ptrw(), len); + } + _advance_padding(len); + r_v = array; + } break; + case VARIANT_INT_ARRAY: { + uint32_t len = f->get_32(); + Vector array; + array.resize(len); + int32_t *w = array.ptrw(); + for (uint32_t i = 0; i < len; i++) { + w[i] = (int32_t)f->get_32(); + } + r_v = array; + } break; + case VARIANT_REAL_ARRAY: { + uint32_t len = f->get_32(); + Vector array; + array.resize(len); + float *w = array.ptrw(); + for (uint32_t i = 0; i < len; i++) { + // Always store into a `float` PackedFloat32Array; pre-1.x + // clients used PackedFloat32Array semantics regardless of `real_t` + // width, but values are still serialized as `real_t`. + w[i] = (float)f->get_real(); + } + r_v = array; + } break; + case VARIANT_STRING_ARRAY: { + uint32_t len = f->get_32(); + Vector array; + array.resize(len); + String *w = array.ptrw(); + for (uint32_t i = 0; i < len; i++) { + w[i] = get_unicode_string(); + } + r_v = array; + } break; + case VARIANT_VECTOR3_ARRAY: { + uint32_t len = f->get_32(); + Vector array; + array.resize(len); + Vector3 *w = array.ptrw(); + static_assert(sizeof(Vector3) == 3 * sizeof(real_t)); + Error err = _read_reals(reinterpret_cast(w), len * 3); + ERR_FAIL_COND_V(err != OK, err); + r_v = array; + } break; + case VARIANT_COLOR_ARRAY: { + uint32_t len = f->get_32(); + Vector array; + array.resize(len); + Color *w = array.ptrw(); + // Pre-1.x stored colors as 4 `real_t`. Modern `Color` is 4 `float`, + // so we read element-wise and narrow if needed. + for (uint32_t i = 0; i < len; i++) { + w[i].r = (float)f->get_real(); + w[i].g = (float)f->get_real(); + w[i].b = (float)f->get_real(); + w[i].a = (float)f->get_real(); + } + r_v = array; + } break; + case VARIANT_VECTOR2_ARRAY: { + uint32_t len = f->get_32(); + Vector array; + array.resize(len); + Vector2 *w = array.ptrw(); + static_assert(sizeof(Vector2) == 2 * sizeof(real_t)); + Error err = _read_reals(reinterpret_cast(w), len * 2); + ERR_FAIL_COND_V(err != OK, err); + r_v = array; + } break; + default: { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("OBDB: unrecognized VARIANT type %d.", type)); + } + } + + return OK; +} + +bool ResourceLoaderCompatOBDB::_read_header(Ref p_f, bool p_load_strings) { + error = OK; + f = p_f; + + uint8_t header[4]; + f->get_buffer(header, 4); + if (header[0] != 'O' || header[1] != 'B' || header[2] != 'D' || header[3] != 'B') { + error = ERR_FILE_UNRECOGNIZED; + return false; + } + + uint32_t big_endian = f->get_32(); + uint32_t use_real64 = f->get_32(); + + f->set_big_endian(big_endian != 0); + + ver_major = f->get_32(); + ver_minor = f->get_32(); + + stored_big_endian = big_endian != 0; + stored_use_real64 = use_real64 != 0; + f->real_is_double = stored_use_real64; + using_real_t_double = stored_use_real64; + + type_magic = get_unicode_string(); + for (int i = 0; i < OBDB_RESERVED_FIELDS; i++) { + f->get_32(); + } + + if (f->eof_reached()) { + error = ERR_FILE_CORRUPT; + return false; + } + + if (type_magic == "RESOURCE") { + is_scene_mode = false; + } else if (type_magic == "SCENE") { + is_scene_mode = true; + } else { + error = ERR_FILE_UNRECOGNIZED; + return false; + } + + if (p_load_strings) { + uint32_t string_table_size = f->get_32(); + string_map.resize(string_table_size); + bin_meta_string_idx = -1; + for (uint32_t i = 0; i < string_table_size; i++) { + StringName s = get_unicode_string(); + string_map.write[i] = s; + if (bin_meta_string_idx < 0 && s == StringName("__bin_meta__")) { + bin_meta_string_idx = (int)i; + } + } + } + + return true; +} + +bool ResourceLoaderCompatOBDB::_index_sections() { + while (true) { + if (f->eof_reached()) { + error = ERR_FILE_CORRUPT; + return false; + } + + uint32_t section = f->get_32(); + if (section == SECTION_END) { + return true; + } + if (section != SECTION_RESOURCE && section != SECTION_OBJECT && section != SECTION_META_OBJECT) { + error = ERR_FILE_CORRUPT; + return false; + } + + uint64_t section_end = f->get_64(); + // `section_end` is the absolute file offset of the byte right after the + // SECTION_END marker that closes this section (matches what the saver + // wrote back into the placeholder). We use it both to skip past the + // section during the index pass and to seek back during load. + + if (section == SECTION_RESOURCE) { + InternalResource ir; + ir.type = get_unicode_string(); + String stored_path = get_unicode_string(); + if (stored_path.begins_with("local://")) { + ir.is_local_id = true; + String id = stored_path.replace_first("local://", ""); + ir.path = local_path + "::" + id; + } else { + ir.is_local_id = false; + ir.path = stored_path; + } + ir.body_offset = f->get_position(); + ir.end_offset = section_end; + internal_resources.push_back(ir); + } else if (section == SECTION_OBJECT) { + SceneNodeEntry e; + e.kind = SECTION_OBJECT; + e.type = get_unicode_string(); + e.body_offset = f->get_position(); + e.end_offset = section_end; + scene_nodes.push_back(e); + } else { // SECTION_META_OBJECT + SceneNodeEntry e; + e.kind = SECTION_META_OBJECT; + e.type = String(); + e.body_offset = f->get_position(); + e.end_offset = section_end; + scene_nodes.push_back(e); + } + + f->seek(section_end); + } +} + +void ResourceLoaderCompatOBDB::open(Ref p_f, bool p_no_resources) { + if (!_read_header(p_f, true)) { + f.unref(); + return; + } + + if (p_no_resources) { + return; + } + + if (!_index_sections()) { + f.unref(); + return; + } + + // Sanity check on mode vs. observed sections. + if (!is_scene_mode) { + // Resource mode expects exactly one trailing SECTION_OBJECT (the main + // resource) plus zero or more SECTION_RESOURCE entries. + int object_count = 0; + for (const SceneNodeEntry &e : scene_nodes) { + if (e.kind == SECTION_OBJECT) { + object_count++; + } + } + if (object_count != 1 || (scene_nodes.size() > 0 && scene_nodes[scene_nodes.size() - 1].kind != SECTION_OBJECT)) { + WARN_PRINT(vformat("OBDB '%s': resource-mode file has %d top-level SECTION_OBJECT entries (expected 1).", + local_path, object_count)); + } + } +} + +String ResourceLoaderCompatOBDB::recognize(Ref p_f) { + if (!_read_header(p_f, false)) { + return String(); + } + return type_magic; +} + +void ResourceLoaderCompatOBDB::get_dependencies(Ref p_f, List *p_dependencies, bool p_add_types) { + open(p_f, false); + if (error != OK) { + return; + } + + // Pre-1.x has no dedicated external-resource table. Walk every section's + // properties looking for OBJECT_EXTERNAL_RESOURCE and SECTION_RESOURCE + // entries with non-`local://` paths; together those represent all external + // references this file might pull in. + auto add_dep = [&](const String &p_path, const String &p_type) { + String dep = p_path; + if (p_add_types && !p_type.is_empty()) { + dep += "::" + p_type; + } + p_dependencies->push_back(dep); + }; + + for (const InternalResource &ir : internal_resources) { + if (!ir.is_local_id) { + add_dep(ir.path, ir.type); + } + } + + // Walk every section once, scanning property bodies for inline external + // references. We don't need to actually instantiate anything here, so we + // rely on parse_variant() with empty caches; nested OBJECT_INTERNAL_RESOURCE + // references will resolve as "no cache" warnings, which we silence by + // skipping past sub-resource sections instead. + for (int idx = 0; idx < internal_resources.size(); idx++) { + f->seek(internal_resources[idx].body_offset); + while (true) { + int name_idx = 0; + Variant v; + Error err = parse_property(name_idx, v); + if (err == ERR_FILE_EOF) { + break; + } + if (err != OK) { + break; + } + } + } + + for (const SceneNodeEntry &node : scene_nodes) { + f->seek(node.body_offset); + while (true) { + int name_idx = 0; + Variant v; + Error err = parse_property(name_idx, v); + if (err == ERR_FILE_EOF) { + break; + } + if (err != OK) { + break; + } + } + } +} + +Error ResourceLoaderCompatOBDB::_load_section_properties(Object *p_obj, Dictionary *r_meta, Dictionary *r_missing_resource_properties) { + while (true) { + int name_idx = 0; + Variant value; + Error err = parse_property(name_idx, value); + if (err == ERR_FILE_EOF) { + return OK; + } + if (err != OK) { + return err; + } + + // Pre-1.x meta dict is always at name_idx 0 (the saver inserts + // "__bin_meta__" first), but we double-check via the string table to be + // resilient to malformed inputs. + bool is_meta = (r_meta != nullptr) && (name_idx == bin_meta_string_idx || (name_idx >= 0 && name_idx < string_map.size() && string_map[name_idx] == StringName("__bin_meta__"))); + + if (is_meta) { + *r_meta = value; + continue; + } + + if (!p_obj) { + // META_OBJECT entries should never carry non-meta properties; tolerate + // stray ones by ignoring them. + continue; + } + + ERR_FAIL_INDEX_V(name_idx, string_map.size(), ERR_FILE_CORRUPT); + StringName name = string_map[name_idx]; + if (name == StringName()) { + return ERR_FILE_CORRUPT; + } + + StringName mapped = name; + // `resource/name` was renamed to `resource_name` in 3.x; align here for + // pre-1.x to keep parity with the v1/v2 binary loader. + if (ver_major <= 2 && name == StringName("resource/name")) { + mapped = StringName("resource_name"); + } + + bool valid = false; + p_obj->set(mapped, value, &valid); + if (!valid && r_missing_resource_properties) { + (*r_missing_resource_properties)[name] = value; + } + } +} + +Error ResourceLoaderCompatOBDB::_load_internal_resources_phase() { + // Resolve sub-resource paths (cache keys) up-front so OBJECT_INTERNAL_RESOURCE + // references parsed mid-flight have stable lookups. + for (int i = 0; i < internal_resources.size(); i++) { + String path = internal_resources[i].path; + String id; + if (internal_resources[i].is_local_id) { + // Path is `local_path::N`; the id is the trailing segment. + id = path.substr(path.rfind("::") + 2); + } + + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE && ResourceCache::has(path)) { + Ref cached = ResourceCache::get_ref(path); + if (cached.is_valid()) { + internal_index_cache[path] = cached; + continue; + } + } + + f->seek(internal_resources[i].body_offset); + + const String &t = internal_resources[i].type; + + Ref res; + Resource *r = nullptr; + Ref missing_resource; + Ref converter; + bool fake_script = false; + + auto init_missing_resource([&](bool no_fake_script) { + Ref nres = CompatFormatLoader::create_missing_internal_resource(path, t, id, no_fake_script); + res = nres; + if (res->get_class() == "MissingResource") { + missing_resource = res; + } else { + fake_script = true; + } + }); + + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { + Ref cached = ResourceCache::get_ref(path); + if (cached.is_valid() && cached->get_class() == t) { + cached->reset_state(); + res = cached; + r = res.ptr(); + } + } + + if (load_type == ResourceInfo::FAKE_LOAD) { + init_missing_resource(false); + } else if (res.is_null()) { + converter = ResourceCompatLoader::get_converter_for_type(t, ver_major); + if (converter.is_valid()) { + init_missing_resource(true); + } + } + + if (res.is_null()) { + Object *obj = (ClassDB::class_exists(t) || !ClassDB::get_compatibility_class(t).is_empty()) + ? ClassDB::instantiate(t) + : nullptr; + if (!obj) { + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_resource = memnew(MissingResource); + missing_resource->set_original_class(t); + missing_resource->set_recording_properties(true); + obj = missing_resource.ptr(); + } else { + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, + vformat("'%s': OBDB resource of unrecognized type '%s'.", local_path, t)); + } + } + r = Object::cast_to(obj); + if (!r) { + String obj_class = obj->get_class(); + memdelete(obj); + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, + vformat("'%s': OBDB SECTION_RESOURCE type '%s' is not a Resource.", local_path, obj_class)); + } + res = Ref(r); + } + + if (r) { + if (!path.is_empty()) { + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + } else { + r->set_path_cache(path); + } + } + r->set_scene_unique_id(id); + } else if (converter.is_null() && !is_real_load()) { + if (!path.is_empty()) { + res->set_path_cache(path); + } + res->set_scene_unique_id(id); + } + + internal_index_cache[path] = res; + + Object *target = r ? (Object *)r : (Object *)missing_resource.ptr(); + if (fake_script) { + target = res.ptr(); + } + + Dictionary missing_resource_properties; + Error perr = _load_section_properties(target, nullptr, &missing_resource_properties); + if (perr != OK) { + error = perr; + return perr; + } + + if (missing_resource.is_valid()) { + missing_resource->set_recording_properties(false); + if (converter.is_valid()) { + Ref compat = ResourceInfo::get_info_from_resource(missing_resource); + if (compat.is_valid()) { + Error cerr = OK; + Ref new_res = converter->convert(missing_resource, load_type, ver_major, &cerr); + if (cerr == OK && new_res.is_valid()) { + res = new_res; + if (!ResourceInfo::resource_has_info(res)) { + compat->set_on_resource(res); + } + if (!path.is_empty()) { + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && is_real_load()) { + res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + } else { + res->set_path_cache(path); + } + } + res->set_scene_unique_id(id); + internal_index_cache[path] = res; + } + } + } + } + + if (!missing_resource_properties.is_empty()) { + res->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } + +#ifdef TOOLS_ENABLED + res->set_edited(false); +#endif + set_internal_resource_compat_meta(path, id, t, res); + resource_cache.push_back(res); + } + + return OK; +} + +namespace { + +// Resolve a target NodePath that is *relative to the source node* into an +// absolute path-from-root using filesystem-style semantics. +String _resolve_relative_to_abs(const String &p_source_abs, const NodePath &p_target_relative) { + Vector stack; + if (!p_source_abs.is_empty() && p_source_abs != ".") { + stack = p_source_abs.split("/", false); + } + + for (int i = 0; i < p_target_relative.get_name_count(); i++) { + String n = p_target_relative.get_name(i); + if (n == "..") { + if (!stack.is_empty()) { + stack.remove_at(stack.size() - 1); + } + } else if (n == "." || n.is_empty()) { + continue; + } else { + stack.push_back(n); + } + } + + if (stack.is_empty()) { + return String(); + } + return String("/").join(stack); +} + +} // namespace + +Error ResourceLoaderCompatOBDB::_build_packed_scene() { + const int node_count = scene_nodes.size(); + if (node_count == 0) { + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("OBDB scene '%s' has no nodes.", local_path)); + } + + struct ParsedNode { + uint32_t kind = 0; + String type; // empty for instance/META_OBJECT + Dictionary meta; + Vector> properties; // node-level (non-meta) properties + + String name; + String parent_path; // relative to root (saver writes "p_root->get_path_to(p_node->get_parent())"). + String abs_path; + bool is_root = false; + + bool has_owner_id = false; + uint32_t owner_id = 0; + bool has_owner = false; + uint32_t owner = 0; + + Vector groups; + Vector connections; + + // Instance handling. + String instance_path; + Vector override_names; + Array override_values; + }; + + LocalVector parsed_nodes; + parsed_nodes.resize(node_count); + + // First pass: parse each node's properties and meta dict. + for (int i = 0; i < node_count; i++) { + ParsedNode &p = parsed_nodes[i]; + p.kind = scene_nodes[i].kind; + p.type = scene_nodes[i].type; + + f->seek(scene_nodes[i].body_offset); + + // We can't reuse _load_section_properties here because we want both meta + // and node-level properties separated. + while (true) { + int name_idx = 0; + Variant value; + Error err = parse_property(name_idx, value); + if (err == ERR_FILE_EOF) { + break; + } + if (err != OK) { + error = err; + return err; + } + + bool is_meta = (name_idx == bin_meta_string_idx) || + (name_idx >= 0 && name_idx < string_map.size() && string_map[name_idx] == StringName("__bin_meta__")); + if (is_meta) { + p.meta = value; + continue; + } + + if (p.kind == SECTION_OBJECT) { + ERR_FAIL_INDEX_V(name_idx, string_map.size(), ERR_FILE_CORRUPT); + StringName n = string_map[name_idx]; + p.properties.push_back(Pair(n, value)); + } + // META_OBJECT entries shouldn't have non-meta properties; tolerate by ignoring. + } + + // Decode meta. + Dictionary &d = p.meta; + p.is_root = (i == 0); + if (d.has("name")) { + p.name = d["name"]; + } + if (d.has("path")) { + p.parent_path = String(((NodePath)d["path"])); + } + if (d.has("groups")) { + Vector g = d["groups"]; + p.groups = g; + } + int conn_count = d.has("connection_count") ? (int)d["connection_count"] : 0; + for (int c = 0; c < conn_count; c++) { + String key = "connection/" + itos(c + 1); + if (d.has(key)) { + Dictionary cd = d[key]; + p.connections.push_back(cd); + } + } + if (d.has("owner_id")) { + p.has_owner_id = true; + p.owner_id = (uint32_t)(int)d["owner_id"]; + } + if (d.has("owner")) { + p.has_owner = true; + p.owner = (uint32_t)(int)d["owner"]; + } + if (d.has("instance")) { + p.instance_path = d["instance"]; + } + if (d.has("override_names")) { + Vector on = d["override_names"]; + p.override_names = on; + } + if (d.has("override_values")) { + p.override_values = d["override_values"]; + } + } + + // Compute absolute paths and the abs_path → idx map. + HashMap abs_path_to_idx; + for (int i = 0; i < node_count; i++) { + ParsedNode &p = parsed_nodes[i]; + if (p.is_root) { + p.abs_path = String(); // root: empty key + } else if (p.parent_path.is_empty() || p.parent_path == ".") { + p.abs_path = p.name; + } else { + p.abs_path = p.parent_path + "/" + p.name; + } + abs_path_to_idx[p.abs_path] = i; + } + + // Build owner_id → idx mapping (the root is implicit owner_id 0 in the saver). + HashMap owner_map; + owner_map[0] = 0; // The root always owns itself; saver special-cases p_root. + for (int i = 0; i < node_count; i++) { + const ParsedNode &p = parsed_nodes[i]; + if (p.has_owner_id) { + owner_map[p.owner_id] = i; + } + } + + // Build SceneState bundle dict via name/variant interning. + HashMap name_map; + Vector names; + HashMap variant_map; + Array variants; + + auto intern_name = [&](const StringName &p_name) -> int { + HashMap::Iterator it = name_map.find(p_name); + if (it) { + return it->value; + } + int idx = names.size(); + names.push_back(p_name); + name_map[p_name] = idx; + return idx; + }; + + auto intern_variant = [&](const Variant &p_v) -> int { + HashMap::Iterator it = variant_map.find(p_v); + if (it) { + return it->value; + } + int idx = variants.size(); + variants.push_back(p_v); + variant_map[p_v] = idx; + return idx; + }; + + Vector rnodes; + for (int i = 0; i < node_count; i++) { + const ParsedNode &p = parsed_nodes[i]; + + // `parent` field: index of parent node, or NO_PARENT_SAVED for the root. + int parent_idx; + if (p.is_root) { + parent_idx = OBDB_SCENE_NO_PARENT_SAVED; + } else { + HashMap::Iterator it = abs_path_to_idx.find( + (p.parent_path.is_empty() || p.parent_path == ".") ? String() : p.parent_path); + if (!it) { + WARN_PRINT(vformat("OBDB scene '%s': node '%s' references missing parent '%s'.", + local_path, p.name, p.parent_path)); + parent_idx = 0; // fall back to root + } else { + parent_idx = it->value; + } + } + + // `owner` field: index of owner node (root for top-level nodes). + int owner_idx; + if (p.is_root) { + owner_idx = 0; + } else if (p.has_owner) { + HashMap::Iterator oit = owner_map.find(p.owner); + owner_idx = oit ? oit->value : 0; + } else { + owner_idx = 0; + } + + // `type`: index into name table, or TYPE_INSTANTIATED for the root of an + // instanced subscene. + int type_idx; + int instance_field; + if (p.kind == SECTION_META_OBJECT && !p.instance_path.is_empty()) { + // Instance node (no real Object data, only meta + overrides). + Ref ext; + Error eerr = OK; + if (!is_real_load()) { + ext = CompatFormatLoader::create_missing_external_resource(p.instance_path, "PackedScene", ResourceUID::INVALID_ID); + } else { + ext = ResourceCompatLoader::custom_load(p.instance_path, "PackedScene", load_type, &eerr, use_sub_threads, cache_mode_for_external); + } + type_idx = (int)SceneState::TYPE_INSTANTIATED; + instance_field = intern_variant(ext); + } else { + StringName tname = p.type.is_empty() ? StringName("Node") : StringName(p.type); + type_idx = intern_name(tname); + instance_field = -1; + } + + // `name` field: combined `name_idx | (index+1) << NAME_INDEX_BITS`. + int name_idx_in_table = intern_name(StringName(p.name)); + uint32_t name_field = (uint32_t)name_idx_in_table & OBDB_SCENE_NAME_MASK; + // `index` is the per-parent ordering used for stable child indices. For + // pre-1.x scenes we don't have it explicitly, so leave it at 0 (encoded + // as "no index" by the SceneState bundle reader: `name_index` without the + // upper bits set means index = -1, which means "append"). + + rnodes.push_back(parent_idx); + rnodes.push_back(owner_idx); + rnodes.push_back(type_idx); + rnodes.push_back((int)name_field); + rnodes.push_back(instance_field); + + // Properties: real Object props for SECTION_OBJECT nodes; for + // META_OBJECT (instance) nodes we synthesize them from + // override_names/override_values. + if (p.kind == SECTION_META_OBJECT && !p.instance_path.is_empty()) { + const int oc = MIN(p.override_names.size(), p.override_values.size()); + rnodes.push_back(oc); + for (int j = 0; j < oc; j++) { + rnodes.push_back(intern_name(StringName(p.override_names[j]))); + rnodes.push_back(intern_variant(p.override_values[j])); + } + } else { + rnodes.push_back(p.properties.size()); + for (int j = 0; j < p.properties.size(); j++) { + rnodes.push_back(intern_name(p.properties[j].first)); + rnodes.push_back(intern_variant(p.properties[j].second)); + } + } + + // Groups. + rnodes.push_back(p.groups.size()); + for (int j = 0; j < p.groups.size(); j++) { + rnodes.push_back(intern_name(StringName(p.groups[j]))); + } + } + + // Connections. + Vector rconns; + int total_conn_count = 0; + for (int i = 0; i < node_count; i++) { + const ParsedNode &p = parsed_nodes[i]; + for (const Dictionary &cd : p.connections) { + NodePath target_rel = cd.has("target") ? (NodePath)cd["target"] : NodePath("."); + String abs_target = _resolve_relative_to_abs(p.abs_path, target_rel); + HashMap::Iterator tit = abs_path_to_idx.find(abs_target); + if (!tit) { + // Fallback: try the literal target path (handles edge cases where + // the target is the source itself). + tit = abs_path_to_idx.find(String(target_rel)); + if (!tit) { + WARN_PRINT(vformat("OBDB scene '%s': could not resolve connection target '%s' from '%s'.", + local_path, String(target_rel), p.abs_path)); + continue; + } + } + int from_idx = i; + int to_idx = tit->value; + int signal_idx = intern_name(cd.has("signal") ? StringName((String)cd["signal"]) : StringName()); + int method_idx = intern_name(cd.has("method") ? StringName((String)cd["method"]) : StringName()); + bool realtime = cd.has("realtime") ? (bool)cd["realtime"] : true; + int flags = realtime ? 0 : OBDB_CONNECT_DEFERRED; + Array binds = cd.has("binds") ? (Array)cd["binds"] : Array(); + + rconns.push_back(from_idx); + rconns.push_back(to_idx); + rconns.push_back(signal_idx); + rconns.push_back(method_idx); + rconns.push_back(flags); + rconns.push_back(binds.size()); + for (int b = 0; b < binds.size(); b++) { + rconns.push_back(intern_variant(binds[b])); + } + total_conn_count++; + } + } + + // Materialize the bundle dictionary in the layout consumed by + // SceneState::set_bundled_scene(). + Dictionary bundle; + { + Vector rnames; + rnames.resize(names.size()); + for (int i = 0; i < names.size(); i++) { + rnames.write[i] = names[i]; + } + bundle["names"] = rnames; + } + bundle["version"] = 1; + bundle["conn_count"] = total_conn_count; + bundle["node_count"] = node_count; + bundle["variants"] = variants; + bundle["nodes"] = rnodes; + bundle["conns"] = rconns; + + packed_scene_version = 1; + + if (load_type == ResourceInfo::FAKE_LOAD) { + Ref mr = CompatFormatLoader::create_missing_main_resource(local_path, "PackedScene", ResourceUID::INVALID_ID, true); + mr->set_recording_properties(true); + mr->set("_bundled", bundle); + mr->set_recording_properties(false); + resource = mr; + } else { + Ref ps; + ps.instantiate(); + ps->set("_bundled", bundle); + if (!local_path.is_empty()) { + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + ps->set_path(local_path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + } else { + ps->set_path_cache(local_path); + } + } + resource = ps; + } + + set_compat_meta(resource); + return OK; +} + +Error ResourceLoaderCompatOBDB::load() { + if (error != OK) { + return error; + } + + Error err = _load_internal_resources_phase(); + if (err != OK) { + return err; + } + + if (is_scene_mode) { + return _build_packed_scene(); + } + + // Resource mode: the (single) trailing SECTION_OBJECT is the main resource. + if (scene_nodes.is_empty() || scene_nodes[scene_nodes.size() - 1].kind != SECTION_OBJECT) { + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, + vformat("OBDB resource '%s' has no main SECTION_OBJECT.", local_path)); + } + + const SceneNodeEntry &main_entry = scene_nodes[scene_nodes.size() - 1]; + const String &t = main_entry.type; + type = t; + + String path; + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && !ResourceCache::has(res_path)) { + path = res_path; + } + + f->seek(main_entry.body_offset); + + Ref res; + Resource *r = nullptr; + Ref missing_resource; + Ref converter; + bool fake_script = false; + + auto init_missing_resource([&](bool no_fake_script) { + Ref nres = CompatFormatLoader::create_missing_main_resource(path, t, ResourceUID::INVALID_ID, no_fake_script); + res = nres; + if (res->get_class() == "MissingResource") { + missing_resource = res; + } else { + fake_script = true; + } + }); + + if (is_real_load()) { + res = ResourceLoader::get_resource_ref_override(local_path); + r = res.ptr(); + } + + if (!r) { + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { + Ref cached = ResourceCache::get_ref(path); + if (cached.is_valid() && cached->get_class() == t) { + cached->reset_state(); + res = cached; + } + } + + if (load_type == ResourceInfo::FAKE_LOAD) { + init_missing_resource(false); + } else if (res.is_null()) { + converter = ResourceCompatLoader::get_converter_for_type(t, ver_major); + if (converter.is_valid()) { + init_missing_resource(true); + } + } + + if (res.is_null()) { + Object *obj = (ClassDB::class_exists(t) || !ClassDB::get_compatibility_class(t).is_empty()) + ? ClassDB::instantiate(t) + : nullptr; + if (!obj) { + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_resource = memnew(MissingResource); + missing_resource->set_original_class(t); + missing_resource->set_recording_properties(true); + obj = missing_resource.ptr(); + } else { + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, + vformat("'%s': OBDB main type '%s' could not be instantiated.", local_path, t)); + } + } + r = Object::cast_to(obj); + if (!r) { + String obj_class = obj->get_class(); + memdelete(obj); + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, + vformat("'%s': OBDB main object class '%s' is not a Resource.", local_path, obj_class)); + } + res = Ref(r); + } + } + + if (r) { + if (!path.is_empty()) { + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + } else { + r->set_path_cache(path); + } + } + } else if (converter.is_null() && !is_real_load()) { + if (!path.is_empty()) { + res->set_path_cache(path); + } + } + + Object *target = r ? (Object *)r : (Object *)missing_resource.ptr(); + if (fake_script) { + target = res.ptr(); + } + + Dictionary missing_resource_properties; + Error perr = _load_section_properties(target, nullptr, &missing_resource_properties); + if (perr != OK) { + error = perr; + return perr; + } + + if (missing_resource.is_valid()) { + missing_resource->set_recording_properties(false); + if (converter.is_valid()) { + Ref compat = ResourceInfo::get_info_from_resource(missing_resource); + if (compat.is_valid()) { + Error cerr = OK; + Ref new_res = converter->convert(missing_resource, load_type, ver_major, &cerr); + if (cerr == OK && new_res.is_valid()) { + res = new_res; + if (!ResourceInfo::resource_has_info(res)) { + compat->set_on_resource(res); + } + if (!path.is_empty()) { + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && is_real_load()) { + res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + } else { + res->set_path_cache(path); + } + } + } + } + } + } + + if (!missing_resource_properties.is_empty()) { + res->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } + +#ifdef TOOLS_ENABLED + res->set_edited(false); +#endif + + resource = res; + if (res.is_valid() && res->get_save_class() == "PackedScene") { + Dictionary _bundled = res->get("_bundled"); + packed_scene_version = (int)_bundled.get("version", 1); + } + + if (is_real_load()) { + resource->set_as_translation_remapped(translation_remapped); + } + set_compat_meta(resource); + return OK; +} + +Ref ResourceLoaderCompatOBDB::get_resource() { + return resource; +} + +void ResourceLoaderCompatOBDB::_set_main_resource_info(Ref &r_info) { + r_info->uid = ResourceUID::INVALID_ID; + r_info->topology_type = ResourceInfo::MAIN_RESOURCE; + r_info->type = is_scene_mode ? String("PackedScene") : type; + r_info->original_path = local_path; + r_info->resource_name = resource.is_valid() ? resource->get_name() : ""; + r_info->ver_major = ver_major; + r_info->ver_minor = ver_minor; + r_info->ver_format = 0; + r_info->suspect_version = false; + r_info->resource_format = "binary_obdb"; + r_info->load_type = load_type; + r_info->using_real_t_double = using_real_t_double; + r_info->stored_use_real64 = stored_use_real64; + r_info->stored_big_endian = stored_big_endian; + r_info->using_named_scene_ids = false; + r_info->using_uids = false; + r_info->script_class = ""; + r_info->is_compressed = false; + if (packed_scene_version >= 0) { + r_info->packed_scene_version = packed_scene_version; + } else if (is_scene_mode) { + r_info->packed_scene_version = 1; + } +} + +void ResourceLoaderCompatOBDB::set_internal_resource_compat_meta(const String &p_path, const String &p_scene_id, const String &p_type, Ref &r_res) { + Ref r_info = ResourceInfo::get_info_from_resource(r_res); + if (!r_info.is_valid()) { + r_info.instantiate(); + r_info->type = p_type; + } + r_info->topology_type = ResourceInfo::INTERNAL_RESOURCE; + r_info->original_path = p_path; + r_info->cached_id = p_scene_id; + r_info->resource_name = r_res->get_name(); + if (r_info->type.is_empty()) { + r_info->type = p_type; + } + r_info->ver_major = ver_major; + r_info->ver_minor = ver_minor; + r_info->ver_format = 0; + r_info->suspect_version = false; + r_info->resource_format = "binary_obdb"; + r_info->using_real_t_double = using_real_t_double; + r_info->stored_use_real64 = stored_use_real64; + r_info->stored_big_endian = stored_big_endian; + r_info->using_named_scene_ids = false; + r_info->using_uids = false; + r_info->is_compressed = false; + r_info->set_on_resource(r_res); +} + +void ResourceLoaderCompatOBDB::set_compat_meta(Ref &r_res) { + Ref compat = ResourceInfo::get_info_from_resource(r_res); + if (compat.is_null()) { + compat.instantiate(); + } + _set_main_resource_info(compat); + compat->set_on_resource(r_res); +} + +Ref ResourceLoaderCompatOBDB::get_resource_info() { + Ref info; + info.instantiate(); + _set_main_resource_info(info); + return info; +} + +// +// ResourceFormatLoaderCompatOBDB +// + +ResourceFormatLoaderCompatOBDB *ResourceFormatLoaderCompatOBDB::singleton = nullptr; + +bool ResourceFormatLoaderCompatOBDB::is_obdb_resource(const String &p_path) { + Ref f = FileAccess::open(p_path, FileAccess::READ); + return is_obdb_resource_file(f); +} + +bool ResourceFormatLoaderCompatOBDB::is_obdb_resource_file(const Ref &p_f) { + if (p_f.is_null()) { + return false; + } + p_f->seek(0); + uint8_t header[4]; + if (p_f->get_buffer(header, 4) != 4) { + p_f->seek(0); + return false; + } + p_f->seek(0); + return header[0] == 'O' && header[1] == 'B' && header[2] == 'D' && header[3] == 'B'; +} + +Error ResourceFormatLoaderCompatOBDB::get_ver_major_minor(const String &p_path, uint32_t &r_ver_major, uint32_t &r_ver_minor, bool &r_suspicious) { + Error err = OK; + Ref f = FileAccess::open(p_path, FileAccess::READ, &err); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, vformat("Cannot open file '%s'.", p_path)); + return get_ver_major_minor_file(f, r_ver_major, r_ver_minor, r_suspicious); +} + +Error ResourceFormatLoaderCompatOBDB::get_ver_major_minor_file(const Ref &p_f, uint32_t &r_ver_major, uint32_t &r_ver_minor, bool &r_suspicious) { + if (p_f.is_null()) { + return ERR_FILE_CANT_OPEN; + } + p_f->seek(0); + uint8_t header[4]; + if (p_f->get_buffer(header, 4) != 4) { + return ERR_FILE_CORRUPT; + } + if (header[0] != 'O' || header[1] != 'B' || header[2] != 'D' || header[3] != 'B') { + return ERR_FILE_UNRECOGNIZED; + } + + uint32_t big_endian = p_f->get_32(); + p_f->get_32(); //use_real64 + + p_f->set_big_endian(big_endian != 0); + + r_ver_major = p_f->get_32(); + r_ver_minor = p_f->get_32(); + if (r_ver_major != 0 || r_ver_minor != 99) { + r_suspicious = true; + } else { + r_suspicious = false; + } + + return OK; +} + +Ref ResourceFormatLoaderCompatOBDB::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + if (r_error) { + *r_error = ERR_FILE_CANT_OPEN; + } + + Error err = OK; + Ref f = FileAccess::open(p_path, FileAccess::READ, &err); + ERR_FAIL_COND_V_MSG(err != OK, Ref(), vformat("Cannot open file '%s'.", p_path)); + + ResourceLoaderCompatOBDB loader; + switch (p_cache_mode) { + case CACHE_MODE_IGNORE: + case CACHE_MODE_REUSE: + case CACHE_MODE_REPLACE: + loader.cache_mode = p_cache_mode; + loader.cache_mode_for_external = CACHE_MODE_REUSE; + break; + case CACHE_MODE_IGNORE_DEEP: + loader.cache_mode = CACHE_MODE_IGNORE; + loader.cache_mode_for_external = p_cache_mode; + break; + case CACHE_MODE_REPLACE_DEEP: + loader.cache_mode = CACHE_MODE_REPLACE; + loader.cache_mode_for_external = p_cache_mode; + break; + } + loader.load_type = get_default_real_load(); + loader.use_sub_threads = p_use_sub_threads; + loader.progress = r_progress; + + String path = !p_original_path.is_empty() ? p_original_path : p_path; + loader.local_path = GDRESettings::get_singleton()->localize_path(path); + loader.res_path = loader.local_path; + loader.open(f); + if (loader.error != OK) { + if (r_error) { + *r_error = loader.error; + } + return Ref(); + } + err = loader.load(); + if (r_error) { + *r_error = err; + } + if (err != OK) { + return Ref(); + } + return loader.resource; +} + +Ref ResourceFormatLoaderCompatOBDB::custom_load(const String &p_path, const String &p_original_path, ResourceInfo::LoadType p_type, Error *r_error, bool use_threads, ResourceFormatLoader::CacheMode p_cache_mode) { + if (r_error) { + *r_error = ERR_CANT_OPEN; + } + + Error err = OK; + Ref f = FileAccess::open(p_path, FileAccess::READ, &err); + ERR_FAIL_COND_V_MSG(err != OK, Ref(), vformat("Cannot open file '%s'.", p_path)); + + ResourceLoaderCompatOBDB loader; + loader.load_type = p_type; + switch (p_type) { + case ResourceInfo::FAKE_LOAD: + case ResourceInfo::NON_GLOBAL_LOAD: + loader.cache_mode = ResourceFormatLoader::CACHE_MODE_IGNORE; + loader.use_sub_threads = false; + break; + case ResourceInfo::GLTF_LOAD: + case ResourceInfo::REAL_LOAD: + default: + switch (p_cache_mode) { + case CACHE_MODE_IGNORE: + case CACHE_MODE_REUSE: + case CACHE_MODE_REPLACE: + loader.cache_mode = p_cache_mode; + loader.cache_mode_for_external = CACHE_MODE_REUSE; + break; + case CACHE_MODE_IGNORE_DEEP: + loader.cache_mode = CACHE_MODE_IGNORE; + loader.cache_mode_for_external = p_cache_mode; + break; + case CACHE_MODE_REPLACE_DEEP: + loader.cache_mode = CACHE_MODE_REPLACE; + loader.cache_mode_for_external = p_cache_mode; + break; + } + loader.use_sub_threads = use_threads; + break; + } + + String path = !p_original_path.is_empty() ? p_original_path : p_path; + loader.local_path = GDRESettings::get_singleton()->localize_path(path); + loader.res_path = loader.local_path; + loader.open(f); + if (loader.error != OK) { + if (r_error) { + *r_error = loader.error; + } + return Ref(); + } + err = loader.load(); + if (r_error) { + *r_error = err; + } + if (err != OK) { + return Ref(); + } + return loader.get_resource(); +} + +Ref ResourceFormatLoaderCompatOBDB::get_resource_info(const String &p_path, Error *r_error) const { + if (r_error) { + *r_error = ERR_CANT_OPEN; + } + + Error err = OK; + Ref f = FileAccess::open(p_path, FileAccess::READ, &err); + ERR_FAIL_COND_V_MSG(err != OK, Ref(), vformat("Cannot open file '%s'.", p_path)); + + ResourceLoaderCompatOBDB loader; + loader.load_type = ResourceInfo::FAKE_LOAD; + loader.cache_mode = ResourceFormatLoader::CACHE_MODE_IGNORE; + loader.use_sub_threads = false; + loader.local_path = GDRESettings::get_singleton()->localize_path(p_path); + loader.res_path = loader.local_path; + loader.open(f, true); + if (loader.error != OK) { + if (r_error) { + *r_error = loader.error; + } + return Ref(); + } + + if (r_error) { + *r_error = OK; + } + + Ref info; + info.instantiate(); + info->uid = ResourceUID::INVALID_ID; + info->topology_type = ResourceInfo::MAIN_RESOURCE; + info->type = loader.is_scene_mode ? String("PackedScene") : String(); + info->original_path = loader.local_path; + info->ver_major = loader.ver_major; + info->ver_minor = loader.ver_minor; + info->ver_format = 0; + info->resource_format = "binary_obdb"; + info->load_type = ResourceInfo::FAKE_LOAD; + info->using_real_t_double = loader.using_real_t_double; + info->stored_use_real64 = loader.stored_use_real64; + info->stored_big_endian = loader.stored_big_endian; + info->using_named_scene_ids = false; + info->using_uids = false; + info->is_compressed = false; + if (loader.is_scene_mode) { + info->packed_scene_version = 1; + } + return info; +} + +void ResourceFormatLoaderCompatOBDB::get_recognized_extensions_for_type(const String &p_type, List *p_extensions) const { + if (p_type.is_empty()) { + get_recognized_extensions(p_extensions); + return; + } + List extensions; + ResourceCompatLoader::get_base_extensions(&extensions, 1); + for (const String &E : extensions) { + p_extensions->push_back(E.to_lower()); + } +} + +void ResourceFormatLoaderCompatOBDB::get_recognized_extensions(List *p_extensions) const { + List extensions; + ResourceCompatLoader::get_base_extensions(&extensions, 1); + for (const String &E : extensions) { + p_extensions->push_back(E.to_lower()); + } +} + +bool ResourceFormatLoaderCompatOBDB::recognize_path(const String &p_path, const String &p_type_hint) const { + // Magic-byte gated: only handle real OBDB files. This intentionally returns + // false for everything else so post-1.x binary files still go to + // `ResourceFormatLoaderCompatBinary`. + + // for performances' sake, we'll only bother checking the magic byte if the ver_major is 0 + if (GDRESettings::get_singleton()->get_ver_major() != 0) { + return false; + } + return is_obdb_resource(p_path); +} + +bool ResourceFormatLoaderCompatOBDB::handles_type(const String &p_type) const { + // for performances' sake, we'll only bother checking the magic byte if the ver_major is 0 + if (GDRESettings::get_singleton()->get_ver_major() != 0) { + return false; + } + return true; +} + +String ResourceFormatLoaderCompatOBDB::get_resource_type(const String &p_path) const { + Error err = OK; + Ref f = FileAccess::open(p_path, FileAccess::READ, &err); + if (err != OK || f.is_null()) { + return String(); + } + if (!is_obdb_resource_file(f)) { + return String(); + } + ResourceLoaderCompatOBDB loader; + loader.local_path = p_path; + loader.res_path = loader.local_path; + String magic = loader.recognize(f); + if (magic == "SCENE") { + return "PackedScene"; + } + // Resource mode: peek at the first SECTION_OBJECT type. We re-open with a + // fresh FileAccess to reset the position cleanly. + f->seek(0); + ResourceLoaderCompatOBDB full; + full.local_path = p_path; + full.res_path = full.local_path; + full.load_type = ResourceInfo::FAKE_LOAD; + full.cache_mode = ResourceFormatLoader::CACHE_MODE_IGNORE; + full.open(f); + if (full.error != OK || full.scene_nodes.is_empty()) { + return String(); + } + if (full.scene_nodes[full.scene_nodes.size() - 1].kind != ResourceLoaderCompatOBDB::SECTION_OBJECT) { + return String(); + } + return full.scene_nodes[full.scene_nodes.size() - 1].type; +} + +void ResourceFormatLoaderCompatOBDB::get_dependencies(const String &p_path, List *p_dependencies, bool p_add_types) { + Ref f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_MSG(f.is_null(), vformat("Cannot open file '%s'.", p_path)); + + ResourceLoaderCompatOBDB loader; + loader.local_path = p_path; + loader.res_path = loader.local_path; + loader.load_type = ResourceInfo::FAKE_LOAD; + loader.cache_mode = ResourceFormatLoader::CACHE_MODE_IGNORE; + loader.use_sub_threads = false; + loader.get_dependencies(f, p_dependencies, p_add_types); +} + +ResourceFormatLoaderCompatOBDB *ResourceFormatLoaderCompatOBDB::get_singleton() { + return singleton; +} + +ResourceFormatLoaderCompatOBDB::ResourceFormatLoaderCompatOBDB() { + singleton = this; +} + +ResourceFormatLoaderCompatOBDB::~ResourceFormatLoaderCompatOBDB() { + if (singleton == this) { + singleton = nullptr; + } +} diff --git a/compat/resource_compat_obdb.h b/compat/resource_compat_obdb.h new file mode 100644 index 000000000..2c0d67f43 --- /dev/null +++ b/compat/resource_compat_obdb.h @@ -0,0 +1,141 @@ +#pragma once + +#include "compat/resource_loader_compat.h" +#include "utility/resource_info.h" + +#include "core/io/file_access.h" +#include "core/io/resource.h" +#include "core/io/resource_loader.h" + +class ResourceLoaderCompatOBDB { + friend class ResourceFormatLoaderCompatOBDB; + +public: + enum SectionKind { + SECTION_RESOURCE = 0, + SECTION_OBJECT = 1, + SECTION_META_OBJECT = 2, + SECTION_PROPERTY = 3, + SECTION_END = 4, + }; + +private: + struct InternalResource { + String type; + // Cache key used for `OBJECT_INTERNAL_RESOURCE` lookups; either + // `local_path::N` for `local://N` paths or a real `res://...` path. + String path; + bool is_local_id = true; + uint64_t body_offset = 0; // Where properties begin (after type/path). + uint64_t end_offset = 0; // First byte after the section. + }; + + struct SceneNodeEntry { + uint32_t kind = SECTION_OBJECT; + String type; // Empty for SECTION_META_OBJECT (instance subscenes). + uint64_t body_offset = 0; + uint64_t end_offset = 0; + }; + + bool translation_remapped = false; + String local_path; + String res_path; + String type; + String type_magic; + + Ref f; + Vector str_buf; + Vector string_map; + int bin_meta_string_idx = -1; + + uint32_t ver_major = 0; + uint32_t ver_minor = 0; + + bool stored_big_endian = false; + bool stored_use_real64 = false; + bool using_real_t_double = false; + bool is_scene_mode = false; + + ResourceInfo::LoadType load_type = ResourceInfo::FAKE_LOAD; + ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; + ResourceFormatLoader::CacheMode cache_mode_for_external = ResourceFormatLoader::CACHE_MODE_REUSE; + + bool use_sub_threads = false; + float *progress = nullptr; + Error error = OK; + + Vector internal_resources; + HashMap> internal_index_cache; + List> resource_cache; + Vector scene_nodes; + + HashMap remaps; + + Ref resource; + int packed_scene_version = -1; + + String get_unicode_string(); + void _advance_padding(uint32_t p_len); + + Error _read_reals(real_t *r_dst, size_t p_count); + + Error parse_property(int &r_name_idx, Variant &r_v); + Error parse_variant(Variant &r_v); + Error _decode_image(Variant &r_v); + + bool _index_sections(); + Error _load_internal_resources_phase(); + Error _load_section_properties(Object *p_obj, Dictionary *r_meta, Dictionary *r_missing_resource_properties); + Error _build_packed_scene(); + + void _set_main_resource_info(Ref &r_info); + void set_internal_resource_compat_meta(const String &p_path, const String &p_scene_id, const String &p_type, Ref &r_res); + void set_compat_meta(Ref &r_res); + + bool is_real_load() const { return load_type == ResourceInfo::REAL_LOAD || load_type == ResourceInfo::GLTF_LOAD; } + + // Recognize-only header read; populates `type` and `is_scene_mode`. + bool _read_header(Ref p_f, bool p_load_strings); + +public: + Ref get_resource(); + Ref get_resource_info(); + + Error load(); + void open(Ref p_f, bool p_no_resources = false); + String recognize(Ref p_f); + void get_dependencies(Ref p_f, List *p_dependencies, bool p_add_types); + + void set_remaps(const HashMap &p_remaps) { remaps = p_remaps; } + void set_translation_remapped(bool p_remapped) { translation_remapped = p_remapped; } + + ResourceLoaderCompatOBDB() {} +}; + +class ResourceFormatLoaderCompatOBDB : public CompatFormatLoader { + GDCLASS(ResourceFormatLoaderCompatOBDB, CompatFormatLoader); + + static ResourceFormatLoaderCompatOBDB *singleton; + +public: + static bool is_obdb_resource(const String &p_path); + static bool is_obdb_resource_file(const Ref &p_f); + static Error get_ver_major_minor(const String &p_path, uint32_t &r_ver_major, uint32_t &r_ver_minor, bool &r_suspicious); + static Error get_ver_major_minor_file(const Ref &p_f, uint32_t &r_ver_major, uint32_t &r_ver_minor, bool &r_suspicious); + + virtual Ref custom_load(const String &p_path, const String &p_original_path, ResourceInfo::LoadType p_type, Error *r_error = nullptr, bool use_threads = true, ResourceFormatLoader::CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual Ref get_resource_info(const String &p_path, Error *r_error) const override; + virtual bool handles_fake_load() const override { return true; } + + virtual Ref load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions_for_type(const String &p_type, List *p_extensions) const override; + virtual void get_recognized_extensions(List *p_extensions) const override; + virtual bool recognize_path(const String &p_path, const String &p_type_hint = String()) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; + virtual void get_dependencies(const String &p_path, List *p_dependencies, bool p_add_types = false) override; + + static ResourceFormatLoaderCompatOBDB *get_singleton(); + ResourceFormatLoaderCompatOBDB(); + ~ResourceFormatLoaderCompatOBDB(); +}; diff --git a/compat/resource_compat_text.cpp b/compat/resource_compat_text.cpp index cfe2b4a2a..cab07d9c1 100644 --- a/compat/resource_compat_text.cpp +++ b/compat/resource_compat_text.cpp @@ -1875,7 +1875,7 @@ ResourceUID::ID ResourceFormatLoaderCompatText::get_resource_uid(const String &p } bool ResourceFormatLoaderCompatText::has_custom_uid_support() const { - return CompatFormatLoader::has_custom_uid_support(); + return true; } void ResourceFormatLoaderCompatText::get_dependencies(const String &p_path, List *p_dependencies, bool p_add_types) { @@ -2159,6 +2159,8 @@ Error ResourceFormatSaverCompatTextInstance::save_to_file(const Ref } #endif + ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Resource is null"); + if (p_path.ends_with(".tscn") || p_path.ends_with(".escn")) { // If this is a MissingResource holder for a PackedScene, we need to instance it for reals packed_scene = ensure_packed_scenes(p_resource); @@ -2999,6 +3001,11 @@ Error ResourceFormatSaverCompatTextInstance::set_save_settings(const Ref &p_resource) { return p_resource.is_valid() && _resource_get_class(p_resource) == "PackedScene"; } -Ref _ensure_resource_is_packed_scene(const Ref &p_resource, HashMap> &p_seen_resources, HashSet &seen_objects, int recursion_depth); - -Variant scan_variant(const Variant &v, HashMap> &p_seen_resources, HashSet &seen_objects, int recursion_depth) { - ERR_FAIL_COND_V_MSG(recursion_depth > 1024, Variant(), "Recursion depth exceeded"); - recursion_depth++; - Variant result = v; - switch (v.get_type()) { - case Variant::OBJECT: { - Ref res = v; - if (is_packed_scene(res)) { - Ref sub_scene; - if (!p_seen_resources.has(res->get_path())) { - if (_resource_get_class(res) == "PackedScene") { - sub_scene = _ensure_resource_is_packed_scene(res, p_seen_resources, seen_objects, recursion_depth); - } - } else { - sub_scene = p_seen_resources.get(res->get_path()); - } - if (sub_scene.is_valid()) { - p_seen_resources[res->get_path()] = sub_scene; - result = sub_scene; - } else { - p_seen_resources[res->get_path()] = res; - result = res; - } - } else if (res.is_valid()) { - if (!p_seen_resources.has(res->get_path())) { - List property_list; - res->get_property_list(&property_list); - for (const PropertyInfo &pi : property_list) { - Variant v = scan_variant(res->get(pi.name), p_seen_resources, seen_objects, recursion_depth); - res->set(pi.name, v); - } - p_seen_resources[res->get_path()] = res; - } - } else if (Object *obj = v.operator Object *(); obj != nullptr && !seen_objects.has(obj)) { - seen_objects.insert(obj); - List property_list; - obj->get_property_list(&property_list); - for (const PropertyInfo &pi : property_list) { - if (pi.usage & PROPERTY_USAGE_STORAGE) { - Variant v = scan_variant(obj->get(pi.name), p_seen_resources, seen_objects, recursion_depth); - obj->set(pi.name, v); - } - } - } - } break; - case Variant::ARRAY: { - Array a = v; - for (int j = 0; j < a.size(); j++) { - a[j] = scan_variant(a[j], p_seen_resources, seen_objects, recursion_depth); - } - result = a; - } break; - case Variant::DICTIONARY: { - Dictionary d; - for (const KeyValue &kv : v.operator Dictionary()) { - Variant key = scan_variant(kv.key, p_seen_resources, seen_objects, recursion_depth); - Variant value = scan_variant(kv.value, p_seen_resources, seen_objects, recursion_depth); - d[key] = value; - } - v.operator Dictionary().clear(); - v.operator Dictionary().assign(d); - result = v; - } break; - default: { - } - } - return result; -} - Ref _ensure_resource_is_packed_scene(const Ref &p_resource, HashMap> &p_seen_resources, HashSet &seen_objects, int recursion_depth) { Ref r_packed_scene = p_resource; if (_resource_get_class(p_resource) == "PackedScene") { - Dictionary bundle = scan_variant(p_resource->get("_bundled"), p_seen_resources, seen_objects, recursion_depth); + Dictionary bundle = p_resource->get("_bundled"); // we need to go through the variants in the bundle and ensure that any MissingResources that are PackedScenes are also replaced with instantiated packed scenes // this is because of PackedScene::SceneState::get_node_instance, which returns a Ref, which will be null if it's not an instance of a PackedScene + // (We do not have to worry about PackedScenes embedded in other resources/dictionaries/arrays because this only matters for SceneState::get_node_instance) + Array arr = bundle.get("variants", Array()); + if (arr.size() > 0) { + for (int i = 0; i < arr.size(); i++) { + Ref res = arr[i]; + if (res.is_valid() && res->get_save_class() == "PackedScene") { + arr[i] = _ensure_resource_is_packed_scene(res, p_seen_resources, seen_objects, recursion_depth); + } + } + bundle.set("variants", arr); + } if (r_packed_scene.is_null()) { // MissingResource r_packed_scene = Ref(memnew(PackedScene)); if (!bundle.is_empty()) { @@ -3210,6 +3157,14 @@ Ref _ensure_resource_is_packed_scene(const Ref &p_resourc } Ref ResourceFormatSaverCompatTextInstance::ensure_packed_scenes(const Ref &p_resource) { + auto info = ResourceInfo::get_info_from_resource(p_resource); + if (info.is_valid()) { + if ((info->load_type == ResourceInfo::REAL_LOAD || info->load_type == ResourceInfo::GLTF_LOAD) && p_resource->get_class() == "PackedScene") { + return p_resource; + } + } else if (p_resource->get_class() == "PackedScene") { + return p_resource; + } HashMap> seen_resources; HashSet seen_objects; return _ensure_resource_is_packed_scene(p_resource, seen_resources, seen_objects, 0); diff --git a/compat/resource_format_xml.cpp b/compat/resource_format_xml.cpp index cf5f430ab..6b17f0317 100644 --- a/compat/resource_format_xml.cpp +++ b/compat/resource_format_xml.cpp @@ -2312,7 +2312,7 @@ void ResourceFormatSaverXMLInstance::write_property(const String &p_name, const break; } - if (external_resources.has(res)) { + if (external_resources.has(res) && ver_major > 1) { // v2.0 and above had indexed external resources params = "external=\"" + itos(external_resources[res]) + "\""; } else { params = "resource_type=\"" + res->get_save_class() + "\""; @@ -2572,7 +2572,10 @@ void ResourceFormatSaverXMLInstance::write_property(const String &p_name, const auto keys = dict.get_key_list(); - keys.sort(); + // v1.1 and above sorted dicts before writing, v1.0 and below did not + if (ver_major > 1 || (ver_major == 1 && ver_minor >= 1)) { + keys.sort(); + } for (auto &key : keys) { //if (!_check_type(dict[E->get()])) @@ -2847,7 +2850,11 @@ Error ResourceFormatSaverXMLInstance::save_to_file(const Ref &p_f, c write_tabs(); String p = E.key->get_path(); - enter_tag("ext_resource", "path=\"" + p + "\" type=\"" + E.key->get_save_class() + "\" index=\"" + itos(E.value) + "\""); //bundled + String args = "path=\"" + p + "\" type=\"" + E.key->get_save_class() + "\""; + if (ver_major > 1) { + args += " index=\"" + itos(E.value) + "\""; + } + enter_tag("ext_resource", args); //bundled exit_tag("ext_resource"); //bundled write_string("\n", false); } diff --git a/compat/resource_loader_compat.cpp b/compat/resource_loader_compat.cpp index 6811c232a..b790f4ae0 100644 --- a/compat/resource_loader_compat.cpp +++ b/compat/resource_loader_compat.cpp @@ -6,6 +6,7 @@ #include "core/error/error_macros.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" +#include "core/version_generated.gen.h" #include "utility/common.h" #include "utility/file_access_buffer.h" #include "utility/gdre_settings.h" @@ -17,6 +18,7 @@ int ResourceCompatLoader::loader_count = 0; int ResourceCompatLoader::converter_count = 0; bool ResourceCompatLoader::doing_gltf_load = false; bool ResourceCompatLoader::globally_available = false; +bool ResourceCompatLoader::initialized = false; #define FAIL_LOADER_NOT_FOUND(loader) \ if (loader.is_null()) { \ @@ -83,7 +85,7 @@ static Vector>> core_recognized_extensions_v2 = { { "res", { "Resource", "Curve2D", "RectangleShape2D", "RayShape", "AudioStreamMPC", "AudioStream", "World2D", "FixedMaterial", "GDScript", "Animation", "MeshLibrary", "AudioStreamSpeex", "VideoStream", "VideoStreamTheora", "AtlasTexture", "Shape2D", "World", "RoomBounds", "StyleBoxImageMask", "StyleBoxEmpty", "EventStreamChibi", "Mesh", "EventStream", "ConcavePolygonShape2D", "LineShape2D", "ColorRamp", "BakedLight", "Translation", "Shape", "CapsuleShape2D", "ImageTexture", "BitmapFont", "Script", "Environment", "DynamicFontData", "Object", "Font", "ConcavePolygonShape", "MultiMesh", "RenderTargetTexture", "SegmentShape2D", "BoxShape", "CanvasItemMaterial", "DynamicFont", "LargeTexture", "ShortCut", "Curve3D", "BitMap", "CubeMap", "NavigationMesh", "CapsuleShape", "StyleBoxFlat", "PlaneShape", "ConvexPolygonShape2D", "Sample", "CanvasItemShader", "PHashTranslation", "AudioStreamOpus", "PackedDataContainer", "MaterialShader", "ShaderMaterial", "Resource", "ShaderGraph", "StyleBoxTexture", "ConvexPolygonShape", "PolygonPathFinder", "Reference", "OccluderPolygon2D", "Material", "AudioStreamOGGVorbis", "Texture", "RayShape2D", "Theme", "CanvasItemShaderGraph", "SpriteFrames", "PackedScene", "SampleLibrary", "Shader", "EditorSettings", "NavigationPolygon", "SphereShape", "MaterialShaderGraph", "CircleShape2D", "StyleBox", "TileSet" } }, }; -HashMap> _init_ext_to_types() { +static inline HashMap> _init_v3_ext_to_types() { HashMap> map; for (const auto &pair : core_recognized_extensions_v3) { if (!map.has(pair.first)) { @@ -93,6 +95,11 @@ HashMap> _init_ext_to_types() { map[pair.first].insert(type); } } + return map; +} + +static inline HashMap> _init_v2_ext_to_types() { + HashMap> map; for (const auto &pair : core_recognized_extensions_v2) { if (!map.has(pair.first)) { map[pair.first] = HashSet(); @@ -104,7 +111,7 @@ HashMap> _init_ext_to_types() { return map; } -HashMap> _init_type_to_exts() { +static inline HashMap> _init_type_to_exts() { HashMap> map; for (const auto &pair : core_recognized_extensions_v3) { if (!map.has(pair.first)) { @@ -125,8 +132,30 @@ HashMap> _init_type_to_exts() { return map; } -static HashMap> ext_to_types = _init_ext_to_types(); +static HashMap> _init_v4_ext_to_types() { + LocalVector types; + ClassDB::get_class_list(types); + HashMap> map; + for (const StringName &type : types) { + List extensions; + ClassDB::get_extensions_for_type(type, &extensions); + for (const String &extension : extensions) { + if (!map.has(extension)) { + map[extension] = HashSet(); + } + map[extension].insert(type); + } + } + return map; +} + static HashMap> type_to_exts = _init_type_to_exts(); +static HashMap> ext_to_v2_types = _init_v2_ext_to_types(); +static HashMap> ext_to_v3_types = _init_v3_ext_to_types(); + +// NOTE: this has to be initialized at the END of the engine's initialization, so we can't do it here statically; +// _init() has to be called after all the modules have been initialized. +static HashMap> ext_to_v4_types; // static void get_base_extensions(List *p_extensions); void ResourceCompatLoader::get_base_extensions(List *p_extensions, int ver_major) { @@ -156,7 +185,13 @@ void ResourceCompatLoader::get_base_extensions(List *p_extensions, int v for (const String &ext : *p_extensions) { unique_extensions.insert(ext); } - for (const auto &pair : ext_to_types) { + for (const auto &pair : ext_to_v3_types) { + if (!unique_extensions.has(pair.key)) { + unique_extensions.insert(pair.key); + p_extensions->push_back(pair.key); + } + } + for (const auto &pair : ext_to_v2_types) { if (!unique_extensions.has(pair.key)) { unique_extensions.insert(pair.key); p_extensions->push_back(pair.key); @@ -183,6 +218,28 @@ void ResourceCompatLoader::get_base_extensions_for_type(const String &p_type, Li } } +static inline void add_types_from_ext_to_types(const HashMap> &ext_to_types, const String &p_extension, HashSet &unique_types) { + if (ext_to_types.has(p_extension)) { + const HashSet &types = ext_to_types.get(p_extension); + for (const String &type : types) { + unique_types.insert(type); + } + } +} + +void ResourceCompatLoader::get_type_for_extension(const String &p_extension, List *p_types, int ver_major) { + HashSet unique_types; + if (ver_major <= 2) { + add_types_from_ext_to_types(ext_to_v2_types, p_extension, unique_types); + } + if (ver_major == 3 || ver_major <= 0) { + add_types_from_ext_to_types(ext_to_v3_types, p_extension, unique_types); + } + if (ver_major == GODOT_VERSION_MAJOR || ver_major <= 0) { + add_types_from_ext_to_types(ext_to_v4_types, p_extension, unique_types); + } +} + Ref ResourceCompatLoader::fake_load(const String &p_path, const String &p_type_hint, Error *r_error) { auto loadr = get_loader_for_path(p_path, p_type_hint); FAIL_LOADER_NOT_FOUND(loadr); @@ -207,7 +264,7 @@ Ref ResourceCompatLoader::gltf_load(const String &p_path, const String return ResourceCompatLoader::custom_load(p_path, p_type_hint, ResourceInfo::LoadType::GLTF_LOAD, r_error); } -Ref ResourceCompatLoader::real_load(const String &p_path, const String &p_type_hint, Error *r_error, ResourceFormatLoader::CacheMode p_cache_mode) { +Ref ResourceCompatLoader::real_load(const String &p_path, const String &p_type_hint, Error *r_error, ResourceCompatLoader::CacheMode p_cache_mode) { return ResourceCompatLoader::custom_load(p_path, p_type_hint, ResourceInfo::LoadType::REAL_LOAD, r_error, true, p_cache_mode); } namespace { @@ -225,7 +282,7 @@ String _validate_local_path(const String &p_path) { } //namespace thread_local HashSet currently_loading_paths; -Ref ResourceCompatLoader::custom_load(const String &p_path, const String &p_type_hint, ResourceInfo::LoadType p_type, Error *r_error, bool use_threads, ResourceFormatLoader::CacheMode p_cache_mode) { +Ref ResourceCompatLoader::custom_load(const String &p_path, const String &p_type_hint, ResourceInfo::LoadType p_type, Error *r_error, bool use_threads, ResourceCompatLoader::CacheMode p_cache_mode) { String local_path = _validate_local_path(p_path); String res_path = GDRESettings::get_singleton()->get_mapped_path(p_path); bool is_real_load = p_type == ResourceInfo::LoadType::REAL_LOAD || p_type == ResourceInfo::LoadType::GLTF_LOAD; @@ -271,7 +328,7 @@ Ref ResourceCompatLoader::custom_load(const String &p_path, const Stri return res; } -Ref ResourceCompatLoader::load_with_real_resource_loader(const String &p_path, const String &p_type_hint, Error *r_error, bool use_threads, ResourceFormatLoader::CacheMode p_cache_mode) { +Ref ResourceCompatLoader::load_with_real_resource_loader(const String &p_path, const String &p_type_hint, Error *r_error, bool use_threads, ResourceCompatLoader::CacheMode p_cache_mode) { String local_path = _validate_local_path(p_path); if (use_threads) { return ResourceLoader::load(local_path, p_type_hint, p_cache_mode, r_error); @@ -569,16 +626,36 @@ String ResourceCompatLoader::get_resource_type(const String &p_path) { return loader->get_resource_type(p_path); } -Vector ResourceCompatLoader::_get_dependencies(const String &p_path, bool p_add_types) { +bool ResourceCompatLoader::exists(const String &p_path) { + String local_path = _validate_local_path(p_path); + if (ResourceCache::has(local_path)) { + return true; // If cached, it probably exists + } + + String path = GDRESettings::get_singleton()->get_mapped_path(local_path); + + for (int i = 0; i < loader_count; i++) { + if (!loaders[i]->recognize_path(path, "")) { + continue; + } + + if (loaders[i]->exists(path)) { + return true; + } + } + return ResourceLoader::exists(path); +} + +bool ResourceCompatLoader::has_custom_uid_support(const String &p_path) { + if (FileAccess::exists(p_path + ".import")) { + return true; + } + auto loader = get_loader_for_path(p_path, ""); - ERR_FAIL_COND_V_MSG(loader.is_null(), Vector(), "Failed to load resource '" + p_path + "'. ResourceFormatLoader::load was not implemented for this resource type."); - List dependencies; - loader->get_dependencies(p_path, &dependencies, p_add_types); - Vector deps; - for (List::Element *E = dependencies.front(); E; E = E->next()) { - deps.push_back(E->get()); + if (loader.is_null()) { + return false; } - return deps; + return loader->has_custom_uid_support(); } bool ResourceCompatLoader::is_default_gltf_load() { @@ -646,24 +723,57 @@ String ResourceCompatConverter::get_resource_name(const Ref &re return name; } +void ResourceCompatLoader::_init() { + if (ResourceCompatLoader::initialized) { + return; + } + ext_to_v4_types = _init_v4_ext_to_types(); + initialized = true; + return; +} + +#ifdef TESTS_ENABLED +void ResourceCompatLoader::_deinit() { + if (!ResourceCompatLoader::initialized) { + return; + } + ext_to_v4_types.clear(); + initialized = false; +} +#endif + +namespace CoreBind { + +Vector ResourceCompatLoader::_get_dependencies(const String &p_path, bool p_add_types) { + auto loader = ::ResourceCompatLoader::get_loader_for_path(p_path, ""); + ERR_FAIL_COND_V_MSG(loader.is_null(), Vector(), "Failed to load resource '" + p_path + "'. ResourceFormatLoader::load was not implemented for this resource type."); + List dependencies; + loader->get_dependencies(p_path, &dependencies, p_add_types); + Vector deps; + for (List::Element *E = dependencies.front(); E; E = E->next()) { + deps.push_back(E->get()); + } + return deps; +} + Ref ResourceCompatLoader::_fake_load(const String &p_path, const String &p_type_hint) { - return fake_load(p_path, p_type_hint, nullptr); + return ::ResourceCompatLoader::fake_load(p_path, p_type_hint, nullptr); } Ref ResourceCompatLoader::_non_global_load(const String &p_path, const String &p_type_hint) { - return non_global_load(p_path, p_type_hint, nullptr); + return ::ResourceCompatLoader::non_global_load(p_path, p_type_hint, nullptr); } Ref ResourceCompatLoader::_gltf_load(const String &p_path, const String &p_type_hint) { - return gltf_load(p_path, p_type_hint, nullptr); + return ::ResourceCompatLoader::gltf_load(p_path, p_type_hint, nullptr); } -Ref ResourceCompatLoader::_real_load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode) { - return real_load(p_path, p_type_hint, nullptr, p_cache_mode); +Ref ResourceCompatLoader::_real_load(const String &p_path, const String &p_type_hint, CacheMode p_cache_mode) { + return ::ResourceCompatLoader::real_load(p_path, p_type_hint, nullptr, (::ResourceCompatLoader::CacheMode)p_cache_mode); } Dictionary ResourceCompatLoader::_get_resource_info(const String &p_path, const String &p_type_hint) { - Ref info = get_resource_info(p_path, p_type_hint, nullptr); + Ref info = ::ResourceCompatLoader::get_resource_info(p_path, p_type_hint, nullptr); if (info.is_valid()) { return info->to_dict(); } @@ -671,35 +781,40 @@ Dictionary ResourceCompatLoader::_get_resource_info(const String &p_path, const } void ResourceCompatLoader::_bind_methods() { + BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE); + BIND_ENUM_CONSTANT(CACHE_MODE_REUSE); + BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE); + BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE_DEEP); + BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE_DEEP); ClassDB::bind_static_method(get_class_static(), D_METHOD("fake_load", "path", "type_hint"), &ResourceCompatLoader::_fake_load, DEFVAL("")); ClassDB::bind_static_method(get_class_static(), D_METHOD("non_global_load", "path", "type_hint"), &ResourceCompatLoader::_non_global_load, DEFVAL("")); ClassDB::bind_static_method(get_class_static(), D_METHOD("gltf_load", "path", "type_hint"), &ResourceCompatLoader::_gltf_load, DEFVAL("")); - ClassDB::bind_static_method(get_class_static(), D_METHOD("real_load", "path", "type_hint", "cache_mode"), &ResourceCompatLoader::_real_load, DEFVAL(""), DEFVAL(ResourceFormatLoader::CACHE_MODE_REUSE)); - ClassDB::bind_static_method(get_class_static(), D_METHOD("add_resource_format_loader", "loader", "at_front"), &ResourceCompatLoader::add_resource_format_loader, DEFVAL(false)); - ClassDB::bind_static_method(get_class_static(), D_METHOD("remove_resource_format_loader", "loader"), &ResourceCompatLoader::remove_resource_format_loader); - ClassDB::bind_static_method(get_class_static(), D_METHOD("add_resource_object_converter", "converter", "at_front"), &ResourceCompatLoader::add_resource_object_converter, DEFVAL(false)); - ClassDB::bind_static_method(get_class_static(), D_METHOD("remove_resource_object_converter", "converter"), &ResourceCompatLoader::remove_resource_object_converter); + ClassDB::bind_static_method(get_class_static(), D_METHOD("real_load", "path", "type_hint", "cache_mode"), &ResourceCompatLoader::_real_load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE)); + ClassDB::bind_static_method(get_class_static(), D_METHOD("add_resource_format_loader", "loader", "at_front"), &::ResourceCompatLoader::add_resource_format_loader, DEFVAL(false)); + ClassDB::bind_static_method(get_class_static(), D_METHOD("remove_resource_format_loader", "loader"), &::ResourceCompatLoader::remove_resource_format_loader); + ClassDB::bind_static_method(get_class_static(), D_METHOD("add_resource_object_converter", "converter", "at_front"), &::ResourceCompatLoader::add_resource_object_converter, DEFVAL(false)); + ClassDB::bind_static_method(get_class_static(), D_METHOD("remove_resource_object_converter", "converter"), &::ResourceCompatLoader::remove_resource_object_converter); ClassDB::bind_static_method(get_class_static(), D_METHOD("get_resource_info", "path", "type_hint"), &ResourceCompatLoader::_get_resource_info, DEFVAL("")); ClassDB::bind_static_method(get_class_static(), D_METHOD("get_dependencies", "path", "add_types"), &ResourceCompatLoader::_get_dependencies, DEFVAL(false)); - ClassDB::bind_static_method(get_class_static(), D_METHOD("to_text", "path", "dst", "flags", "original_path"), &ResourceCompatLoader::to_text, DEFVAL(0), DEFVAL("")); - ClassDB::bind_static_method(get_class_static(), D_METHOD("to_binary", "path", "dst", "flags"), &ResourceCompatLoader::to_binary, DEFVAL(0)); - ClassDB::bind_static_method(get_class_static(), D_METHOD("make_globally_available"), &ResourceCompatLoader::make_globally_available); - ClassDB::bind_static_method(get_class_static(), D_METHOD("unmake_globally_available"), &ResourceCompatLoader::unmake_globally_available); - ClassDB::bind_static_method(get_class_static(), D_METHOD("is_globally_available"), &ResourceCompatLoader::is_globally_available); - ClassDB::bind_static_method(get_class_static(), D_METHOD("set_default_gltf_load", "enable"), &ResourceCompatLoader::set_default_gltf_load); - ClassDB::bind_static_method(get_class_static(), D_METHOD("is_default_gltf_load"), &ResourceCompatLoader::is_default_gltf_load); - ClassDB::bind_static_method(get_class_static(), D_METHOD("handles_resource", "path", "type_hint"), &ResourceCompatLoader::handles_resource, DEFVAL("")); - ClassDB::bind_static_method(get_class_static(), D_METHOD("get_resource_script_class", "path"), &ResourceCompatLoader::get_resource_script_class); - ClassDB::bind_static_method(get_class_static(), D_METHOD("get_resource_type", "path"), &ResourceCompatLoader::get_resource_type); - ClassDB::bind_static_method(get_class_static(), D_METHOD("save_custom", "resource", "path", "ver_major", "ver_minor"), &ResourceCompatLoader::save_custom); - ClassDB::bind_static_method(get_class_static(), D_METHOD("resource_to_string", "path", "skip_cr"), &ResourceCompatLoader::resource_to_string, DEFVAL(true)); + ClassDB::bind_static_method(get_class_static(), D_METHOD("to_text", "path", "dst", "flags", "original_path"), &::ResourceCompatLoader::to_text, DEFVAL(0), DEFVAL("")); + ClassDB::bind_static_method(get_class_static(), D_METHOD("to_binary", "path", "dst", "flags"), &::ResourceCompatLoader::to_binary, DEFVAL(0)); + ClassDB::bind_static_method(get_class_static(), D_METHOD("make_globally_available"), &::ResourceCompatLoader::make_globally_available); + ClassDB::bind_static_method(get_class_static(), D_METHOD("unmake_globally_available"), &::ResourceCompatLoader::unmake_globally_available); + ClassDB::bind_static_method(get_class_static(), D_METHOD("is_globally_available"), &::ResourceCompatLoader::is_globally_available); + ClassDB::bind_static_method(get_class_static(), D_METHOD("set_default_gltf_load", "enable"), &::ResourceCompatLoader::set_default_gltf_load); + ClassDB::bind_static_method(get_class_static(), D_METHOD("is_default_gltf_load"), &::ResourceCompatLoader::is_default_gltf_load); + ClassDB::bind_static_method(get_class_static(), D_METHOD("handles_resource", "path", "type_hint"), &::ResourceCompatLoader::handles_resource, DEFVAL("")); + ClassDB::bind_static_method(get_class_static(), D_METHOD("get_resource_script_class", "path"), &::ResourceCompatLoader::get_resource_script_class); + ClassDB::bind_static_method(get_class_static(), D_METHOD("get_resource_type", "path"), &::ResourceCompatLoader::get_resource_type); + ClassDB::bind_static_method(get_class_static(), D_METHOD("save_custom", "resource", "path", "ver_major", "ver_minor"), &::ResourceCompatLoader::save_custom); + ClassDB::bind_static_method(get_class_static(), D_METHOD("resource_to_string", "path", "skip_cr"), &::ResourceCompatLoader::resource_to_string, DEFVAL(true)); ClassDB::bind_integer_constant(get_class_static(), "LoadType", "FAKE_LOAD", ResourceInfo::FAKE_LOAD); ClassDB::bind_integer_constant(get_class_static(), "LoadType", "NON_GLOBAL_LOAD", ResourceInfo::NON_GLOBAL_LOAD); ClassDB::bind_integer_constant(get_class_static(), "LoadType", "GLTF_LOAD", ResourceInfo::GLTF_LOAD); ClassDB::bind_integer_constant(get_class_static(), "LoadType", "REAL_LOAD", ResourceInfo::REAL_LOAD); } - -Ref CompatFormatLoader::custom_load(const String &p_path, const String &p_original_path, ResourceInfo::LoadType p_type, Error *r_error, bool use_threads, ResourceFormatLoader::CacheMode p_cache_mode) { +} //namespace CoreBind +Ref CompatFormatLoader::custom_load(const String &p_path, const String &p_original_path, ResourceInfo::LoadType p_type, Error *r_error, bool use_threads, ResourceCompatLoader::CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_UNAVAILABLE; } diff --git a/compat/resource_loader_compat.h b/compat/resource_loader_compat.h index 5286d5ae6..ea223bce3 100644 --- a/compat/resource_loader_compat.h +++ b/compat/resource_loader_compat.h @@ -9,9 +9,7 @@ class CompatFormatLoader; class CompatFormatSaver; class ResourceCompatConverter; -class ResourceCompatLoader : public Object { - GDCLASS(ResourceCompatLoader, Object); - +class ResourceCompatLoader { enum { MAX_LOADERS = 64, MAX_CONVERTERS = 8192, @@ -23,28 +21,21 @@ class ResourceCompatLoader : public Object { static int converter_count; static bool doing_gltf_load; static bool globally_available; + static bool initialized; protected: - static Ref _fake_load(const String &p_path, const String &p_type_hint = ""); - static Ref _non_global_load(const String &p_path, const String &p_type_hint = ""); - static Ref _gltf_load(const String &p_path, const String &p_type_hint = ""); - static Ref _real_load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); - static Dictionary _get_resource_info(const String &p_path, const String &p_type_hint = ""); - static Vector _get_dependencies(const String &p_path, bool p_add_types); - - static void _bind_methods(); - static Ref _load_for_text_conversion(const String &p_path, const String &original_path = "", Error *r_error = nullptr); public: + using CacheMode = ResourceLoaderConstants::CacheMode; static ResourceInfo::LoadType get_default_load_type(); static Ref fake_load(const String &p_path, const String &p_type_hint = "", Error *r_error = nullptr); static Ref non_global_load(const String &p_path, const String &p_type_hint = "", Error *r_error = nullptr); static Ref gltf_load(const String &p_path, const String &p_type_hint = "", Error *r_error = nullptr); - static Ref real_load(const String &p_path, const String &p_type_hint = "", Error *r_error = nullptr, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); - static Ref custom_load(const String &p_path, const String &p_type_hint = "", ResourceInfo::LoadType p_type = ResourceInfo::LoadType::REAL_LOAD, Error *r_error = nullptr, bool use_threads = true, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); - static Ref load_with_real_resource_loader(const String &p_path, const String &p_type_hint = "", Error *r_error = nullptr, bool use_threads = true, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); + static Ref real_load(const String &p_path, const String &p_type_hint = "", Error *r_error = nullptr, CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); + static Ref custom_load(const String &p_path, const String &p_type_hint = "", ResourceInfo::LoadType p_type = ResourceInfo::LoadType::REAL_LOAD, Error *r_error = nullptr, bool use_threads = true, CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); + static Ref load_with_real_resource_loader(const String &p_path, const String &p_type_hint = "", Error *r_error = nullptr, bool use_threads = true, CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); static void add_resource_format_loader(Ref p_format_loader, bool p_at_front = false); static void remove_resource_format_loader(Ref p_format_loader); static void add_resource_object_converter(Ref p_converter, bool p_at_front = false); @@ -58,6 +49,8 @@ class ResourceCompatLoader : public Object { static bool handles_resource(const String &p_path, const String &p_type_hint = ""); static String get_resource_script_class(const String &p_path); static String get_resource_type(const String &p_path); + static bool exists(const String &p_path); + static bool has_custom_uid_support(const String &p_path); static String resource_to_string(const String &p_path, bool p_skip_cr = true); @@ -69,6 +62,14 @@ class ResourceCompatLoader : public Object { static void get_base_extensions_for_type(const String &p_type, List *p_extensions); static void get_base_extensions(List *p_extensions, int ver_major = 0); + static void get_type_for_extension(const String &p_extension, List *p_types, int ver_major = 0); + + static void _init(); + +#ifdef TESTS_ENABLED + // NOTE: ONLY tests should call this + static void _deinit(); +#endif // only supports resource text and binary formats, not texture formats static Error save_custom(const Ref &p_resource, const String &p_path, int ver_major, int ver_minor); @@ -78,28 +79,32 @@ class CompatFormatLoader : public ResourceFormatLoader { GDCLASS(CompatFormatLoader, ResourceFormatLoader); public: - virtual Ref custom_load(const String &p_path, const String &p_original_path, ResourceInfo::LoadType p_type, Error *r_error = nullptr, bool use_threads = true, ResourceFormatLoader::CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual Ref custom_load(const String &p_path, const String &p_original_path, ResourceInfo::LoadType p_type, Error *r_error = nullptr, bool use_threads = true, ResourceCompatLoader::CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual Ref get_resource_info(const String &p_path, Error *r_error) const; virtual bool handles_fake_load() const; + // Layout of the version bits in p_flags: + // bits 28-31 (4 bits): format_version + // bits 20-27 (8 bits): ver_major + // bits 12-19 (8 bits): ver_minor + // Bits 0-11 are reserved for ResourceSaver::SaverFlags (which currently only use bits 0-6). static constexpr int get_format_version_from_flags(uint32_t p_flags) { - // we want to get the last 4 bits of the flags return (p_flags >> 28) & 0xF; } static constexpr int get_ver_major_from_flags(uint32_t p_flags) { - return (p_flags >> 24) & 0xF; + return (p_flags >> 20) & 0xFF; } static constexpr int get_ver_minor_from_flags(uint32_t p_flags) { - return (p_flags >> 20) & 0xF; + return (p_flags >> 12) & 0xFF; } - static constexpr int set_version_info_in_flags(uint32_t p_flags, int p_format_version, int p_ver_major, int p_ver_minor) { - p_flags &= ~0xFFF00000; - p_flags |= p_format_version << 28; - p_flags |= p_ver_major << 24; - p_flags |= p_ver_minor << 20; + static constexpr uint32_t set_version_info_in_flags(uint32_t p_flags, int p_format_version, int p_ver_major, int p_ver_minor) { + p_flags &= ~0xFFFFF000; + p_flags |= (p_format_version & 0xF) << 28; + p_flags |= (p_ver_major & 0xFF) << 20; + p_flags |= (p_ver_minor & 0xFF) << 12; return p_flags; } @@ -145,6 +150,7 @@ class CompatFormatLoader : public ResourceFormatLoader { Ref compat; compat.instantiate(); compat->uid = uid; + compat->original_path = path; compat->type = type; compat->cached_id = scene_id; compat->topology_type = ResourceInfo::UNLOADED_EXTERNAL_RESOURCE; @@ -157,6 +163,7 @@ class CompatFormatLoader : public ResourceFormatLoader { Ref compat; compat.instantiate(); compat->uid = uid; + compat->original_path = path; compat->type = type; compat->topology_type = ResourceInfo::MAIN_RESOURCE; compat->set_on_resource(res); @@ -168,6 +175,8 @@ class CompatFormatLoader : public ResourceFormatLoader { Ref compat; compat.instantiate(); compat->uid = ResourceUID::INVALID_ID; + compat->original_path = path; + compat->cached_id = scene_id; compat->type = type; compat->topology_type = ResourceInfo::INTERNAL_RESOURCE; compat->set_on_resource(res); @@ -215,3 +224,28 @@ class ResourceCompatConverter : public RefCounted { static Ref set_real_from_missing_resource(Ref mr, Ref res, ResourceInfo::LoadType load_type, const HashMap &prop_map = {}); static bool is_external_resource(Ref mr); }; + +namespace CoreBind { +class ResourceCompatLoader : public Object { + GDCLASS(ResourceCompatLoader, Object); + enum CacheMode { + CACHE_MODE_IGNORE, + CACHE_MODE_REUSE, + CACHE_MODE_REPLACE, + CACHE_MODE_IGNORE_DEEP, + CACHE_MODE_REPLACE_DEEP, + }; + +protected: + static void _bind_methods(); + +public: + static Ref _fake_load(const String &p_path, const String &p_type_hint = ""); + static Ref _non_global_load(const String &p_path, const String &p_type_hint = ""); + static Ref _gltf_load(const String &p_path, const String &p_type_hint = ""); + static Dictionary _get_resource_info(const String &p_path, const String &p_type_hint = ""); + static Vector _get_dependencies(const String &p_path, bool p_add_types); + static Ref _real_load(const String &p_path, const String &p_type_hint = "", CacheMode p_cache_mode = CACHE_MODE_REUSE); +}; +} //namespace CoreBind +VARIANT_ENUM_CAST(CoreBind::ResourceCompatLoader::CacheMode); diff --git a/compat/texture_loader_compat.cpp b/compat/texture_loader_compat.cpp index 4c26731d6..ff8d1bc03 100644 --- a/compat/texture_loader_compat.cpp +++ b/compat/texture_loader_compat.cpp @@ -11,7 +11,7 @@ #include "core/error/error_list.h" #include "core/error/error_macros.h" #include "core/io/file_access.h" -#include "core/io/image_loader.h" +#include "core/io/image_resource_format.h" #include "core/io/missing_resource.h" #include "core/variant/dictionary.h" #include "scene/resources/compressed_texture.h" @@ -1139,15 +1139,24 @@ Ref ImageTextureConverterCompat::convert(const Ref &r ERR_FAIL_COND_V_MSG(type != "ImageTexture", res, "Unsupported type: " + type); name = get_resource_name(res, ver_major); image = convert_image(res->get("image")); - ERR_FAIL_COND_V_MSG(image.is_null(), res, "Cannot load image from ImageTexture resource '" + name + "'."); + if (image.is_null()) { + String path = res->get_path(); + if (path.is_empty()) { + path = info->original_path; + } + WARN_PRINT("ImageTextureConverterCompat: image is null for resource '" + path + "'."); + } size = res->get("size"); flags = res->get("flags"); - bool mipmaps = flags & 1 || image->has_mipmaps(); + bool mipmaps = flags & 1; - image->set_name(name); - tw = image->get_width(); - th = image->get_height(); + if (!image.is_null()) { + mipmaps = mipmaps || image->has_mipmaps(); + image->set_name(name); + tw = image->get_width(); + th = image->get_height(); + } if (size.width && tw != size.width) { tw_custom = size.width; } @@ -1239,17 +1248,17 @@ Ref TextureLoaderCompat::create_image_texture(const String &p_path texture.instantiate(); } fakeimagetex *fake = reinterpret_cast(texture.ptr()); - fake->image_stored = true; + fake->image_stored = image.is_valid(); fake->w = tw; fake->h = th; - fake->format = image->get_format(); + fake->format = image.is_valid() ? image->get_format() : Image::FORMAT_L8; if (tw_custom || th_custom) { fake->size_override = Size2(tw_custom, th_custom); } fake->mipmaps = mipmaps; bool size_override = tw_custom || th_custom; if (p_type == ResourceInfo::LoadType::REAL_LOAD) { - RID texture_rid = RS::get_singleton()->texture_2d_create(image); + RID texture_rid = image.is_valid() ? RS::get_singleton()->texture_2d_create(image) : RS::get_singleton()->texture_2d_placeholder_create(); fake->texture = texture_rid; if (size_override) { RS::get_singleton()->texture_set_size_override(texture_rid, tw_custom, th_custom); @@ -1297,7 +1306,9 @@ Ref LargeTextureConverterCompat::convert(const Ref &r image_texture = texture_res; } ERR_FAIL_COND_V_MSG(!image_texture.is_valid(), Ref(), "LargeTextureConverterCompat: Failed to convert ImageTexture in array data of LargeTexture " + res->get_path()); - auto image_size = image_texture->get_image()->get_size(); + Ref image = image_texture->get_image(); + ERR_FAIL_COND_V_MSG(image.is_null(), Ref(), "Image is null for texture " + image_texture->get_path()); + auto image_size = image->get_size(); max_piece_size.x = MAX(max_piece_size.x, image_size.x); max_piece_size.y = MAX(max_piece_size.y, image_size.y); pieces.push_back({ offset, image_texture }); @@ -1312,7 +1323,9 @@ Ref LargeTextureConverterCompat::convert(const Ref &r auto pos = Point2(0, 0); for (int i = 0; i < pieces.size(); i++) { + ERR_FAIL_COND_V_MSG(pieces[i].texture.is_null(), Ref(), vformat("Texture is null for piece %d of LargeTexture %s", i, info->original_path)); auto image = pieces[i].texture->get_image(); + ERR_FAIL_COND_V_MSG(image.is_null(), Ref(), "Image is null for texture " + pieces[i].texture->get_path()); pos = pieces[i].offset; while (pos.x != expected_x || pos.y != expected_y) { Size2i gap_size = max_piece_size; @@ -1438,9 +1451,17 @@ Ref ResourceFormatLoaderImageTextureCompat::custom_load(const String & return image_texture; } +// only enable this on ver_major <= 2 +static inline bool is_texture_loader_enabled() { + int major = GDRESettings::get_singleton()->get_ver_major(); + if (major == 0) { + return GDRESettings::get_singleton()->get_ver_minor() != 0; + } + return major <= 2; +} + void ResourceFormatLoaderImageTextureCompat::get_recognized_extensions(List *p_extensions) const { - // only enable this on ver_major <= 2 - if (GDRESettings::get_singleton()->get_ver_major() > 2 && GDRESettings::get_singleton()->get_ver_major() != 0) { + if (!is_texture_loader_enabled()) { return; } p_extensions->push_back("png"); @@ -1450,21 +1471,21 @@ void ResourceFormatLoaderImageTextureCompat::get_recognized_extensions(Listget_ver_major() > 2 && GDRESettings::get_singleton()->get_ver_major() != 0) { + if (!is_texture_loader_enabled()) { return false; } return p_type == "Texture" || p_type == "ImageTexture"; } String ResourceFormatLoaderImageTextureCompat::get_resource_type(const String &p_path) const { - if (GDRESettings::get_singleton()->get_ver_major() > 2 && GDRESettings::get_singleton()->get_ver_major() != 0) { + if (!is_texture_loader_enabled()) { return String(); } return "ImageTexture"; } Ref ResourceFormatLoaderImageTextureCompat::get_resource_info(const String &p_path, Error *r_error) const { - if (GDRESettings::get_singleton()->get_ver_major() > 2 && GDRESettings::get_singleton()->get_ver_major() != 0) { + if (!is_texture_loader_enabled()) { return Ref(); } static const Vector supported_extensions = { "png", "webp", "jpg", "jpeg" }; diff --git a/compat/variant_decoder_compat.cpp b/compat/variant_decoder_compat.cpp index 823c8f600..fd281e356 100644 --- a/compat/variant_decoder_compat.cpp +++ b/compat/variant_decoder_compat.cpp @@ -8,6 +8,8 @@ #include "core/io/image.h" #include "core/io/marshalls.h" #include "core/object/class_db.h" +#include "core/variant/array.h" +#include "core/variant/container_type_validate.h" #define _S(a) ((int32_t)a) #define ERR_FAIL_ADD_OF(a, b, err) ERR_FAIL_COND_V(_S(b) < 0 || _S(a) < 0 || _S(a) > INT_MAX - _S(b), err) @@ -3064,11 +3066,861 @@ Error VariantDecoderCompat::encode_variant_2(const Variant &p_variant, uint8_t * return OK; } -Error VariantDecoderCompat::encode_variant_compat(int ver_major, const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects, int p_depth) { +enum ContainerTypeKind { + CONTAINER_TYPE_KIND_NONE = 0b00, + CONTAINER_TYPE_KIND_BUILTIN = 0b01, + CONTAINER_TYPE_KIND_CLASS_NAME = 0b10, + CONTAINER_TYPE_KIND_SCRIPT = 0b11, +}; + +// Byte 0: `Variant::Type`, byte 1: unused, bytes 2 and 3: additional data. +#define HEADER_TYPE_MASK 0xFF + +// For `Variant::INT`, `Variant::FLOAT` and other math types. +#define HEADER_DATA_FLAG_64 (1 << 16) + +// For `Variant::OBJECT`. +#define HEADER_DATA_FLAG_OBJECT_AS_ID (1 << 16) + +// For `Variant::ARRAY`. +// Occupies bits 16 and 17. +#define HEADER_DATA_FIELD_TYPED_ARRAY_MASK (0b11 << 16) +#define HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT 16 + +// For `Variant::DICTIONARY`. +// Occupies bits 16 and 17. +#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_MASK (0b11 << 16) +#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT 16 +// Occupies bits 18 and 19. +#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_MASK (0b11 << 18) +#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT 18 + +#define GET_CONTAINER_TYPE_KIND(m_header, m_field) \ + ((ContainerTypeKind)(((m_header) & HEADER_DATA_FIELD_##m_field##_MASK) >> HEADER_DATA_FIELD_##m_field##_SHIFT)) + +static void _encode_container_type_header(const ContainerType &p_type, uint32_t &header, uint32_t p_shift, bool p_full_objects) { + if (p_type.builtin_type != Variant::NIL) { + if (p_type.script.is_valid()) { + header |= (p_full_objects ? CONTAINER_TYPE_KIND_SCRIPT : CONTAINER_TYPE_KIND_CLASS_NAME) << p_shift; + } else if (p_type.class_name != StringName()) { + header |= CONTAINER_TYPE_KIND_CLASS_NAME << p_shift; + } else { + // No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`. + header |= CONTAINER_TYPE_KIND_BUILTIN << p_shift; + } + } +} + +static Error _encode_container_type(const ContainerType &p_type, uint8_t *&buf, int &r_len, bool p_full_objects) { + if (p_type.builtin_type != Variant::NIL) { + if (p_type.script.is_valid()) { + if (p_full_objects) { + String path = p_type.script->get_path(); + ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for a container type."); + _encode_string(path, buf, r_len); + } else { + _encode_string(EncodedObjectAsID::get_class_static(), buf, r_len); + } + } else if (p_type.class_name != StringName()) { + _encode_string(p_full_objects ? p_type.class_name : EncodedObjectAsID::get_class_static(), buf, r_len); + } else { + // No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`. + if (buf) { + encode_uint32(p_type.builtin_type, buf); + buf += 4; + } + r_len += 4; + } + } + return OK; +} + +static inline unsigned int _encode_real(real_t p_real, uint8_t *p_arr, bool p_real_t_is_double) { + if (p_real_t_is_double) { + return encode_double(p_real, p_arr); + } + return encode_float(p_real, p_arr); +} + +Error VariantDecoderCompat::encode_variant_4(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects, bool p_real_t_is_double, int p_depth) { + ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Potential infinite recursion detected. Bailing."); + uint8_t *buf = r_buffer; + + r_len = 0; + + uint32_t header = p_variant.get_type(); + + switch (p_variant.get_type()) { + case Variant::INT: { + int64_t val = p_variant; + if (val > (int64_t)INT_MAX || val < (int64_t)INT_MIN) { + header |= HEADER_DATA_FLAG_64; + } + } break; + case Variant::FLOAT: { + double d = p_variant; + float f = d; + if (double(f) != d) { + header |= HEADER_DATA_FLAG_64; + } + } break; + case Variant::OBJECT: { + // Test for potential wrong values sent by the debugger when it breaks. + Object *obj = p_variant.get_validated_object(); + if (!obj) { + // Object is invalid, send a nullptr instead. + if (buf) { + encode_uint32(Variant::NIL, buf); + } + r_len += 4; + return OK; + } + + if (!p_full_objects) { + header |= HEADER_DATA_FLAG_OBJECT_AS_ID; + } + } break; + case Variant::DICTIONARY: { + const Dictionary dict = p_variant; + _encode_container_type_header(dict.get_key_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects); + _encode_container_type_header(dict.get_value_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects); + } break; + case Variant::ARRAY: { + const Array array = p_variant; + _encode_container_type_header(array.get_element_type(), header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects); + } break; + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::VECTOR4: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_VECTOR4_ARRAY: + case Variant::TRANSFORM2D: + case Variant::TRANSFORM3D: + case Variant::PROJECTION: + case Variant::QUATERNION: + case Variant::PLANE: + case Variant::BASIS: + case Variant::RECT2: + case Variant::AABB: { + //#ifdef REAL_T_IS_DOUBLE + if (p_real_t_is_double) { + header |= HEADER_DATA_FLAG_64; + } + } break; + default: { + // Nothing to do at this stage. + } break; + } + + if (buf) { + encode_uint32(header, buf); + buf += 4; + } + r_len += 4; + + switch (p_variant.get_type()) { + case Variant::NIL: { + // Nothing to do. + } break; + case Variant::BOOL: { + if (buf) { + encode_uint32(p_variant.operator bool(), buf); + } + + r_len += 4; + + } break; + case Variant::INT: { + if (header & HEADER_DATA_FLAG_64) { + // 64 bits. + if (buf) { + encode_uint64(p_variant.operator uint64_t(), buf); + } + + r_len += 8; + } else { + if (buf) { + encode_uint32(p_variant.operator uint32_t(), buf); + } + + r_len += 4; + } + } break; + case Variant::FLOAT: { + if (header & HEADER_DATA_FLAG_64) { + if (buf) { + encode_double(p_variant.operator double(), buf); + } + + r_len += 8; + + } else { + if (buf) { + encode_float(p_variant.operator float(), buf); + } + + r_len += 4; + } + + } break; + case Variant::NODE_PATH: { + NodePath np = p_variant; + if (buf) { + encode_uint32(uint32_t(np.get_name_count()) | 0x80000000, buf); // For compatibility with the old format. + encode_uint32(np.get_subname_count(), buf + 4); + uint32_t np_flags = 0; + if (np.is_absolute()) { + np_flags |= 1; + } + + encode_uint32(np_flags, buf + 8); + + buf += 12; + } + + r_len += 12; + + int total = np.get_name_count() + np.get_subname_count(); + + for (int i = 0; i < total; i++) { + String str; + + if (i < np.get_name_count()) { + str = np.get_name(i); + } else { + str = np.get_subname(i - np.get_name_count()); + } + + CharString utf8 = str.utf8(); + + int pad = 0; + + if (utf8.length() % 4) { + pad = 4 - utf8.length() % 4; + } + + if (buf) { + encode_uint32(utf8.length(), buf); + buf += 4; + memcpy(buf, utf8.get_data(), utf8.length()); + buf += utf8.length(); + memset(buf, 0, pad); + buf += pad; + } + + r_len += 4 + utf8.length() + pad; + } + + } break; + case Variant::STRING: + case Variant::STRING_NAME: { + _encode_string(p_variant, buf, r_len); + + } break; + + // Math types. + case Variant::VECTOR2: { + if (buf) { + Vector2 v2 = p_variant; + _encode_real(v2.x, &buf[0], p_real_t_is_double); + _encode_real(v2.y, &buf[sizeof(real_t)], p_real_t_is_double); + } + + r_len += 2 * sizeof(real_t); + + } break; + case Variant::VECTOR2I: { + if (buf) { + Vector2i v2 = p_variant; + encode_uint32(v2.x, &buf[0]); + encode_uint32(v2.y, &buf[4]); + } + + r_len += 2 * 4; + + } break; + case Variant::RECT2: { + if (buf) { + Rect2 r2 = p_variant; + _encode_real(r2.position.x, &buf[0], p_real_t_is_double); + _encode_real(r2.position.y, &buf[sizeof(real_t)], p_real_t_is_double); + _encode_real(r2.size.x, &buf[sizeof(real_t) * 2], p_real_t_is_double); + _encode_real(r2.size.y, &buf[sizeof(real_t) * 3], p_real_t_is_double); + } + r_len += 4 * sizeof(real_t); + + } break; + case Variant::RECT2I: { + if (buf) { + Rect2i r2 = p_variant; + encode_uint32(r2.position.x, &buf[0]); + encode_uint32(r2.position.y, &buf[4]); + encode_uint32(r2.size.x, &buf[8]); + encode_uint32(r2.size.y, &buf[12]); + } + r_len += 4 * 4; + + } break; + case Variant::VECTOR3: { + if (buf) { + Vector3 v3 = p_variant; + _encode_real(v3.x, &buf[0], p_real_t_is_double); + _encode_real(v3.y, &buf[sizeof(real_t)], p_real_t_is_double); + _encode_real(v3.z, &buf[sizeof(real_t) * 2], p_real_t_is_double); + } + + r_len += 3 * sizeof(real_t); + + } break; + case Variant::VECTOR3I: { + if (buf) { + Vector3i v3 = p_variant; + encode_uint32(v3.x, &buf[0]); + encode_uint32(v3.y, &buf[4]); + encode_uint32(v3.z, &buf[8]); + } + + r_len += 3 * 4; + + } break; + case Variant::TRANSFORM2D: { + if (buf) { + Transform2D val = p_variant; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 2; j++) { + memcpy(&buf[(i * 2 + j) * sizeof(real_t)], &val.columns[i][j], sizeof(real_t)); + } + } + } + + r_len += 6 * sizeof(real_t); + + } break; + case Variant::VECTOR4: { + if (buf) { + Vector4 v4 = p_variant; + _encode_real(v4.x, &buf[0], p_real_t_is_double); + _encode_real(v4.y, &buf[sizeof(real_t)], p_real_t_is_double); + _encode_real(v4.z, &buf[sizeof(real_t) * 2], p_real_t_is_double); + _encode_real(v4.w, &buf[sizeof(real_t) * 3], p_real_t_is_double); + } + + r_len += 4 * sizeof(real_t); + + } break; + case Variant::VECTOR4I: { + if (buf) { + Vector4i v4 = p_variant; + encode_uint32(v4.x, &buf[0]); + encode_uint32(v4.y, &buf[4]); + encode_uint32(v4.z, &buf[8]); + encode_uint32(v4.w, &buf[12]); + } + + r_len += 4 * 4; + + } break; + case Variant::PLANE: { + if (buf) { + Plane p = p_variant; + _encode_real(p.normal.x, &buf[0], p_real_t_is_double); + _encode_real(p.normal.y, &buf[sizeof(real_t)], p_real_t_is_double); + _encode_real(p.normal.z, &buf[sizeof(real_t) * 2], p_real_t_is_double); + _encode_real(p.d, &buf[sizeof(real_t) * 3], p_real_t_is_double); + } + + r_len += 4 * sizeof(real_t); + + } break; + case Variant::QUATERNION: { + if (buf) { + Quaternion q = p_variant; + _encode_real(q.x, &buf[0], p_real_t_is_double); + _encode_real(q.y, &buf[sizeof(real_t)], p_real_t_is_double); + _encode_real(q.z, &buf[sizeof(real_t) * 2], p_real_t_is_double); + _encode_real(q.w, &buf[sizeof(real_t) * 3], p_real_t_is_double); + } + + r_len += 4 * sizeof(real_t); + + } break; + case Variant::AABB: { + if (buf) { + AABB aabb = p_variant; + _encode_real(aabb.position.x, &buf[0], p_real_t_is_double); + _encode_real(aabb.position.y, &buf[sizeof(real_t)], p_real_t_is_double); + _encode_real(aabb.position.z, &buf[sizeof(real_t) * 2], p_real_t_is_double); + _encode_real(aabb.size.x, &buf[sizeof(real_t) * 3], p_real_t_is_double); + _encode_real(aabb.size.y, &buf[sizeof(real_t) * 4], p_real_t_is_double); + _encode_real(aabb.size.z, &buf[sizeof(real_t) * 5], p_real_t_is_double); + } + + r_len += 6 * sizeof(real_t); + + } break; + case Variant::BASIS: { + if (buf) { + Basis val = p_variant; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + memcpy(&buf[(i * 3 + j) * sizeof(real_t)], &val.rows[i][j], sizeof(real_t)); + } + } + } + + r_len += 9 * sizeof(real_t); + + } break; + case Variant::TRANSFORM3D: { + if (buf) { + Transform3D val = p_variant; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + memcpy(&buf[(i * 3 + j) * sizeof(real_t)], &val.basis.rows[i][j], sizeof(real_t)); + } + } + + _encode_real(val.origin.x, &buf[sizeof(real_t) * 9], p_real_t_is_double); + _encode_real(val.origin.y, &buf[sizeof(real_t) * 10], p_real_t_is_double); + _encode_real(val.origin.z, &buf[sizeof(real_t) * 11], p_real_t_is_double); + } + + r_len += 12 * sizeof(real_t); + + } break; + case Variant::PROJECTION: { + if (buf) { + Projection val = p_variant; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + memcpy(&buf[(i * 4 + j) * sizeof(real_t)], &val.columns[i][j], sizeof(real_t)); + } + } + } + + r_len += 16 * sizeof(real_t); + + } break; + + // Misc types. + case Variant::COLOR: { + if (buf) { + Color c = p_variant; + encode_float(c.r, &buf[0]); + encode_float(c.g, &buf[4]); + encode_float(c.b, &buf[8]); + encode_float(c.a, &buf[12]); + } + + r_len += 4 * 4; // Colors should always be in single-precision. + + } break; + case Variant::RID: { + RID rid = p_variant; + + if (buf) { + encode_uint64(rid.get_id(), buf); + } + r_len += 8; + } break; + case Variant::OBJECT: { + if (p_full_objects) { + Object *obj = p_variant; + if (!obj) { + if (buf) { + encode_uint32(0, buf); + } + r_len += 4; + + } else { + ERR_FAIL_COND_V(!ClassDB::can_instantiate(obj->get_class()), ERR_INVALID_PARAMETER); + + _encode_string(obj->get_class(), buf, r_len); + + List props; + obj->get_property_list(&props); + + int pc = 0; + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + pc++; + } + + if (buf) { + encode_uint32(pc, buf); + buf += 4; + } + + r_len += 4; + + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + _encode_string(E.name, buf, r_len); + + Variant value; + + if (E.name == CoreStringName(script)) { + Ref