diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 02b07e8..4536262 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -14,9 +14,6 @@ jobs: strategy: fail-fast: false matrix: - mode: - - Client - - Server arch: - AMD64 - ARM64 @@ -31,40 +28,19 @@ jobs: submodules: true # Dependencies - name: Install Dependencies - run: ./scripts/install-dependencies.sh ${{ matrix.arch }} + run: ./scripts/install-dependencies.sh build ${{ matrix.arch }} # Build - name: Build - run: ./scripts/build.mjs appimage ${{ matrix.mode }} ${{ matrix.arch }} + run: ./scripts/build.mjs appimage ${{ matrix.arch }} - name: Upload Artifacts uses: christopherhx/gitea-upload-artifact@v4 with: - name: ${{ matrix.mode }} (${{ matrix.arch }}) + name: ${{ matrix.arch }} path: ./out/*.AppImage* if-no-files-found: error # Test Project test: - strategy: - fail-fast: false - matrix: - mode: - - Client - - Server - name: Test - runs-on: ubuntu-latest - container: node:lts-bullseye - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - with: - submodules: true - # Dependencies - - name: Install Dependencies - run: ./scripts/install-dependencies.sh - # Test - - name: Test - run: ./scripts/test.sh ${{ matrix.mode }} - # Test Project On ARM - rpi-test: + needs: build strategy: fail-fast: false matrix: @@ -72,10 +48,11 @@ jobs: - Client - Server arch: + - AMD64 - ARM64 - ARMHF - name: Raspberry Pi Test - runs-on: raspberry-pi + name: Test + runs-on: ${{ startsWith(matrix.arch, 'ARM') && 'raspberry-pi' || 'ubuntu-latest' }} container: node:lts-bullseye steps: - name: Checkout Repository @@ -84,12 +61,19 @@ jobs: submodules: true # Dependencies - name: Install Dependencies - run: ./scripts/install-dependencies.sh ${{ matrix.arch }} + run: ./scripts/install-dependencies.sh test ${{ matrix.arch }} + # Download Artifact + - name: Download Artifact + uses: christopherhx/gitea-download-artifact@v4 + with: + name: ${{ matrix.arch }} + path: out # Test - name: Test - run: ./scripts/test.sh ${{ matrix.mode }} + run: ./scripts/test.sh ${{ matrix.mode }} ${{ matrix.arch }} # Example Mods example-mods: + needs: build name: Build Example Mods runs-on: ubuntu-latest container: node:lts-bullseye @@ -100,16 +84,18 @@ jobs: submodules: true # Dependencies - name: Install Dependencies - run: ./scripts/install-dependencies.sh - - name: Install ARM Toolchain - run: apt-get install --no-install-recommends -y g++-arm-linux-gnueabihf gcc-arm-linux-gnueabihf - # Build SDK - - name: Build SDK + run: ./scripts/install-dependencies.sh example_mods amd64 + # SDK + - name: Download SDK + uses: christopherhx/gitea-download-artifact@v4 + with: + name: AMD64 + path: out + - name: Extract SDK run: | - ./scripts/build.mjs none client host - export _MCPI_SKIP_ROOT_CHECK=1 - export DISPLAY= - ./out/client/host/usr/bin/minecraft-pi-reborn-client --copy-sdk + ./scripts/fix-appimage-for-docker.sh ./out/*.AppImage + chmod +x ./out/*.AppImage + ./out/*.AppImage --copy-sdk # Build Example Mods - name: Build Example Mods run: ./example-mods/build.sh @@ -122,7 +108,10 @@ jobs: # Create Release release: if: startsWith(github.ref, 'refs/tags/') - needs: build + needs: + - build + - test + - example-mods name: Release runs-on: ubuntu-latest container: node:lts-bullseye diff --git a/cmake/options/extra-options.cmake b/cmake/options/extra-options.cmake index fc83462..2f50538 100644 --- a/cmake/options/extra-options.cmake +++ b/cmake/options/extra-options.cmake @@ -6,10 +6,6 @@ if(MCPI_IS_APPIMAGE_BUILD AND MCPI_IS_FLATPAK_BUILD) message(FATAL_ERROR "Invalid Build Configuration") endif() -# Server/Headless Builds -mcpi_option(SERVER_MODE "Server Mode" BOOL FALSE) -mcpi_option(HEADLESS_MODE "Headless Mode" BOOL "${MCPI_SERVER_MODE}") - # Prebuilt ARMHF Toolchain if(BUILD_NATIVE_COMPONENTS) set(MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN FALSE) @@ -22,16 +18,12 @@ if(BUILD_NATIVE_COMPONENTS) endif() # Media Layer -if(NOT MCPI_HEADLESS_MODE) - set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE FALSE) - if(BUILD_NATIVE_COMPONENTS AND NOT IS_ARM_TARGETING) - set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE TRUE) - endif() - mcpi_option(USE_MEDIA_LAYER_TRAMPOLINE "Whether To Enable The Media Layer Trampoline" BOOL "${DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE}") - mcpi_option(USE_GLES1_COMPATIBILITY_LAYER "Whether To Enable The GLESv1_CM Compatibility Layer" BOOL TRUE) -else() - set(MCPI_USE_MEDIA_LAYER_TRAMPOLINE FALSE) +set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE FALSE) +if(BUILD_NATIVE_COMPONENTS AND NOT IS_ARM_TARGETING) + set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE TRUE) endif() +mcpi_option(USE_MEDIA_LAYER_TRAMPOLINE "Whether To Enable The Media Layer Trampoline" BOOL "${DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE}") +mcpi_option(USE_GLES1_COMPATIBILITY_LAYER "Whether To Enable The GLESv1_CM Compatibility Layer" BOOL TRUE) if(MCPI_USE_MEDIA_LAYER_TRAMPOLINE) set(BUILD_MEDIA_LAYER_CORE "${BUILD_NATIVE_COMPONENTS}") else() @@ -40,30 +32,12 @@ endif() # Specify Variant Name set(MCPI_VARIANT_NAME "minecraft-pi-reborn") -if(MCPI_SERVER_MODE) - string(APPEND MCPI_VARIANT_NAME "-server") -else() - string(APPEND MCPI_VARIANT_NAME "-client") -endif() # App ID -set(DEFAULT_APP_ID "com.thebrokenrail.MCPIReborn") -if(MCPI_SERVER_MODE) - string(APPEND DEFAULT_APP_ID "Server") -else() - string(APPEND DEFAULT_APP_ID "Client") -endif() -mcpi_option(APP_ID "App ID" STRING "${DEFAULT_APP_ID}") +mcpi_option(APP_ID "App ID" STRING "com.thebrokenrail.MCPIReborn") # App Title -mcpi_option(APP_BASE_TITLE "Base App Title" STRING "Minecraft: Pi Edition: Reborn") -set(DEFAULT_APP_TITLE "${MCPI_APP_BASE_TITLE}") -if(MCPI_SERVER_MODE) - string(APPEND DEFAULT_APP_TITLE " (Server)") -else() - string(APPEND DEFAULT_APP_TITLE " (Client)") -endif() -mcpi_option(APP_TITLE "App Title" STRING "${DEFAULT_APP_TITLE}") +mcpi_option(APP_TITLE "App Title" STRING "Minecraft: Pi Edition: Reborn") # Skin Server mcpi_option(SKIN_SERVER "Skin Server" STRING "https://raw.githubusercontent.com/MCPI-Revival/Skins/data") diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 2045929..94d2b22 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -1,7 +1,7 @@ project(dependencies) # stb_image -if(BUILD_ARM_COMPONENTS AND NOT MCPI_HEADLESS_MODE) +if(BUILD_ARM_COMPONENTS) add_subdirectory(stb_image) endif() # Minecraft: Pi Edition @@ -9,21 +9,21 @@ if(BUILD_ARM_COMPONENTS AND NOT MCPI_OPEN_SOURCE_ONLY) add_subdirectory(minecraft-pi) endif() # Zenity (Minimal Build) -if(BUILD_NATIVE_COMPONENTS AND NOT MCPI_SERVER_MODE) +if(BUILD_NATIVE_COMPONENTS) add_subdirectory(zenity) endif() # LIEF -if(BUILD_NATIVE_COMPONENTS OR (BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE)) +if(BUILD_NATIVE_COMPONENTS OR BUILD_MEDIA_LAYER_CORE) add_subdirectory(LIEF) endif() # Extra Runtime add_subdirectory(runtime) # GLFW -if(BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE) +if(BUILD_MEDIA_LAYER_CORE) add_subdirectory(glfw) endif() # GLES Compatibility Layer -if(BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE AND MCPI_USE_GLES1_COMPATIBILITY_LAYER) +if(BUILD_MEDIA_LAYER_CORE AND MCPI_USE_GLES1_COMPATIBILITY_LAYER) add_subdirectory(gles-compatibility-layer) endif() # UTF8-CPP diff --git a/dependencies/minecraft-pi/CMakeLists.txt b/dependencies/minecraft-pi/CMakeLists.txt index 76017fb..b7c4f72 100644 --- a/dependencies/minecraft-pi/CMakeLists.txt +++ b/dependencies/minecraft-pi/CMakeLists.txt @@ -17,13 +17,5 @@ install( DESTINATION "${MCPI_INSTALL_DIR}/game" USE_SOURCE_PERMISSIONS REGEX "api" EXCLUDE - REGEX "data" EXCLUDE ) -if(NOT MCPI_HEADLESS_MODE) - install( - DIRECTORY "${minecraft-pi_SOURCE_DIR}/data/" - DESTINATION "${MCPI_INSTALL_DIR}/game/data" - USE_SOURCE_PERMISSIONS - ) -endif() install_symlink("game/minecraft-pi" "${MCPI_INSTALL_DIR}/minecraft-pi") diff --git a/dependencies/runtime/src b/dependencies/runtime/src index 790e791..aa87488 160000 --- a/dependencies/runtime/src +++ b/dependencies/runtime/src @@ -1 +1 @@ -Subproject commit 790e7918b1d63102b7a7f39dc1db006b2a5abf48 +Subproject commit aa874884072a700750956e241c7a1ce91dbfa74c diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6e87600..e6ce50d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -32,6 +32,8 @@ * Fix Furnace Visual Bug When Using Lava Bucket As Fuel * Add Splash Text To Start Screen * `overwrite_calls` Now Scans VTables +* Unify Server/Client Builds +* Use `|` For Separator In `servers.txt` Instead Of `:` **2.5.3** * Add `Replace Block Highlight With Outline` Feature Flag (Enabled By Default) diff --git a/docs/DEDICATED_SERVER.md b/docs/DEDICATED_SERVER.md index 5b70345..c721851 100644 --- a/docs/DEDICATED_SERVER.md +++ b/docs/DEDICATED_SERVER.md @@ -4,11 +4,11 @@ The dedicated server is a version of Minecraft: Pi Edition modified to run in a This server is also compatible with MCPE Alpha v0.6.1[^1]. ## Setup -To use, install and run the `minecraft-pi-reborn-server` AppImage. It will generate the world and `server.properties` in the current directory. +To use, run the normal AppImage with the `--server` argument. It will generate the world and `server.properties` in the current directory. ## Server Limitations * Player data is not saved because of limitations with MCPE LAN worlds * An easy workaround is to place your inventory in a chest before logging off -* Survival Mode servers are incompatible with unmodded MCPI +* Survival Mode servers are incompatible with un-modded MCPI -[^1]: The exception to this is buckets, those will crash MCPE players. +[^1]: The exception to this is buckets and other modded items, those will crash MCPE players. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 0c93137..b3e18ac 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -27,7 +27,7 @@ The AppImage requires Debian Bullseye or higher. This is equivalent to Ubuntu 20 It also requires some additional packages. To install them, run: ```sh -sudo apt install -y libfuse2 libgtk-3-0 libopenal1 +sudo apt install -y libfuse2 libgtk-3-0 libopenal1 libglib2.0-0 ``` diff --git a/example-mods/README.md b/example-mods/README.md index 223da96..11e1c79 100644 --- a/example-mods/README.md +++ b/example-mods/README.md @@ -1,19 +1,11 @@ # Example Mods -This is an example of a mod that can be built using the modding SDK. +These are example mods that can be built using the modding SDK. * **Expanded Creative Mod**: This specific mod adds even more items and blocks to the Creative Inventory. It was originally by [@Bigjango13](https://github.com/bigjango13). -* **Chat Commands Mod**: This specific mod makes an chat message starting with a ``/`` handled by the MCPI API. +* **Chat Commands Mod**: This specific mod makes a chat message starting with `/` handled by the MCPI API. * **Recipes Mod**: This specific mod demos custom recipes. ## The SDK The modding SDK is a collection of exported CMake targets that allows anyone to create their own MCPI mod! -The SDK is copied to ``~/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake`` whenever MCPI-Reborn is started. - -## How do I use this? -```sh -mkdir build -cd build -cmake .. -cp libexpanded-creative.so ~/.minecraft-pi/mods -``` +The SDK is copied to `~/.minecraft-pi/sdk` whenever MCPI-Reborn is started. diff --git a/example-mods/chat-commands/CMakeLists.txt b/example-mods/chat-commands/CMakeLists.txt index 90a8e93..17f2939 100644 --- a/example-mods/chat-commands/CMakeLists.txt +++ b/example-mods/chat-commands/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_PROCESSOR "arm") project(chat-commands) # Include SDK -include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake") +include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn/sdk/sdk.cmake") # Build add_library(chat-commands SHARED chat-commands.cpp) diff --git a/example-mods/expanded-creative/CMakeLists.txt b/example-mods/expanded-creative/CMakeLists.txt index 524b11d..0f21c3f 100644 --- a/example-mods/expanded-creative/CMakeLists.txt +++ b/example-mods/expanded-creative/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_PROCESSOR "arm") project(expanded-creative) # Include SDK -include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake") +include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn/sdk/sdk.cmake") # Build add_library(expanded-creative SHARED expanded-creative.cpp) diff --git a/example-mods/recipes/CMakeLists.txt b/example-mods/recipes/CMakeLists.txt index b1be8ad..4daf77e 100644 --- a/example-mods/recipes/CMakeLists.txt +++ b/example-mods/recipes/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_PROCESSOR "arm") project(recipes) # Include SDK -include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake") +include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn/sdk/sdk.cmake") # Build add_library(recipes SHARED recipes.cpp) diff --git a/images/CMakeLists.txt b/images/CMakeLists.txt index c5dc5c7..efd6902 100644 --- a/images/CMakeLists.txt +++ b/images/CMakeLists.txt @@ -1,21 +1,17 @@ project(images) # Title Background -if(NOT MCPI_HEADLESS_MODE) - install( - FILES "background.png" - DESTINATION "${MCPI_INSTALL_DIR}/data/images/gui" - RENAME "titleBG.png" - ) -endif() +install( + FILES "background.png" + DESTINATION "${MCPI_INSTALL_DIR}/data/images/gui" + RENAME "titleBG.png" +) # Chest Model -if(NOT MCPI_HEADLESS_MODE) - install( - FILES "chest.png" - DESTINATION "${MCPI_INSTALL_DIR}/data/images/item" - ) -endif() +install( + FILES "chest.png" + DESTINATION "${MCPI_INSTALL_DIR}/data/images/item" +) # Icon install( diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 49c4360..f5d8afe 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -10,15 +10,11 @@ add_executable(launcher src/mods.cpp src/options/parser.cpp src/main.cpp + src/client/configuration.cpp + src/client/cache.cpp + src/client/available-feature-flags # Show In IDE ) -if(NOT MCPI_SERVER_MODE) - embed_resource(launcher src/client/available-feature-flags) - target_sources(launcher PRIVATE - src/client/configuration.cpp - src/client/cache.cpp - src/client/available-feature-flags # Show In IDE - ) -endif() +embed_resource(launcher src/client/available-feature-flags) target_link_libraries(launcher reborn-util LIB_LIEF trampoline-headers) # RPath set_target_properties(launcher PROPERTIES INSTALL_RPATH "$ORIGIN/lib/native") @@ -38,18 +34,10 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop" "Type=Application\n" "Categories=Game;\n" ) -if(MCPI_HEADLESS_MODE) - file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop" - "Terminal=true\n" - "NoDisplay=true\n" - ) -else() - file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop" - "Terminal=false\n" - "StartupNotify=false\n" - "StartupWMClass=${MCPI_APP_ID}\n" - ) -endif() +file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop" + "Terminal=true\n" + "NoDisplay=true\n" +) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop" DESTINATION "${MCPI_SHARE_DIR}/applications" @@ -100,17 +88,11 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml" " \n" " \n" " \n" -) -if(NOT MCPI_HEADLESS_MODE) - file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml" - " \n" - " \n" - " https://gitea.thebrokenrail.com/TheBrokenRail/minecraft-pi-reborn/raw/branch/master/images/start.png\n" - " \n" - " \n" - ) -endif() -file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml" + " \n" + " \n" + " https://gitea.thebrokenrail.com/TheBrokenRail/minecraft-pi-reborn/raw/branch/master/images/start.png\n" + " \n" + " \n" "\n" ) install( diff --git a/launcher/src/client/cache.cpp b/launcher/src/client/cache.cpp index ad11681..a81259f 100644 --- a/launcher/src/client/cache.cpp +++ b/launcher/src/client/cache.cpp @@ -17,7 +17,7 @@ static std::string get_cache_path() { if (home == nullptr) { IMPOSSIBLE(); } - return std::string(home) + HOME_SUBDIRECTORY_FOR_GAME_DATA "/.launcher-cache"; + return std::string(home) + get_home_subdirectory_for_game_data() + "/.launcher-cache"; } // Load @@ -165,7 +165,8 @@ void wipe_cache() { INFO("Wiping Launcher Cache..."); // Unlink File - if (unlink(get_cache_path().c_str()) != 0) { + const int ret = unlink(get_cache_path().c_str()); + if (ret != 0 && errno != ENOENT) { WARN("Failure While Wiping Cache: %s", strerror(errno)); } } diff --git a/launcher/src/client/configuration.cpp b/launcher/src/client/configuration.cpp index 4b9416c..45c1007 100644 --- a/launcher/src/client/configuration.cpp +++ b/launcher/src/client/configuration.cpp @@ -108,6 +108,7 @@ static void run_command_and_set_env(const char *env_name, const char *command[]) // Use Zenity To Set Environmental Variable #define DIALOG_TITLE "Launcher" static void run_zenity_and_set_env(const char *env_name, std::vector command) { + reborn_check_display(); // Create Full Command std::vector full_command; full_command.push_back("zenity"); @@ -146,31 +147,16 @@ void handle_non_launch_client_only_commands(const options_t &options) { }); exit(EXIT_SUCCESS); } -} - -// Check Environment -void check_environment_client() { - // Don't Run As Root - if (getenv("_MCPI_SKIP_ROOT_CHECK") == nullptr && (getuid() == 0 || geteuid() == 0)) { - ERR("Don't Run As Root"); + // Wipe Cache If Needed + if (options.wipe_cache) { + wipe_cache(); + exit(EXIT_SUCCESS); } - - // Check For Display -#ifndef MCPI_HEADLESS_MODE - if (getenv("DISPLAY") == nullptr && getenv("WAYLAND_DISPLAY") == nullptr) { - ERR("No display attached! Make sure $DISPLAY or $WAYLAND_DISPLAY is set."); - } -#endif } // Configure Client Options #define LIST_DIALOG_SIZE "400" void configure_client(const options_t &options) { - // Wipe Cache If Needed - if (options.wipe_cache) { - wipe_cache(); - } - // Load Cache launcher_cache cache = options.no_cache ? empty_cache : load_cache(); diff --git a/launcher/src/client/configuration.h b/launcher/src/client/configuration.h index eaa5afe..e7c20ce 100644 --- a/launcher/src/client/configuration.h +++ b/launcher/src/client/configuration.h @@ -16,8 +16,5 @@ void load_available_feature_flags(const std::function &callba // Handle Non-Launch Commands void handle_non_launch_client_only_commands(const options_t &options); -// Check Environment -void check_environment_client(); - // Configure Client Options void configure_client(const options_t &options); \ No newline at end of file diff --git a/launcher/src/crash-report.c b/launcher/src/crash-report.c index 04ba79e..fe5fd6f 100644 --- a/launcher/src/crash-report.c +++ b/launcher/src/crash-report.c @@ -15,7 +15,6 @@ #include "crash-report.h" // Show Crash Report Dialog -#ifndef MCPI_HEADLESS_MODE #define DIALOG_TITLE "Crash Report" #define CRASH_REPORT_DIALOG_WIDTH "640" #define CRASH_REPORT_DIALOG_HEIGHT "480" @@ -35,7 +34,7 @@ static void show_report(const char *log_filename) { "--width", CRASH_REPORT_DIALOG_WIDTH, "--height", CRASH_REPORT_DIALOG_HEIGHT, "--text-info", - "--text", MCPI_APP_BASE_TITLE " has crashed!\n\nNeed help? Consider asking on the Discord server! If you believe this is a problem with " MCPI_APP_BASE_TITLE " itself, please upload this crash report to the #bugs Discord channel.", + "--text", MCPI_APP_TITLE " has crashed!\n\nNeed help? Consider asking on the Discord server! If you believe this is a problem with " MCPI_APP_TITLE " itself, please upload this crash report to the #bugs Discord channel.", "--filename", log_filename, "--no-wrap", "--font", "Monospace", @@ -46,7 +45,6 @@ static void show_report(const char *log_filename) { safe_execvpe(command, (const char *const *) environ); } } -#endif // Exit Handler static void exit_handler(__attribute__((unused)) int signal) { @@ -248,11 +246,9 @@ void setup_crash_report() { unsetenv(MCPI_LOG_ENV); // Show Crash Log -#ifndef MCPI_HEADLESS_MODE - if (is_crash) { + if (is_crash && !reborn_is_headless()) { show_report(log_filename); } -#endif // Exit exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE); diff --git a/launcher/src/main.cpp b/launcher/src/main.cpp index b48eb8a..084488a 100644 --- a/launcher/src/main.cpp +++ b/launcher/src/main.cpp @@ -6,9 +6,7 @@ #include "options/parser.h" #include "crash-report.h" #include "util.h" -#ifndef MCPI_SERVER_MODE #include "client/configuration.h" -#endif // Bind Options To Environmental Variable static void bind_to_env(const char *env, const bool value) { @@ -19,16 +17,14 @@ static void bind_to_env(const char *env, const bool value) { } static void setup_environment(const options_t &options) { // Passthrough Options To Game -#ifndef MCPI_SERVER_MODE + bind_to_env(MCPI_SERVER_MODE_ENV, options.server_mode); bind_to_env("_MCPI_BENCHMARK", options.benchmark); -#else bind_to_env("_MCPI_ONLY_GENERATE", options.only_generate); -#endif + bind_to_env(MCPI_FORCE_HEADLESS_ENV, options.force_headless); + bind_to_env(MCPI_FORCE_NON_HEADLESS_ENV, options.force_non_headless); // GTK Dark Mode -#ifndef MCPI_HEADLESS_MODE set_and_print_env("GTK_THEME", "Adwaita:dark"); -#endif // Configure PATH { @@ -72,9 +68,6 @@ static void start_game(const options_t &options) { // Disable stdout Buffering setvbuf(stdout, nullptr, _IONBF, 0); - // Environemntal Variable Options - setup_environment(options); - // Setup Crash Reporting if (!options.disable_crash_report) { setup_log_file(); @@ -92,35 +85,35 @@ static void start_game(const options_t &options) { sigaction(SIGTERM, &act_sigterm, nullptr); // Setup Home -#ifndef MCPI_SERVER_MODE - // Ensure $HOME - const char *home = getenv("HOME"); - if (home == nullptr) { - ERR("$HOME Isn't Set"); - } - // Create If Needed - { - std::string minecraft_folder = std::string(home) + HOME_SUBDIRECTORY_FOR_GAME_DATA; - struct stat tmp_stat = {}; - bool exists = stat(minecraft_folder.c_str(), &tmp_stat) != 0 ? false : S_ISDIR(tmp_stat.st_mode); - if (!exists) { - // Doesn't Exist - if (mkdir(minecraft_folder.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) { - ERR("Unable To Create Data Directory: %s", strerror(errno)); + if (!reborn_is_server()) { + // Ensure $HOME + const char *home = getenv("HOME"); + if (home == nullptr) { + ERR("$HOME Is Not Set"); + } + // Create If Needed + { + std::string minecraft_folder = std::string(home) + get_home_subdirectory_for_game_data(); + struct stat tmp_stat = {}; + bool exists = stat(minecraft_folder.c_str(), &tmp_stat) != 0 ? false : S_ISDIR(tmp_stat.st_mode); + if (!exists) { + // Doesn't Exist + if (mkdir(minecraft_folder.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) { + ERR("Unable To Create Data Directory: %s", strerror(errno)); + } } } + } else { + // Set Home To Current Directory, So World Data Is Stored There + char *launch_directory = getcwd(nullptr, 0); + set_and_print_env("HOME", launch_directory); + free(launch_directory); } -#else - // Set Home To Current Directory, So World Data Is Stored There - char *launch_directory = getcwd(NULL, 0); - set_and_print_env("HOME", launch_directory); - free(launch_directory); -#endif // Configure Client Options -#ifndef MCPI_SERVER_MODE - configure_client(options); -#endif + if (!reborn_is_server()) { + configure_client(options); + } // Bootstrap bootstrap(); @@ -138,17 +131,12 @@ int main(int argc, char *argv[]) { unsetenv(MCPI_LOG_ENV); bind_to_env(MCPI_DEBUG_ENV, options.debug); + // Setup Environment + setup_environment(options); + // Handle Non-Launch Commands (Copy SDK, Print Feature Flags, Etc) handle_non_launch_commands(options); -#ifndef MCPI_SERVER_MODE handle_non_launch_client_only_commands(options); -#endif - - // Check Environment -#ifndef MCPI_SERVER_MODE - // Code After This Can Safely Open A Window - check_environment_client(); -#endif // Start The Game start_game(options); diff --git a/launcher/src/mods.cpp b/launcher/src/mods.cpp index 2ad3d39..11932ca 100644 --- a/launcher/src/mods.cpp +++ b/launcher/src/mods.cpp @@ -60,7 +60,7 @@ std::string bootstrap_mods(const std::string &binary_directory) { // ~/.minecraft-pi/mods { // Get Mods Folder - std::string mods_folder = std::string(getenv("HOME")) + HOME_SUBDIRECTORY_FOR_GAME_DATA SUBDIRECTORY_FOR_MODS; + std::string mods_folder = std::string(getenv("HOME")) + get_home_subdirectory_for_game_data() + SUBDIRECTORY_FOR_MODS; // Load Mods From ./mods load(preload, mods_folder); } diff --git a/launcher/src/options/option-list.h b/launcher/src/options/option-list.h index b69d64e..315688b 100644 --- a/launcher/src/options/option-list.h +++ b/launcher/src/options/option-list.h @@ -1,12 +1,12 @@ OPTION(debug, "debug", 'd', "Enable Debug Logging (" MCPI_DEBUG_ENV ")") OPTION(copy_sdk, "copy-sdk", -2, "Extract Modding SDK And Exit") OPTION(disable_crash_report, "disable-crash-report", -1, "Disable Crash Report Dialog") -#ifndef MCPI_SERVER_MODE -OPTION(use_default, "default", -3, "Skip Configuration Dialogs") -OPTION(no_cache, "no-cache", -4, "Disable Configuration Cache") -OPTION(wipe_cache, "wipe-cache", -5, "Wipe Cached Configuration") -OPTION(print_available_feature_flags, "print-available-feature-flags", -6, "Print Available Feature Flags") -OPTION(benchmark, "benchmark", -7, "Run Benchmark") -#else -OPTION(only_generate, "only-generate", -8, "Generate World And Exit") -#endif \ No newline at end of file +OPTION(use_default, "default", -3, "Skip Client-Mode Configuration Dialogs") +OPTION(no_cache, "no-cache", -4, "Disable Client-Mode Configuration Cache") +OPTION(wipe_cache, "wipe-cache", -5, "Wipe Cached Client-Mode Configuration And Exit") +OPTION(print_available_feature_flags, "print-available-feature-flags", -6, "Print Available Client-Mode Feature Flags") +OPTION(benchmark, "benchmark", -7, "Run Client-Mode Benchmark") +OPTION(only_generate, "only-generate", -8, "Generate World And Exit (Server-Mode Only)") +OPTION(force_headless, "force-headless", -9, "Force Disable Game Rendering") +OPTION(force_non_headless, "force-non-headless", -10, "Force Enable Game Rendering") +OPTION(server_mode, "server", -11, "Run In Server-Mode") \ No newline at end of file diff --git a/launcher/src/sdk.cpp b/launcher/src/sdk.cpp index cd3b1bf..2dde7e9 100644 --- a/launcher/src/sdk.cpp +++ b/launcher/src/sdk.cpp @@ -14,7 +14,7 @@ } // Copy SDK Into ~/.minecraft-pi -#define HOME_SUBDIRECTORY_FOR_SDK HOME_SUBDIRECTORY_FOR_GAME_DATA "/sdk" +#define HOME_SUBDIRECTORY_FOR_SDK (std::string(get_home_subdirectory_for_game_data()) + "/sdk") void copy_sdk(const std::string &binary_directory, const bool log_with_debug) { // Ensure SDK Directory std::string sdk_path; diff --git a/libreborn/include/libreborn/config.h.in b/libreborn/include/libreborn/config.h.in index dab2a3e..519c3f2 100644 --- a/libreborn/include/libreborn/config.h.in +++ b/libreborn/include/libreborn/config.h.in @@ -1,7 +1,5 @@ #pragma once -#cmakedefine MCPI_SERVER_MODE -#cmakedefine MCPI_HEADLESS_MODE #cmakedefine MCPI_IS_APPIMAGE_BUILD #cmakedefine MCPI_IS_FLATPAK_BUILD #cmakedefine MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN diff --git a/libreborn/include/libreborn/home.h b/libreborn/include/libreborn/home.h deleted file mode 100644 index 65da96b..0000000 --- a/libreborn/include/libreborn/home.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -// Minecraft Pi Game Data Root -#ifndef MCPI_SERVER_MODE -// Store Game Data In "~/.minecraft-pi" Instead Of "~/.minecraft" To Avoid Conflicts -#define HOME_SUBDIRECTORY_FOR_GAME_DATA "/.minecraft-pi" -#else -// Store Game Data In $HOME Root (In Server Mode, $HOME Is Changed To The Launch Directory) -#define HOME_SUBDIRECTORY_FOR_GAME_DATA "" -#endif diff --git a/libreborn/include/libreborn/libreborn.h b/libreborn/include/libreborn/libreborn.h index 4ef7cba..c9068b4 100644 --- a/libreborn/include/libreborn/libreborn.h +++ b/libreborn/include/libreborn/libreborn.h @@ -5,5 +5,4 @@ #include "util.h" #include "string.h" #include "exec.h" -#include "home.h" #include "patch.h" diff --git a/libreborn/include/libreborn/util.h b/libreborn/include/libreborn/util.h index d4960a7..c387a7d 100644 --- a/libreborn/include/libreborn/util.h +++ b/libreborn/include/libreborn/util.h @@ -51,10 +51,19 @@ int lock_file(const char *file); void unlock_file(const char *file, int fd); // Access Configuration At Runtime +#define MCPI_SERVER_MODE_ENV "_MCPI_SERVER_MODE" +#define MCPI_FORCE_HEADLESS_ENV "_MCPI_FORCE_HEADLESS" +#define MCPI_FORCE_NON_HEADLESS_ENV "_MCPI_FORCE_NON_HEADLESS" const char *reborn_get_version(); int reborn_is_headless(); int reborn_is_server(); +// Check $DISPLAY +void reborn_check_display(); + +// Get Home Subdirectory +const char *get_home_subdirectory_for_game_data(); + // Customize VTable #define CUSTOM_VTABLE(name, parent) \ void _setup_##name##_vtable(parent##_vtable *vtable); \ diff --git a/libreborn/src/util/util.c b/libreborn/src/util/util.c index 6a37b7b..e45e250 100644 --- a/libreborn/src/util/util.c +++ b/libreborn/src/util/util.c @@ -50,16 +50,43 @@ const char *reborn_get_version() { return MCPI_VERSION; } int reborn_is_headless() { -#ifdef MCPI_HEADLESS_MODE - return 1; -#else - return 0; -#endif + static int ret; + static int is_set = 0; + if (!is_set) { + ret = reborn_is_server(); + if (getenv(MCPI_FORCE_HEADLESS_ENV)) { + ret = 1; + } else if (getenv(MCPI_FORCE_NON_HEADLESS_ENV)) { + ret = 0; + } + is_set = 1; + } + return ret; } int reborn_is_server() { -#ifdef MCPI_SERVER_MODE - return 1; -#else - return 0; -#endif + static int ret; + static int is_set = 0; + if (!is_set) { + ret = getenv(MCPI_SERVER_MODE_ENV) != NULL; + is_set = 1; + } + return ret; } + +// Check $DISPLAY +void reborn_check_display() { + if (!getenv("DISPLAY") && !getenv("WAYLAND_DISPLAY")) { + ERR("No display attached! Make sure $DISPLAY or $WAYLAND_DISPLAY is set."); + } +} + +// Home Subdirectory +const char *get_home_subdirectory_for_game_data() { + if (!reborn_is_server()) { + // Store Game Data In "~/.minecraft-pi" Instead Of "~/.minecraft" To Avoid Conflicts + return "/.minecraft-pi"; + } else { + // Store Game Data In $HOME Root (In Server Mode, $HOME Is Changed To The Launch Directory) + return ""; + } +} \ No newline at end of file diff --git a/media-layer/core/CMakeLists.txt b/media-layer/core/CMakeLists.txt index 6d66d9b..c6c2faf 100644 --- a/media-layer/core/CMakeLists.txt +++ b/media-layer/core/CMakeLists.txt @@ -1,17 +1,17 @@ project(media-layer-core) # OpenGL -if(NOT MCPI_HEADLESS_MODE) - add_subdirectory(gles) -endif() +add_subdirectory(gles) -# Configuration -set(CORE_SRC src/base.cpp src/media.c $) # SDL Re-Implementation Using GLFW -if(NOT MCPI_HEADLESS_MODE) - list(APPEND CORE_SRC src/audio/api.cpp src/audio/engine.c src/audio/file.cpp) -else() - list(APPEND CORE_SRC src/audio/stubs.c) -endif() +# SDL Re-Implementation Using GLFW +set(CORE_SRC + src/base.cpp + src/media.cpp + src/audio/api.cpp + src/audio/engine.c + src/audio/file.cpp + $ +) # Build add_library(media-layer-core-real SHARED ${CORE_SRC}) # Dependencies Are Setup Later @@ -23,10 +23,14 @@ endif() install(TARGETS media-layer-core-real DESTINATION "${MCPI_LIB_DIR}") # Link -target_link_libraries(media-layer-core-real PUBLIC media-layer-headers PUBLIC reborn-util PUBLIC dl) -if(NOT MCPI_HEADLESS_MODE) - # OpenAL - find_library(OPENAL_LIBRARY NAMES openal REQUIRED) - # Link - target_link_libraries(media-layer-core-real PRIVATE "${OPENAL_LIBRARY}" PRIVATE m PRIVATE glfw PUBLIC GLESv1_CM PRIVATE LIB_LIEF) -endif() +find_library(OPENAL_LIBRARY NAMES openal REQUIRED) +target_link_libraries(media-layer-core-real + PUBLIC media-layer-headers + PUBLIC reborn-util + PRIVATE "${OPENAL_LIBRARY}" + PRIVATE m + PRIVATE glfw + PUBLIC GLESv1_CM + PRIVATE LIB_LIEF + PUBLIC dl +) diff --git a/media-layer/core/src/audio/stubs.c b/media-layer/core/src/audio/stubs.c deleted file mode 100644 index 2db851b..0000000 --- a/media-layer/core/src/audio/stubs.c +++ /dev/null @@ -1,6 +0,0 @@ -#include - -void media_audio_update(__attribute__((unused)) float volume, __attribute__((unused)) float x, __attribute__((unused)) float y, __attribute__((unused)) float z, __attribute__((unused)) float yaw) { -} -void media_audio_play(__attribute__((unused)) const char *source, __attribute__((unused)) const char *name, __attribute__((unused)) float x, __attribute__((unused)) float y, __attribute__((unused)) float z, __attribute__((unused)) float pitch, __attribute__((unused)) float volume, __attribute__((unused)) int is_ui) { -} diff --git a/media-layer/core/src/media.c b/media-layer/core/src/media.c deleted file mode 100644 index 6adb376..0000000 --- a/media-layer/core/src/media.c +++ /dev/null @@ -1,861 +0,0 @@ -#include - -#include -#include - -#ifndef MCPI_HEADLESS_MODE -#include - -#define GLFW_INCLUDE_NONE -#include -#endif - -#include -#include - -#ifndef MCPI_HEADLESS_MODE -#include "audio/engine.h" -#endif - -// Allow Disabling Interaction -static void update_cursor(); -#ifndef MCPI_HEADLESS_MODE -static void emit_events_after_is_interactable_change(); -#endif -static int is_interactable = 1; -void media_set_interactable(int toggle) { - if (toggle != is_interactable) { - is_interactable = toggle; - update_cursor(); -#ifndef MCPI_HEADLESS_MODE - emit_events_after_is_interactable_change(); -#endif - } -} - -// Track Media Layer State -static volatile int is_running = 0; - -// Store Cursor State -static int cursor_grabbed = 0; -static int cursor_visible = 1; - -// Track If Raw Mouse Motion Is Enabled -static int raw_mouse_motion_enabled = 1; - -// GLFW Code Not Needed In Headless Mode -#ifndef MCPI_HEADLESS_MODE - -static GLFWwindow *glfw_window = NULL; - -// Handle GLFW Error -static void glfw_error(__attribute__((unused)) int error, const char *description) { - WARN("GLFW Error: %s", description); -} - -// Pass Character Event -static void character_event(char c) { - // SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events - SDL_Event event; - event.type = SDL_USEREVENT; - event.user.code = USER_EVENT_CHARACTER; - event.user.data1 = (int) c; - SDL_PushEvent(&event); -} - -// Convert GLFW Key To SDL Key -#define IMAGINARY_GLFW_CRAFTING_KEY GLFW_KEY_LAST -static SDLKey glfw_key_to_sdl_key(int key) { - switch (key) { - // Movement - case GLFW_KEY_W: - return SDLK_w; - case GLFW_KEY_A: - return SDLK_a; - case GLFW_KEY_S: - return SDLK_s; - case GLFW_KEY_D: - return SDLK_d; - case GLFW_KEY_SPACE: - return SDLK_SPACE; - case GLFW_KEY_LEFT_SHIFT: - return SDLK_LSHIFT; - case GLFW_KEY_RIGHT_SHIFT: - return SDLK_RSHIFT; - // Inventory - case GLFW_KEY_E: - return SDLK_e; - // Drop Item - case GLFW_KEY_Q: - return SDLK_q; - // Toolbar - case GLFW_KEY_1: - return SDLK_1; - case GLFW_KEY_2: - return SDLK_2; - case GLFW_KEY_3: - return SDLK_3; - case GLFW_KEY_4: - return SDLK_4; - case GLFW_KEY_5: - return SDLK_5; - case GLFW_KEY_6: - return SDLK_6; - case GLFW_KEY_7: - return SDLK_7; - case GLFW_KEY_8: - return SDLK_8; - case GLFW_KEY_9: - return SDLK_9; - case GLFW_KEY_0: - return SDLK_0; - // UI Control - case GLFW_KEY_ESCAPE: - return SDLK_ESCAPE; - case GLFW_KEY_UP: - return SDLK_UP; - case GLFW_KEY_DOWN: - return SDLK_DOWN; - case GLFW_KEY_LEFT: - return SDLK_LEFT; - case GLFW_KEY_RIGHT: - return SDLK_RIGHT; - case GLFW_KEY_TAB: - return SDLK_TAB; - case GLFW_KEY_ENTER: - return SDLK_RETURN; - case GLFW_KEY_BACKSPACE: - return SDLK_BACKSPACE; - case GLFW_KEY_DELETE: - return SDLK_DELETE; - // Fullscreen - case GLFW_KEY_F11: - return SDLK_F11; - // Screenshot - case GLFW_KEY_F2: - return SDLK_F2; - // Hide GUI - case GLFW_KEY_F1: - return SDLK_F1; - // Third Person - case GLFW_KEY_F5: - return SDLK_F5; - // Chat - case GLFW_KEY_T: - return SDLK_t; - // Crafting - case IMAGINARY_GLFW_CRAFTING_KEY: - return SDLK_WORLD_0; - // Unknown - default: - return SDLK_UNKNOWN; - } -} - -// Convert GLFW Key Modifier To SDL Key Modifier -static SDLMod glfw_modifier_to_sdl_modifier(int mods) { - SDLMod ret = KMOD_NONE; - // Control - if ((mods & GLFW_MOD_CONTROL) != 0) { - ret |= KMOD_CTRL; - } - // Shift - if ((mods & GLFW_MOD_SHIFT) != 0) { - ret |= KMOD_SHIFT; - } - // Alt - if ((mods & GLFW_MOD_ALT) != 0) { - ret |= KMOD_ALT; - } - // Return - return ret; -} - -// Pass Key Presses To SDL -static void glfw_key_raw(int key, int scancode, int action, int mods) { - SDL_Event event1; - int up = action == GLFW_RELEASE; - event1.type = up ? SDL_KEYUP : SDL_KEYDOWN; - event1.key.state = up ? SDL_RELEASED : SDL_PRESSED; - event1.key.keysym.scancode = scancode; - event1.key.keysym.mod = glfw_modifier_to_sdl_modifier(mods); - event1.key.keysym.sym = glfw_key_to_sdl_key(key); - SDL_PushEvent(&event1); - // Allow MCPI To Access Original GLFW Keycode - SDL_Event event2; - event2.type = SDL_USEREVENT; - event2.user.code = USER_EVENT_REAL_KEY; - event2.user.data1 = event1.key.state; - event2.user.data2 = key; - SDL_PushEvent(&event2); -} -static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, int mods) { - if (is_interactable) { - glfw_key_raw(key, scancode, action, mods); - } -} - -// Pass Text To Minecraft -static void codepoint_to_utf8(unsigned char *const buffer, const unsigned int code) { - // https://stackoverflow.com/a/42013433/16198887 - if (code <= 0x7f) { - buffer[0] = code; - } else if (code <= 0x7ff) { - buffer[0] = 0xc0 | (code >> 6); // 110xxxxx - buffer[1] = 0x80 | (code & 0x3f); // 10xxxxxx - } else if (code <= 0xffff) { - buffer[0] = 0xe0 | (code >> 12); // 1110xxxx - buffer[1] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx - buffer[2] = 0x80 | (code & 0x3f); // 10xxxxxx - } else if (code <= 0x10ffff) { - buffer[0] = 0xf0 | (code >> 18); // 11110xxx - buffer[1] = 0x80 | ((code >> 12) & 0x3f); // 10xxxxxx - buffer[2] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx - buffer[3] = 0x80 | (code & 0x3f); // 10xxxxxx - } -} -static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int codepoint) { - if (is_interactable) { - // Convert - size_t str_size = 4 /* Maximum UTF-8 character size */ + 1 /* NULL-terminator */; - char str[str_size]; - memset(str, 0, str_size); - codepoint_to_utf8((unsigned char *) str, codepoint); - char *cp437_str = to_cp437(str); - // Send Event - for (int i = 0; cp437_str[i] != '\0'; i++) { - character_event(cp437_str[i]); - } - // Free - free(cp437_str); - } -} - -// Last Mouse Location -static double last_mouse_x = 0; -static double last_mouse_y = 0; -// Ignore Relative Cursor Motion -static int ignore_relative_motion = 0; - -// Convert Screen Coordinates To Pixels -static void convert_to_pixels(GLFWwindow *window, double *xpos, double *ypos) { - // Skip If Cursor Is Grabbed - if (cursor_grabbed && raw_mouse_motion_enabled) { - return; - } - // Get Window Size - int window_width; - int window_height; - glfwGetWindowSize(window, &window_width, &window_height); - // Get Framebuffer Size - int framebuffer_width; - int framebuffer_height; - glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); - // Calculate Ratios - double width_ratio = ((double) framebuffer_width) / ((double) window_width); - double height_ratio = ((double) framebuffer_height) / ((double) window_height); - // Multiply - *xpos *= width_ratio; - *ypos *= height_ratio; -} - -// Pass Mouse Movement To SDL -static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) { - convert_to_pixels(window, &xpos, &ypos); - if (is_interactable) { - SDL_Event event; - event.type = SDL_MOUSEMOTION; - event.motion.x = xpos; - event.motion.y = ypos; - event.motion.xrel = !ignore_relative_motion ? (xpos - last_mouse_x) : 0; - event.motion.yrel = !ignore_relative_motion ? (ypos - last_mouse_y) : 0; - SDL_PushEvent(&event); - } - ignore_relative_motion = 0; - last_mouse_x = xpos; - last_mouse_y = ypos; -} - -// Create And Push SDL Mouse Click Event -static void click_event(int button, int up) { - SDL_Event event; - event.type = up ? SDL_MOUSEBUTTONUP : SDL_MOUSEBUTTONDOWN; - event.button.x = last_mouse_x; - event.button.y = last_mouse_y; - event.button.state = up ? SDL_RELEASED : SDL_PRESSED; - event.button.button = button; - SDL_PushEvent(&event); -} - -// Pass Mouse Click To SDL -static void glfw_click_raw(int button, int action) { - int up = action == GLFW_RELEASE; - int sdl_button = button == GLFW_MOUSE_BUTTON_RIGHT ? SDL_BUTTON_RIGHT : (button == GLFW_MOUSE_BUTTON_LEFT ? SDL_BUTTON_LEFT : SDL_BUTTON_MIDDLE); - click_event(sdl_button, up); -} -static void glfw_click(__attribute__((unused)) GLFWwindow *window, int button, int action, __attribute__((unused)) int mods) { - if (is_interactable) { - glfw_click_raw(button, action); - } -} - -// Pass Mouse Scroll To SDL -static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) { - if (is_interactable && yoffset != 0) { - int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; - click_event(sdl_button, 0); - click_event(sdl_button, 1); - } -} - -// Controller Events -static SDLKey glfw_controller_button_to_key(int button) { - switch (button) { - // Jump - case GLFW_GAMEPAD_BUTTON_A: - return GLFW_KEY_SPACE; - // Drop Item - case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: - return GLFW_KEY_Q; - // Inventory - case GLFW_GAMEPAD_BUTTON_Y: - return GLFW_KEY_E; - // Third-Person - case GLFW_GAMEPAD_BUTTON_DPAD_UP: - return GLFW_KEY_F5; - // Sneak - case GLFW_GAMEPAD_BUTTON_B: - return GLFW_KEY_LEFT_SHIFT; - // Chat - case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: - return GLFW_KEY_T; - // Pause - case GLFW_GAMEPAD_BUTTON_START: - case GLFW_GAMEPAD_BUTTON_BACK: - return GLFW_KEY_ESCAPE; - // Crafting - case GLFW_GAMEPAD_BUTTON_X: - return IMAGINARY_GLFW_CRAFTING_KEY; - // Unknown - default: - return GLFW_KEY_UNKNOWN; - } -} -static void glfw_controller_button(int button, int action) { - int key = glfw_controller_button_to_key(button); - if (key != GLFW_KEY_UNKNOWN) { - // Press Key - glfw_key_raw(key, glfwGetKeyScancode(key), action, 0); - } else { - // Scrolling - if (button == GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) { - key = SDL_BUTTON_WHEELUP; - } else if (button == GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) { - key = SDL_BUTTON_WHEELDOWN; - } - if (key != GLFW_KEY_UNKNOWN) { - click_event(key, action == GLFW_PRESS); - } - } -} - -// Controller Movement Axis -static int controller_horizontal_key = GLFW_KEY_UNKNOWN; -static int controller_vertical_key = GLFW_KEY_UNKNOWN; -static void release_and_press_key(int *old_key, int new_key) { - if (*old_key != new_key) { - if (*old_key != GLFW_KEY_UNKNOWN) { - glfw_key_raw(*old_key, glfwGetKeyScancode(*old_key), GLFW_RELEASE, 0); - } - if (new_key != GLFW_KEY_UNKNOWN) { - glfw_key_raw(new_key, glfwGetKeyScancode(new_key), GLFW_PRESS, 0); - } - } - *old_key = new_key; -} -#define verify_controller_axis_value(value, threshold) \ - if ((value < (threshold) && value > 0) || (value > -(threshold) && value < 0)) { \ - value = 0; \ - } -#define CONTROLLER_MOVEMENT_AXIS_THRESHOLD 0.5f -static void glfw_controller_movement(float x, float y) { - // Verify - verify_controller_axis_value(x, CONTROLLER_MOVEMENT_AXIS_THRESHOLD); - verify_controller_axis_value(y, CONTROLLER_MOVEMENT_AXIS_THRESHOLD); - // Horizontal Movement - if (x > 0) { - release_and_press_key(&controller_horizontal_key, GLFW_KEY_D); - } else if (x < 0) { - release_and_press_key(&controller_horizontal_key, GLFW_KEY_A); - } else { - release_and_press_key(&controller_horizontal_key, GLFW_KEY_UNKNOWN); - } - // Vertical Movement - if (y < 0) { - release_and_press_key(&controller_vertical_key, GLFW_KEY_W); - } else if (y > 0) { - release_and_press_key(&controller_vertical_key, GLFW_KEY_S); - } else { - release_and_press_key(&controller_vertical_key, GLFW_KEY_UNKNOWN); - } -} - -// Get Time -#define NANOSECONDS_IN_SECOND 1000000000ll -static long long int get_time() { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC_RAW, &ts); - long long int a = (long long int) ts.tv_nsec; - long long int b = ((long long int) ts.tv_sec) * NANOSECONDS_IN_SECOND; - return a + b; -} - -// Controller Look Axis -#define CONTROLLER_LOOK_EVENT_PERIOD 50000000ll // 1/20 Seconds -#define CONTROLLER_LOOK_AXIS_THRESHOLD 0.2f -#define CONTROLLER_LOOK_AXIS_SENSITIVITY 70 -static void glfw_controller_look(float x, float y) { - // Current Time - long long int current_time = get_time(); - // Last Time - static long long int last_time = 0; - static int is_last_time_set = 0; - if (!is_last_time_set) { - is_last_time_set = 1; - last_time = current_time; - } - - // Check If Period Has Passed - if ((current_time - last_time) > CONTROLLER_LOOK_EVENT_PERIOD) { - // Reset Last Time - last_time = current_time; - - // Verify - verify_controller_axis_value(x, CONTROLLER_LOOK_AXIS_THRESHOLD); - verify_controller_axis_value(y, CONTROLLER_LOOK_AXIS_THRESHOLD); - - // Send Event - if (is_interactable) { - SDL_Event event; - event.type = SDL_MOUSEMOTION; - event.motion.x = last_mouse_x; - event.motion.y = last_mouse_y; - event.motion.xrel = x * CONTROLLER_LOOK_AXIS_SENSITIVITY; - event.motion.yrel = y * CONTROLLER_LOOK_AXIS_SENSITIVITY; - SDL_PushEvent(&event); - } - } -} - -// Controller Place/Mine Triggers -#define CONTROLLER_TRIGGER_THRESHOLD 0 -#define CONTROLLER_TRIGGER_COUNT 2 -static void glfw_controller_trigger(int trigger, int action) { - glfw_click_raw(trigger == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER ? GLFW_MOUSE_BUTTON_LEFT : GLFW_MOUSE_BUTTON_RIGHT, action); -} - -// Current Controller -static int current_controller = -1; - -// Track Controller State -static void update_controller_state() { - // Store Button/Trigger State - static int controller_buttons[GLFW_GAMEPAD_BUTTON_LAST + 1]; - static int controller_triggers[CONTROLLER_TRIGGER_COUNT]; - - // Get State - GLFWgamepadstate state; - int controller_enabled = cursor_grabbed && is_interactable; - int controller_valid = controller_enabled && current_controller != -1 && glfwGetGamepadState(current_controller, &state); - if (!controller_valid) { - // Invalid Controller - - // Generate Blank State - for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) { - state.buttons[i] = GLFW_RELEASE; - } - for (int i = GLFW_GAMEPAD_AXIS_LEFT_X; i <= GLFW_GAMEPAD_AXIS_LAST; i++) { - int is_trigger = i == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || i == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER; - state.axes[i] = is_trigger ? -1 : 0; - } - } - - // Check Buttons - for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) { - int old_state = controller_buttons[i]; - controller_buttons[i] = state.buttons[i]; - if (old_state != controller_buttons[i]) { - // State Changed - glfw_controller_button(i, controller_buttons[i]); - } - } - - // Handle Movement & Look - glfw_controller_movement(state.axes[GLFW_GAMEPAD_AXIS_LEFT_X], state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]); - glfw_controller_look(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X], state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]); - - // Check Triggers - for (int i = 0; i < CONTROLLER_TRIGGER_COUNT; i++) { - int old_state = controller_triggers[i]; - int trigger_id = i == 0 ? GLFW_GAMEPAD_AXIS_LEFT_TRIGGER : GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER; - controller_triggers[i] = state.axes[trigger_id] < CONTROLLER_TRIGGER_THRESHOLD ? GLFW_RELEASE : GLFW_PRESS; - if (old_state != controller_triggers[i]) { - // State Changed - glfw_controller_trigger(trigger_id, controller_triggers[i]); - } - } -} - -// Pick Controller -static int joysticks[GLFW_JOYSTICK_LAST + 1]; -static void pick_new_controller() { - current_controller = -1; - for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) { - if (joysticks[i] == 1) { - current_controller = i; - DEBUG("Using Controller: %s (%s)", glfwGetGamepadName(i), glfwGetJoystickName(i)); - break; - } - } -} -static void find_controllers() { - for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) { - joysticks[i] = glfwJoystickIsGamepad(i); - } - pick_new_controller(); -} -static void glfw_joystick(int jid, int event) { - if (event == GLFW_CONNECTED && glfwJoystickIsGamepad(jid)) { - joysticks[jid] = 1; - pick_new_controller(); - } else if (event == GLFW_DISCONNECTED) { - joysticks[jid] = 0; - if (jid == current_controller) { - DEBUG("Controller Disconnected"); - pick_new_controller(); - } - } -} - -// Release all keys/buttons when interaction is disabled and vice versa. -static void emit_events_after_is_interactable_change() { - if (is_running) { - for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; i++) { - int state = glfwGetKey(glfw_window, i); - if (state == GLFW_PRESS) { - glfw_key_raw(i, glfwGetKeyScancode(i), is_interactable ? GLFW_PRESS : GLFW_RELEASE, 0); - } - } - for (int i = GLFW_MOUSE_BUTTON_1; i <= GLFW_MOUSE_BUTTON_LAST; i++) { - int state = glfwGetMouseButton(glfw_window, i); - if (state == GLFW_PRESS) { - glfw_click_raw(i, is_interactable ? GLFW_PRESS : GLFW_RELEASE); - } - } - } -} - -#endif - -// Enable/Disable Raw Mouse Motion -void media_set_raw_mouse_motion_enabled(int enabled) { - raw_mouse_motion_enabled = enabled; -#ifndef MCPI_HEADLESS_MODE - if (is_running) { - glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE); - } -#endif - if (!raw_mouse_motion_enabled) { - WARN("Raw mouse motion has been DISABLED, this IS NOT recommended, and should only ever be used on systems that don't support or have broken raw mouse motion."); - } -} - -// Disable V-Sync -static int disable_vsync = 0; -void media_disable_vsync() { - disable_vsync = 1; -#ifndef MCPI_HEADLESS_MODE - if (is_running) { - glfwSwapInterval(0); - } -#endif -} - -// Force EGL -static int force_egl = 0; -void media_force_egl() { - if (force_egl == -1) { - IMPOSSIBLE(); - } - force_egl = 1; -} - -// Init Media Layer -#define GL_VERSION 0x1f02 -typedef const unsigned char *(*glGetString_t)(unsigned int name); -void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *icon) { - // Don't Enable GLFW In Headless Mode -#ifndef MCPI_HEADLESS_MODE - // Init GLFW - glfwSetErrorCallback(glfw_error); - - if (!glfwInit()) { - ERR("Unable To Initialize GLFW"); - } - - // Create OpenGL ES Context - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); -#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); -#else - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); -#endif - // Use EGL - if (force_egl) { - glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); - } - force_egl = -1; - // Extra Settings - glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE); - glfwWindowHint(GLFW_ALPHA_BITS, 0); // Fix Transparent Window On Wayland - // App ID - glfwWindowHintString(GLFW_X11_CLASS_NAME, MCPI_APP_ID); - glfwWindowHintString(GLFW_WAYLAND_APP_ID, MCPI_APP_ID); - - // Create Window - glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL); - if (!glfw_window) { - ERR("Unable To Create GLFW Window"); - } - - // Event Handlers - glfwSetKeyCallback(glfw_window, glfw_key); - glfwSetCharCallback(glfw_window, glfw_char); - glfwSetCursorPosCallback(glfw_window, glfw_motion); - glfwSetMouseButtonCallback(glfw_window, glfw_click); - glfwSetScrollCallback(glfw_window, glfw_scroll); - - // Setup Controller Support - find_controllers(); - glfwSetJoystickCallback(glfw_joystick); - - // Make Window Context Current - glfwMakeContextCurrent(glfw_window); - - // Setup Compatibility Layer -#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER - extern void init_gles_compatibility_layer(void *); - init_gles_compatibility_layer(glfwGetProcAddress); -#endif - - // Debug - glGetString_t glGetString = (glGetString_t) glfwGetProcAddress("glGetString"); - DEBUG("Using %s", (*glGetString)(GL_VERSION)); - - // Init OpenAL - _media_audio_init(); -#else - (void) title; // Mark As Used -#endif - - // Set State - is_running = 1; - - // Update State - update_cursor(); - if (disable_vsync) { - media_disable_vsync(); - } - - // Always Cleanup Media Layer - atexit(media_cleanup); -} - -void media_swap_buffers() { -#ifndef MCPI_HEADLESS_MODE - // Don't Swap Buffers In A Context-Less Window - glfwSwapBuffers(glfw_window); -#endif -} - -// Fullscreen Not Needed In Headless Mode -#ifndef MCPI_HEADLESS_MODE -static int is_fullscreen = 0; - -// Old Size And Position To Use When Exiting Fullscreen -static int old_width = -1; -static int old_height = -1; -static int old_x = -1; -static int old_y = -1; - -// Toggle Fullscreen -void media_toggle_fullscreen() { - if (is_fullscreen) { - glfwSetWindowMonitor(glfw_window, NULL, old_x, old_y, old_width, old_height, GLFW_DONT_CARE); - - old_width = -1; - old_height = -1; - old_x = -1; - old_y = -1; - } else { - glfwGetWindowSize(glfw_window, &old_width, &old_height); - glfwGetWindowPos(glfw_window, &old_x, &old_y); - - GLFWmonitor *monitor = glfwGetPrimaryMonitor(); - const GLFWvidmode *mode = glfwGetVideoMode(monitor); - - glfwSetWindowMonitor(glfw_window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE); - } - is_fullscreen = !is_fullscreen; -} -#else -void media_toggle_fullscreen() { -} -#endif - -// Intercept SDL Events -void _media_handle_SDL_PollEvent() { - // GLFW And Audio Are Disabled Disabled In Headless Mode -#ifndef MCPI_HEADLESS_MODE - // Process GLFW Events - glfwPollEvents(); - - // Controller - update_controller_state(); - - // Close Window - if (glfwWindowShouldClose(glfw_window)) { - SDL_Event event; - event.type = SDL_QUIT; - SDL_PushEvent(&event); - glfwSetWindowShouldClose(glfw_window, GLFW_FALSE); - } -#endif -} - -// Cleanup Media Layer -void media_cleanup() { - if (is_running) { - // GLFW And Audio Are Disabled In Headless Mode -#ifndef MCPI_HEADLESS_MODE - // Ignore GLFW Errors During Termination - glfwSetErrorCallback(NULL); - - // Terminate GLFW - glfwDestroyWindow(glfw_window); - glfwTerminate(); - - // Cleanup OpenAL - _media_audio_cleanup(); -#endif - - // Update State - is_running = 0; - } -} - -// Update GLFW Cursor State (Client Only) -static void update_cursor() { -#ifndef MCPI_HEADLESS_MODE - if (is_running) { - // Get New State - int new_cursor_visible = is_interactable ? cursor_visible : 1; - int new_cursor_grabbed = is_interactable ? cursor_grabbed : 0; - - // Store Old Mode - int old_mode = glfwGetInputMode(glfw_window, GLFW_CURSOR); - - // Handle Cursor Visibility - int new_mode; - if (!new_cursor_visible) { - if (new_cursor_grabbed) { - new_mode = GLFW_CURSOR_DISABLED; - } else { - new_mode = GLFW_CURSOR_HIDDEN; - } - } else { - new_mode = GLFW_CURSOR_NORMAL; - } - if (new_mode != old_mode) { - // Ignore Relative Cursor Motion When Locking - if (new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) { - ignore_relative_motion = 1; - } - - // Set New Mode - glfwSetInputMode(glfw_window, GLFW_CURSOR, new_mode); - - // Handle Cursor Lock/Unlock - if ((new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) || (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED)) { - // Use Raw Mouse Motion - if (raw_mouse_motion_enabled) { - glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, new_mode == GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE); - } - - // Request Focus - if (!glfwGetWindowAttrib(glfw_window, GLFW_FOCUSED)) { - glfwRequestWindowAttention(glfw_window); - } - } - - // Reset Mouse Position When Unlocking - if (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED) { - double cursor_x; - double cursor_y; - glfwGetCursorPos(glfw_window, &cursor_x, &cursor_y); - glfw_motion(glfw_window, cursor_x, cursor_y); - } - } - } -#endif -} - -// Fix SDL Cursor Visibility/Grabbing -SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode) { - if (mode == SDL_GRAB_QUERY) { - // Query - return cursor_grabbed ? SDL_GRAB_ON : SDL_GRAB_OFF; - } else if (mode == SDL_GRAB_ON) { - // Store State - cursor_grabbed = 1; - } else if (mode == SDL_GRAB_OFF) { - // Store State - cursor_grabbed = 0; - } - // Update Cursor GLFW State (Client Only) - update_cursor(); - // Return - return mode; -} - -// Stub SDL Cursor Visibility -int SDL_ShowCursor(int toggle) { - if (toggle == SDL_QUERY) { - // Query - return cursor_visible ? SDL_ENABLE : SDL_DISABLE; - } else if (toggle == SDL_ENABLE) { - // Store State - cursor_visible = 1; - } else if (toggle == SDL_DISABLE) { - // Store State - cursor_visible = 0; - } - // Update Cursor GLFW State (Client Only) - update_cursor(); - // Return - return toggle; -} - -// Get Framebuffer Size -void media_get_framebuffer_size(int *width, int *height) { -#ifndef MCPI_HEADLESS_MODE - if (glfw_window != NULL) { - glfwGetFramebufferSize(glfw_window, width, height); - return; - } -#endif - *width = DEFAULT_WIDTH; - *height = DEFAULT_HEIGHT; -} diff --git a/media-layer/core/src/media.cpp b/media-layer/core/src/media.cpp new file mode 100644 index 0000000..8ac8e8b --- /dev/null +++ b/media-layer/core/src/media.cpp @@ -0,0 +1,561 @@ +#include +#include + +#include +#include + +#define GLFW_INCLUDE_NONE +#include + +#include +#include + +#include "audio/engine.h" + +// Allow Disabling Interaction +static void update_cursor(); +static int is_interactable = 1; +void media_set_interactable(int toggle) { + if (bool(toggle) != is_interactable) { + is_interactable = toggle; + update_cursor(); + } +} + +// Store Cursor State +static bool cursor_grabbed = false; +static bool cursor_visible = true; + +// Track If Raw Mouse Motion Is Enabled +static bool raw_mouse_motion_enabled = true; + +// Window +static GLFWwindow *glfw_window = nullptr; + +// Handle GLFW Error +static void glfw_error(__attribute__((unused)) int error, const char *description) { + WARN("GLFW Error: %s", description); +} + +// Convert GLFW Key To SDL Key +static SDLKey glfw_key_to_sdl_key(const int key) { + switch (key) { + // Movement + case GLFW_KEY_W: + return SDLK_w; + case GLFW_KEY_A: + return SDLK_a; + case GLFW_KEY_S: + return SDLK_s; + case GLFW_KEY_D: + return SDLK_d; + case GLFW_KEY_SPACE: + return SDLK_SPACE; + case GLFW_KEY_LEFT_SHIFT: + return SDLK_LSHIFT; + case GLFW_KEY_RIGHT_SHIFT: + return SDLK_RSHIFT; + // Inventory + case GLFW_KEY_E: + return SDLK_e; + // Drop Item + case GLFW_KEY_Q: + return SDLK_q; + // Toolbar + case GLFW_KEY_1: + return SDLK_1; + case GLFW_KEY_2: + return SDLK_2; + case GLFW_KEY_3: + return SDLK_3; + case GLFW_KEY_4: + return SDLK_4; + case GLFW_KEY_5: + return SDLK_5; + case GLFW_KEY_6: + return SDLK_6; + case GLFW_KEY_7: + return SDLK_7; + case GLFW_KEY_8: + return SDLK_8; + case GLFW_KEY_9: + return SDLK_9; + case GLFW_KEY_0: + return SDLK_0; + // UI Control + case GLFW_KEY_ESCAPE: + return SDLK_ESCAPE; + case GLFW_KEY_UP: + return SDLK_UP; + case GLFW_KEY_DOWN: + return SDLK_DOWN; + case GLFW_KEY_LEFT: + return SDLK_LEFT; + case GLFW_KEY_RIGHT: + return SDLK_RIGHT; + case GLFW_KEY_TAB: + return SDLK_TAB; + case GLFW_KEY_ENTER: + return SDLK_RETURN; + case GLFW_KEY_BACKSPACE: + return SDLK_BACKSPACE; + case GLFW_KEY_DELETE: + return SDLK_DELETE; + // Fullscreen + case GLFW_KEY_F11: + return SDLK_F11; + // Screenshot + case GLFW_KEY_F2: + return SDLK_F2; + // Hide GUI + case GLFW_KEY_F1: + return SDLK_F1; + // Third Person + case GLFW_KEY_F5: + return SDLK_F5; + // Chat + case GLFW_KEY_T: + return SDLK_t; + // Unknown + default: + return SDLK_UNKNOWN; + } +} + +// Convert GLFW Key Modifier To SDL Key Modifier +static SDLMod glfw_modifier_to_sdl_modifier(const int mods) { + int ret = KMOD_NONE; + // Control + if ((mods & GLFW_MOD_CONTROL) != 0) { + ret |= KMOD_CTRL; + } + // Shift + if ((mods & GLFW_MOD_SHIFT) != 0) { + ret |= KMOD_SHIFT; + } + // Alt + if ((mods & GLFW_MOD_ALT) != 0) { + ret |= KMOD_ALT; + } + // Return + return SDLMod(ret); +} + +// Pass Key Presses To SDL +static void glfw_key_raw(int key, int scancode, int action, int mods) { + SDL_Event event1; + bool up = action == GLFW_RELEASE; + event1.type = up ? SDL_KEYUP : SDL_KEYDOWN; + event1.key.state = up ? SDL_RELEASED : SDL_PRESSED; + event1.key.keysym.scancode = scancode; + event1.key.keysym.mod = glfw_modifier_to_sdl_modifier(mods); + event1.key.keysym.sym = glfw_key_to_sdl_key(key); + SDL_PushEvent(&event1); + // Allow MCPI To Access Original GLFW Keycode + SDL_Event event2; + event2.type = SDL_USEREVENT; + event2.user.code = USER_EVENT_REAL_KEY; + event2.user.data1 = event1.key.state; + event2.user.data2 = key; + SDL_PushEvent(&event2); +} +static void glfw_key(__attribute__((unused)) GLFWwindow *window, const int key, const int scancode, const int action, const int mods) { + if (is_interactable) { + glfw_key_raw(key, scancode, action, mods); + } +} + +// Pass Text To Minecraft +static void character_event(char c) { + if (!is_interactable) { + return; + } + // SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events + SDL_Event event; + event.type = SDL_USEREVENT; + event.user.code = USER_EVENT_CHARACTER; + event.user.data1 = (int) c; + SDL_PushEvent(&event); +} +static void codepoint_to_utf8(unsigned char *const buffer, const unsigned int code) { + // https://stackoverflow.com/a/42013433/16198887 + if (code <= 0x7f) { + buffer[0] = code; + } else if (code <= 0x7ff) { + buffer[0] = 0xc0 | (code >> 6); // 110xxxxx + buffer[1] = 0x80 | (code & 0x3f); // 10xxxxxx + } else if (code <= 0xffff) { + buffer[0] = 0xe0 | (code >> 12); // 1110xxxx + buffer[1] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx + buffer[2] = 0x80 | (code & 0x3f); // 10xxxxxx + } else if (code <= 0x10ffff) { + buffer[0] = 0xf0 | (code >> 18); // 11110xxx + buffer[1] = 0x80 | ((code >> 12) & 0x3f); // 10xxxxxx + buffer[2] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx + buffer[3] = 0x80 | (code & 0x3f); // 10xxxxxx + } +} +static void glfw_char(__attribute__((unused)) GLFWwindow *window, const unsigned int codepoint) { + // Convert + size_t str_size = 4 /* Maximum UTF-8 character size */ + 1 /* NULL-terminator */; + char str[str_size] = {}; + codepoint_to_utf8((unsigned char *) str, codepoint); + char *cp437_str = to_cp437(str); + // Send Event + for (int i = 0; cp437_str[i] != '\0'; i++) { + character_event(cp437_str[i]); + } + // Free + free(cp437_str); +} + +// Last Mouse Location +static double last_mouse_x = 0; +static double last_mouse_y = 0; +// Ignore Relative Cursor Motion +static bool ignore_relative_motion = false; + +// Convert Screen Coordinates To Pixels +static void convert_to_pixels(GLFWwindow *window, double *xpos, double *ypos) { + // Skip If Cursor Is Grabbed + if (cursor_grabbed && raw_mouse_motion_enabled) { + return; + } + // Get Window Size + int window_width; + int window_height; + glfwGetWindowSize(window, &window_width, &window_height); + // Get Framebuffer Size + int framebuffer_width; + int framebuffer_height; + glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); + // Calculate Ratios + const double width_ratio = ((double) framebuffer_width) / ((double) window_width); + const double height_ratio = ((double) framebuffer_height) / ((double) window_height); + // Multiply + *xpos *= width_ratio; + *ypos *= height_ratio; +} + +// Pass Mouse Movement To SDL +static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) { + convert_to_pixels(window, &xpos, &ypos); + if (is_interactable) { + SDL_Event event; + event.type = SDL_MOUSEMOTION; + event.motion.x = xpos; + event.motion.y = ypos; + event.motion.xrel = !ignore_relative_motion ? (xpos - last_mouse_x) : 0; + event.motion.yrel = !ignore_relative_motion ? (ypos - last_mouse_y) : 0; + SDL_PushEvent(&event); + } + ignore_relative_motion = false; + last_mouse_x = xpos; + last_mouse_y = ypos; +} + +// Create And Push SDL Mouse Click Event +static void click_event(int button, bool up) { + SDL_Event event; + event.type = up ? SDL_MOUSEBUTTONUP : SDL_MOUSEBUTTONDOWN; + event.button.x = last_mouse_x; + event.button.y = last_mouse_y; + event.button.state = up ? SDL_RELEASED : SDL_PRESSED; + event.button.button = button; + SDL_PushEvent(&event); +} + +// Pass Mouse Click To SDL +static void glfw_click_raw(const int button, const int action) { + const bool up = action == GLFW_RELEASE; + const int sdl_button = button == GLFW_MOUSE_BUTTON_RIGHT ? SDL_BUTTON_RIGHT : (button == GLFW_MOUSE_BUTTON_LEFT ? SDL_BUTTON_LEFT : SDL_BUTTON_MIDDLE); + click_event(sdl_button, up); +} +static void glfw_click(__attribute__((unused)) GLFWwindow *window, const int button, const int action, __attribute__((unused)) int mods) { + if (is_interactable) { + glfw_click_raw(button, action); + } +} + +// Pass Mouse Scroll To SDL +static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) { + if (is_interactable && yoffset != 0) { + int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; + click_event(sdl_button, false); + click_event(sdl_button, true); + } +} + +// Enable/Disable Raw Mouse Motion +void media_set_raw_mouse_motion_enabled(const int enabled) { + raw_mouse_motion_enabled = enabled; + if (glfw_window) { + glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE); + } + if (!raw_mouse_motion_enabled) { + WARN("Raw mouse motion has been DISABLED, this IS NOT recommended, and should only ever be used on systems that don't support or have broken raw mouse motion."); + } +} + +// Disable V-Sync +static int disable_vsync = 0; +void media_disable_vsync() { + disable_vsync = 1; + if (glfw_window) { + glfwSwapInterval(0); + } +} + +// Force EGL +static int force_egl = 0; +void media_force_egl() { + if (force_egl == -1) { + IMPOSSIBLE(); + } + force_egl = 1; +} + +// Init Media Layer +#define GL_VERSION 0x1f02 +typedef const char *(*glGetString_t)(unsigned int name); +#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER +extern "C" void init_gles_compatibility_layer(void *); +#endif +void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *icon) { + // Disable In Headless Mode + if (reborn_is_headless()) { + return; + } + + // Init GLFW + reborn_check_display(); + glfwSetErrorCallback(glfw_error); + if (!glfwInit()) { + ERR("Unable To Initialize GLFW"); + } + + // Create OpenGL ES Context + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); +#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); +#else + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); +#endif + // Use EGL + if (force_egl) { + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); + } + force_egl = -1; + // Extra Settings + glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE); + glfwWindowHint(GLFW_ALPHA_BITS, 0); // Fix Transparent Window On Wayland + // App ID + glfwWindowHintString(GLFW_X11_CLASS_NAME, MCPI_APP_ID); + glfwWindowHintString(GLFW_WAYLAND_APP_ID, MCPI_APP_ID); + + // Create Window + glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL); + if (!glfw_window) { + ERR("Unable To Create GLFW Window"); + } + + // Event Handlers + glfwSetKeyCallback(glfw_window, glfw_key); + glfwSetCharCallback(glfw_window, glfw_char); + glfwSetCursorPosCallback(glfw_window, glfw_motion); + glfwSetMouseButtonCallback(glfw_window, glfw_click); + glfwSetScrollCallback(glfw_window, glfw_scroll); + + // Make Window Context Current + glfwMakeContextCurrent(glfw_window); + + // Setup Compatibility Layer +#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER + init_gles_compatibility_layer((void *) glfwGetProcAddress); +#endif + + // Debug + glGetString_t glGetString = (glGetString_t) glfwGetProcAddress("glGetString"); + DEBUG("Using %s", (*glGetString)(GL_VERSION)); + + // Init OpenAL + _media_audio_init(); + + // Update State + update_cursor(); + if (disable_vsync) { + media_disable_vsync(); + } + + // Always Cleanup Media Layer + atexit(media_cleanup); +} + +void media_swap_buffers() { + if (glfw_window) { + glfwSwapBuffers(glfw_window); + } +} + +// Track Fullscreen +static bool is_fullscreen = false; +// Old Size And Position To Use When Exiting Fullscreen +static int old_width = -1; +static int old_height = -1; +static int old_x = -1; +static int old_y = -1; + +// Toggle Fullscreen +void media_toggle_fullscreen() { + if (glfw_window) { + if (is_fullscreen) { + glfwSetWindowMonitor(glfw_window, nullptr, old_x, old_y, old_width, old_height, GLFW_DONT_CARE); + + old_width = -1; + old_height = -1; + old_x = -1; + old_y = -1; + } else { + glfwGetWindowSize(glfw_window, &old_width, &old_height); + glfwGetWindowPos(glfw_window, &old_x, &old_y); + + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + + glfwSetWindowMonitor(glfw_window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE); + } + is_fullscreen = !is_fullscreen; + } +} + +// Intercept SDL Events +void _media_handle_SDL_PollEvent() { + if (glfw_window) { + // Process GLFW Events + glfwPollEvents(); + // Close Window + if (glfwWindowShouldClose(glfw_window)) { + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); + glfwSetWindowShouldClose(glfw_window, GLFW_FALSE); + } + } +} + +// Cleanup Media Layer +void media_cleanup() { + if (glfw_window) { + // Ignore GLFW Errors During Termination + glfwSetErrorCallback(nullptr); + + // Terminate GLFW + glfwDestroyWindow(glfw_window); + glfwTerminate(); + + // Cleanup OpenAL + _media_audio_cleanup(); + } +} + +// Update GLFW Cursor State (Client Only) +static void update_cursor() { + if (glfw_window) { + // Get New State + const bool new_cursor_visible = is_interactable ? cursor_visible : true; + const bool new_cursor_grabbed = is_interactable ? cursor_grabbed : false; + + // Store Old Mode + const int old_mode = glfwGetInputMode(glfw_window, GLFW_CURSOR); + + // Handle Cursor Visibility + int new_mode; + if (!new_cursor_visible) { + if (new_cursor_grabbed) { + new_mode = GLFW_CURSOR_DISABLED; + } else { + new_mode = GLFW_CURSOR_HIDDEN; + } + } else { + new_mode = GLFW_CURSOR_NORMAL; + } + if (new_mode != old_mode) { + // Ignore Relative Cursor Motion When Locking + if (new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) { + ignore_relative_motion = true; + } + + // Set New Mode + glfwSetInputMode(glfw_window, GLFW_CURSOR, new_mode); + + // Handle Cursor Lock/Unlock + if ((new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) || (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED)) { + // Use Raw Mouse Motion + if (raw_mouse_motion_enabled) { + glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, new_mode == GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE); + } + + // Request Focus + if (!glfwGetWindowAttrib(glfw_window, GLFW_FOCUSED)) { + glfwRequestWindowAttention(glfw_window); + } + } + + // Reset Mouse Position When Unlocking + if (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED) { + double cursor_x; + double cursor_y; + glfwGetCursorPos(glfw_window, &cursor_x, &cursor_y); + glfw_motion(glfw_window, cursor_x, cursor_y); + } + } + } +} + +// Fix SDL Cursor Visibility/Grabbing +SDL_GrabMode SDL_WM_GrabInput(const SDL_GrabMode mode) { + if (mode == SDL_GRAB_QUERY) { + // Query + return cursor_grabbed ? SDL_GRAB_ON : SDL_GRAB_OFF; + } else if (mode == SDL_GRAB_ON) { + // Store State + cursor_grabbed = true; + } else if (mode == SDL_GRAB_OFF) { + // Store State + cursor_grabbed = false; + } + // Update Cursor GLFW State (Client Only) + update_cursor(); + // Return + return mode; +} + +// Stub SDL Cursor Visibility +int SDL_ShowCursor(const int toggle) { + if (toggle == SDL_QUERY) { + // Query + return cursor_visible ? SDL_ENABLE : SDL_DISABLE; + } else if (toggle == SDL_ENABLE) { + // Store State + cursor_visible = true; + } else if (toggle == SDL_DISABLE) { + // Store State + cursor_visible = false; + } + // Update Cursor GLFW State (Client Only) + update_cursor(); + // Return + return toggle; +} + +// Get Framebuffer Size +void media_get_framebuffer_size(int *width, int *height) { + if (glfw_window) { + glfwGetFramebufferSize(glfw_window, width, height); + } else { + *width = DEFAULT_WIDTH; + *height = DEFAULT_HEIGHT; + } +} diff --git a/media-layer/trampoline/CMakeLists.txt b/media-layer/trampoline/CMakeLists.txt index 3dd5bbd..1f4307e 100644 --- a/media-layer/trampoline/CMakeLists.txt +++ b/media-layer/trampoline/CMakeLists.txt @@ -1,19 +1,13 @@ project(media-layer-trampoline) -# Configuration -set(MEDIA_LAYER_TRAMPOLINE_SRC src/media-layer-core.cpp) # Media Layer Trampoline Source -if(NOT MCPI_HEADLESS_MODE) - list(APPEND MEDIA_LAYER_TRAMPOLINE_SRC src/GLESv1_CM.cpp) -endif() +# Common Sources +set(MEDIA_LAYER_TRAMPOLINE_SRC src/media-layer-core.cpp src/GLESv1_CM.cpp) # Build if(BUILD_NATIVE_COMPONENTS) # Host Component add_library(media-layer-trampoline src/host/host.cpp ${MEDIA_LAYER_TRAMPOLINE_SRC}) - target_link_libraries(media-layer-trampoline reborn-util media-layer-core trampoline-headers) - if(NOT MCPI_HEADLESS_MODE) - target_link_libraries(media-layer-trampoline GLESv1_CM) - endif() + target_link_libraries(media-layer-trampoline reborn-util media-layer-core GLESv1_CM trampoline-headers) target_compile_definitions(media-layer-trampoline PRIVATE -DMEDIA_LAYER_TRAMPOLINE_HOST) # Install install(TARGETS media-layer-trampoline DESTINATION "${MCPI_LIB_DIR}") diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index e252e0b..60e58c1 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -46,72 +46,52 @@ set(SRC src/text-input-box/TextInputScreen.cpp # test src/test/test.cpp + # sound + src/sound/sound.cpp + src/sound/repository.cpp + # camera + src/camera/camera.cpp + # input + src/input/input.cpp + src/input/bow.cpp + src/input/attack.cpp + src/input/toggle.cpp + src/input/misc.cpp + src/input/drop.cpp + # sign + src/sign/sign.cpp + # atlas + src/atlas/atlas.cpp + # title-screen + src/title-screen/title-screen.cpp + src/title-screen/splashes.txt # Show In IDE + src/title-screen/welcome.cpp + # skin + src/skin/skin.cpp + src/skin/loader.cpp + # screenshot + src/screenshot/screenshot.cpp + # textures + src/textures/textures.cpp + src/textures/lava.cpp + src/textures/headless.cpp + # fps + src/fps/fps.cpp + # server + src/server/server.cpp + src/server/server_properties.cpp + # multiplayer + src/multiplayer/multiplayer.cpp + # benchmark + src/benchmark/benchmark.cpp # init src/init/init.cpp ) - -# Server-Only Sources -if(MCPI_SERVER_MODE) - list(APPEND SRC - # server - src/server/server.cpp - src/server/server_properties.cpp - ) -else() - list(APPEND SRC - # multiplayer - src/multiplayer/multiplayer.cpp - # benchmark - src/benchmark/benchmark.cpp - ) -endif() - -# Headless-Only Sources -if(MCPI_HEADLESS_MODE) - list(APPEND SRC - # textures - src/textures/headless.cpp - ) -else() - list(APPEND SRC - # sound - src/sound/sound.cpp - src/sound/repository.cpp - # camera - src/camera/camera.cpp - # input - src/input/input.cpp - src/input/bow.cpp - src/input/attack.cpp - src/input/toggle.cpp - src/input/misc.cpp - src/input/drop.cpp - src/input/crafting.cpp - # sign - src/sign/sign.cpp - # atlas - src/atlas/atlas.cpp - # title-screen - src/title-screen/title-screen.cpp - src/title-screen/splashes.txt # Show In IDE - src/title-screen/welcome.cpp - # skin - src/skin/skin.cpp - src/skin/loader.cpp - # screenshot - src/screenshot/screenshot.cpp - # textures - src/textures/textures.cpp - src/textures/lava.cpp - # fps - src/fps/fps.cpp - ) - # Install Splashes - install( - FILES "src/title-screen/splashes.txt" - DESTINATION "${MCPI_INSTALL_DIR}/data" - ) -endif() +# Install Splashes +install( + FILES "src/title-screen/splashes.txt" + DESTINATION "${MCPI_INSTALL_DIR}/data" +) # Build add_library(mods SHARED ${SRC}) @@ -122,10 +102,7 @@ install(TARGETS mods DESTINATION "${MCPI_INSTALL_DIR}/mods") install(TARGETS mods EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}") # Dependencies -target_link_libraries(mods symbols reborn-patch media-layer-core dl pthread) -if(NOT MCPI_HEADLESS_MODE) - target_link_libraries(mods stb_image) -endif() +target_link_libraries(mods symbols reborn-patch media-layer-core stb_image dl pthread) # Headers target_include_directories( diff --git a/mods/include/mods/feature/feature.h b/mods/include/mods/feature/feature.h index 0a0b64c..40fcf5f 100644 --- a/mods/include/mods/feature/feature.h +++ b/mods/include/mods/feature/feature.h @@ -1,16 +1,10 @@ #pragma once -#include - extern "C" { -bool _feature_has(const char *name); +bool _feature_has(const char *name, int server_default); } -#ifdef MCPI_SERVER_MODE -#define _feature_has__server_defaul_is_server_disabled(name) 0 -#define _feature_has__server_defaul_is_server_auto(name) _feature_has(name) -#define _feature_has__server_defaul_is_server_enabled(name) 1 -#define feature_has(name, server_default) _feature_has__server_defaul_is_##server_default(name) -#else -#define feature_has(name, server_default) _feature_has(name) -#endif +#define _feature_has_server_disabled (0) +#define _feature_has_server_auto (-1) +#define _feature_has_server_enabled (1) +#define feature_has(name, server_default) _feature_has(name, _feature_has_##server_default) diff --git a/mods/include/mods/game-mode/game-mode.h b/mods/include/mods/game-mode/game-mode.h new file mode 100644 index 0000000..7d037e5 --- /dev/null +++ b/mods/include/mods/game-mode/game-mode.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int get_seed_from_string(std::string str); \ No newline at end of file diff --git a/mods/include/mods/home/home.h b/mods/include/mods/home/home.h index 09e4283..87dfa54 100644 --- a/mods/include/mods/home/home.h +++ b/mods/include/mods/home/home.h @@ -1,5 +1,5 @@ #pragma once extern "C" { -char *home_get(); +const char *home_get(); } diff --git a/mods/include/mods/init/init.h b/mods/include/mods/init/init.h index f8ad672..eaaabd4 100644 --- a/mods/include/mods/init/init.h +++ b/mods/include/mods/init/init.h @@ -4,13 +4,9 @@ extern "C" { void run_tests(); void init_version(); void init_compat(); -#ifdef MCPI_SERVER_MODE void init_server(); -#else void init_multiplayer(); void init_benchmark(); -#endif -#ifndef MCPI_HEADLESS_MODE void init_sound(); void init_input(); void init_sign(); @@ -19,7 +15,6 @@ void init_atlas(); void init_title_screen(); void init_skin(); void init_fps(); -#endif void init_touch(); void init_textures(); void init_creative(); diff --git a/mods/include/mods/input/input.h b/mods/include/mods/input/input.h index 1e7322f..1075f15 100644 --- a/mods/include/mods/input/input.h +++ b/mods/include/mods/input/input.h @@ -9,7 +9,6 @@ void input_run_on_tick(input_tick_function_t function); void input_set_is_right_click(int val); int input_back(); void input_drop(int drop_slot); -void input_open_crafting(); void input_set_is_left_click(int val); diff --git a/mods/include/mods/screenshot/screenshot.h b/mods/include/mods/screenshot/screenshot.h index 2ac4dff..693e062 100644 --- a/mods/include/mods/screenshot/screenshot.h +++ b/mods/include/mods/screenshot/screenshot.h @@ -1,5 +1,5 @@ #pragma once extern "C" { -void screenshot_take(char *home); +void screenshot_take(const char *home); } \ No newline at end of file diff --git a/mods/src/atlas/atlas.cpp b/mods/src/atlas/atlas.cpp index bd749c3..0b60a02 100644 --- a/mods/src/atlas/atlas.cpp +++ b/mods/src/atlas/atlas.cpp @@ -91,9 +91,6 @@ static void FurnaceScreen_render_ItemRenderer_renderGuiItem_one_injection(Font * // Init void init_atlas() { - // Add Better nullptr-Check (And More UI Fixes When The gui_blocks Atlas Is Disabled) - overwrite_calls(ItemRenderer_renderGuiItem_two, ItemRenderer_renderGuiItem_two_injection); - // Disable The gui_blocks Atlas Which Contains Pre-Rendered Textures For Blocks In The Inventory if (feature_has("Disable \"gui_blocks\" Atlas", server_disabled)) { unsigned char disable_gui_blocks_atlas_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop" @@ -105,5 +102,6 @@ void init_atlas() { overwrite_calls(Tesselator_color, Tesselator_color_injection); overwrite_call((void *) 0x32324, (void *) FurnaceScreen_render_ItemRenderer_renderGuiItem_one_injection); overwrite_call((void *) 0x1e21c, (void *) InventoryPane_renderBatch_Tesselator_color_injection); + overwrite_calls(ItemRenderer_renderGuiItem_two, ItemRenderer_renderGuiItem_two_injection); } } diff --git a/mods/src/benchmark/benchmark.cpp b/mods/src/benchmark/benchmark.cpp index 77c3349..0d7fdb4 100644 --- a/mods/src/benchmark/benchmark.cpp +++ b/mods/src/benchmark/benchmark.cpp @@ -47,14 +47,12 @@ static void start_world(Minecraft *minecraft) { } // Track Frames -#ifndef MCPI_HEADLESS_MODE static unsigned long long int frames = 0; HOOK(media_swap_buffers, void, ()) { ensure_media_swap_buffers(); real_media_swap_buffers(); frames++; } -#endif // Track Ticks static unsigned long long int ticks = 0; @@ -74,9 +72,7 @@ static long long int get_time() { // Store Time When World Loaded static bool world_loaded = false; static long long int world_loaded_time; -#ifndef MCPI_HEADLESS_MODE static unsigned long long int world_loaded_frames; -#endif static unsigned long long int world_loaded_ticks; // Last Logged Status Update @@ -98,11 +94,9 @@ static void Minecraft_update_injection(Minecraft *minecraft) { // Detect World Loaded if (!world_loaded && minecraft->isLevelGenerated()) { world_loaded = true; - INFO("Loaded"); + INFO("Benchmark Loaded"); world_loaded_time = now; -#ifndef MCPI_HEADLESS_MODE world_loaded_frames = frames; -#endif world_loaded_ticks = ticks; } @@ -110,9 +104,7 @@ static void Minecraft_update_injection(Minecraft *minecraft) { if (!exit_requested && world_loaded) { // Get Time long long int current_time = now - world_loaded_time; -#ifndef MCPI_HEADLESS_MODE unsigned long long int current_frames = frames - world_loaded_frames; -#endif unsigned long long int current_ticks = ticks - world_loaded_ticks; // Log @@ -151,11 +143,11 @@ static void Minecraft_update_injection(Minecraft *minecraft) { // Calculate FPS & TPS INFO("Benchmark Completed"); INFO(" Total Time: %lld Nanoseconds", current_time); -#ifndef MCPI_HEADLESS_MODE - static double frames_per_nanosecond = ((double) current_frames) / ((double) current_time); - static double frames_per_second = frames_per_nanosecond * NANOSECONDS_IN_SECOND; - INFO(" FPS: %f (%llu Total Frames)", frames_per_second, current_frames); -#endif + if (!reborn_is_headless()) { + static double frames_per_nanosecond = ((double) current_frames) / ((double) current_time); + static double frames_per_second = frames_per_nanosecond * NANOSECONDS_IN_SECOND; + INFO(" FPS: %f (%llu Total Frames)", frames_per_second, current_frames); + } static double ticks_per_nanosecond = ((double) current_ticks) / ((double) current_time); static double ticks_per_second = ticks_per_nanosecond * NANOSECONDS_IN_SECOND; INFO(" TPS: %f (%llu Total Ticks)", ticks_per_second, current_ticks); diff --git a/mods/src/camera/camera.cpp b/mods/src/camera/camera.cpp index 8cbb4bd..496e59c 100644 --- a/mods/src/camera/camera.cpp +++ b/mods/src/camera/camera.cpp @@ -8,9 +8,7 @@ // Take Screenshot Using TripodCamera static void AppPlatform_saveScreenshot_injection(__attribute__((unused)) AppPlatform_saveScreenshot_t original, __attribute__((unused)) AppPlatform *app_platform, __attribute__((unused)) std::string *path, __attribute__((unused)) int32_t width, __attribute__((unused)) int32_t height) { -#ifndef MCPI_HEADLESS_MODE screenshot_take(home_get()); -#endif } // Enable TripodCameraRenderer diff --git a/mods/src/compat/compat.cpp b/mods/src/compat/compat.cpp index 980c738..b2ee166 100644 --- a/mods/src/compat/compat.cpp +++ b/mods/src/compat/compat.cpp @@ -10,15 +10,12 @@ #include #include - -#ifndef MCPI_HEADLESS_MODE #include #include #include #include #include -#endif // Custom Title HOOK(SDL_WM_SetCaption, void, (__attribute__((unused)) const char *title, const char *icon)) { @@ -37,14 +34,12 @@ HOOK(SDL_ShowCursor, int, (int toggle)) { HOOK(SDL_PollEvent, int, (SDL_Event *event)) { // In Server Mode, Exit Requests Are Handled In src/server/server.cpp // Check If Exit Is Requested -#ifndef MCPI_SERVER_MODE - if (compat_check_exit_requested()) { + if (!reborn_is_server() && compat_check_exit_requested()) { // Send SDL_QUIT SDL_Event new_event; new_event.type = SDL_QUIT; SDL_PushEvent(&new_event); } -#endif // Poll Events ensure_SDL_PollEvent(); @@ -53,8 +48,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) { // Handle Events if (ret == 1 && event != nullptr) { int handled = 0; - -#ifndef MCPI_HEADLESS_MODE switch (event->type) { case SDL_KEYDOWN: { // Handle Key Presses @@ -71,10 +64,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) { // Drop Item input_drop((event->key.keysym.mod & KMOD_CTRL) != 0); handled = 1; - } else if (event->key.keysym.sym == SDLK_WORLD_0) { - // Crafting - input_open_crafting(); - handled = 1; } break; } @@ -97,8 +86,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) { break; } } -#endif - if (handled) { // Event Was Handled return SDL_PollEvent(event); diff --git a/mods/src/creative/creative.cpp b/mods/src/creative/creative.cpp index ee06b1b..6fedff5 100644 --- a/mods/src/creative/creative.cpp +++ b/mods/src/creative/creative.cpp @@ -6,7 +6,6 @@ #include #include -#ifndef MCPI_SERVER_MODE // Add Item To Inventory static void inventory_add_item(FillingContainer *inventory, Item *item) { ItemInstance *item_instance = new ItemInstance; @@ -83,7 +82,6 @@ static void Inventory_setupDefault_FillingContainer_addItem_call_injection(Filli filling_container->addItem(new_item_instance); } } -#endif // Hook Specific TileItem Constructor static TileItem *Tile_initTiles_TileItem_injection(TileItem *tile_item, int32_t id) { @@ -111,9 +109,7 @@ int creative_is_restricted() { void init_creative() { // Add Extra Items To Creative Inventory (Only Replace Specific Function Call) if (feature_has("Expand Creative Mode Inventory", server_enabled)) { -#ifndef MCPI_SERVER_MODE misc_run_on_creative_inventory_setup(Inventory_setupDefault_FillingContainer_addItem_call_injection); -#endif // Use AuxDataTileItem by default instead of TileItem, so tiles in the Creative // Inventory can have arbitrary auxiliary values. diff --git a/mods/src/feature/feature.cpp b/mods/src/feature/feature.cpp index 498530d..71b3367 100644 --- a/mods/src/feature/feature.cpp +++ b/mods/src/feature/feature.cpp @@ -6,7 +6,11 @@ #include // Check For Feature -bool _feature_has(const char *name) { +bool _feature_has(const char *name, int server_default) { + // Server Handling + if (reborn_is_server() && server_default != -1) { + return server_default > 0; + } // Get Value char *env = getenv("MCPI_FEATURE_FLAGS"); char *features = strdup(env != nullptr ? env : ""); diff --git a/mods/src/game-mode/ui.cpp b/mods/src/game-mode/ui.cpp index 709417d..6ed1bcb 100644 --- a/mods/src/game-mode/ui.cpp +++ b/mods/src/game-mode/ui.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -7,6 +8,7 @@ #include #include #include +#include #include "game-mode-internal.h" // Strings @@ -175,20 +177,24 @@ static std::string getUniqueLevelName(LevelStorageSource *source, const std::str } // Create World -static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed_str) { - // Get Seed +int get_seed_from_string(std::string str) { int seed; - seed_str = Util::stringTrim(&seed_str); - if (!seed_str.empty()) { + str = Util::stringTrim(&str); + if (!str.empty()) { int num; - if (sscanf(seed_str.c_str(), "%d", &num) > 0) { + if (sscanf(str.c_str(), "%d", &num) > 0) { seed = num; } else { - seed = Util::hashCode(&seed_str); + seed = Util::hashCode(&str); } } else { seed = Common::getEpochTimeS(); } + return seed; +} +static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed_str) { + // Get Seed + int seed = get_seed_from_string(std::move(seed_str)); // Get Folder Name name = Util::stringTrim(&name); diff --git a/mods/src/home/home.cpp b/mods/src/home/home.cpp index fef1b4c..fd118e0 100644 --- a/mods/src/home/home.cpp +++ b/mods/src/home/home.cpp @@ -7,24 +7,20 @@ #include // Get MCPI Home Directory -char *home_get() { - static char *dir = nullptr; +const char *home_get() { + static std::string dir = ""; // Load - if (dir == nullptr) { - safe_asprintf(&dir, "%s" HOME_SUBDIRECTORY_FOR_GAME_DATA, getenv("HOME")); + if (dir.empty()) { + dir = std::string(getenv("HOME")) + std::string(get_home_subdirectory_for_game_data()); } // Return - return dir; -} -// Free -__attribute__((destructor)) static void _free_home() { - free(home_get()); + return dir.c_str(); } // Init void init_home() { // Store Data In ~/.minecraft-pi Instead Of ~/.minecraft - patch_address((void *) &Strings::default_path, (void *) HOME_SUBDIRECTORY_FOR_GAME_DATA); + patch_address((void *) &Strings::default_path, (void *) get_home_subdirectory_for_game_data()); // The override code resolves assets manually, // making changing directory redundant. diff --git a/mods/src/init/init.cpp b/mods/src/init/init.cpp index abee803..259636e 100644 --- a/mods/src/init/init.cpp +++ b/mods/src/init/init.cpp @@ -8,12 +8,11 @@ __attribute__((constructor)) static void init() { run_tests(); init_version(); init_compat(); -#ifdef MCPI_SERVER_MODE - init_server(); -#else - init_multiplayer(); -#endif -#ifndef MCPI_HEADLESS_MODE + if (reborn_is_server()) { + init_server(); + } else { + init_multiplayer(); + } init_sound(); init_input(); init_sign(); @@ -22,7 +21,6 @@ __attribute__((constructor)) static void init() { init_title_screen(); init_skin(); init_fps(); -#endif init_touch(); init_textures(); init_creative(); @@ -34,7 +32,7 @@ __attribute__((constructor)) static void init() { init_bucket(); init_cake(); init_home(); -#ifndef MCPI_SERVER_MODE - init_benchmark(); -#endif + if (!reborn_is_server()) { + init_benchmark(); + } } diff --git a/mods/src/input/crafting.cpp b/mods/src/input/crafting.cpp deleted file mode 100644 index 30f7219..0000000 --- a/mods/src/input/crafting.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include - -#include "input-internal.h" -#include -#include - -// Store Should Open Crafting Menu -static int should_open_crafting = 0; -void input_open_crafting() { - should_open_crafting = 1; -} -static void _handle_open_crafting(Minecraft *minecraft) { - if (should_open_crafting) { - should_open_crafting = 0; - - // Set Screen - if (!creative_is_restricted() || !Minecraft_isCreativeMode(minecraft)) { - WorkbenchScreen *screen = new WorkbenchScreen; - ALLOC_CHECK(screen); - screen = screen->constructor(0); - minecraft->setScreen((Screen *) screen); - } - } -} - -// Init -void _init_crafting() { - input_run_on_tick(_handle_open_crafting); -} diff --git a/mods/src/input/input-internal.h b/mods/src/input/input-internal.h index 95fd0ba..779dbf3 100644 --- a/mods/src/input/input-internal.h +++ b/mods/src/input/input-internal.h @@ -4,5 +4,4 @@ __attribute__((visibility("internal"))) void _init_attack(); __attribute__((visibility("internal"))) void _init_bow(); __attribute__((visibility("internal"))) void _init_misc(); __attribute__((visibility("internal"))) void _init_toggle(); -__attribute__((visibility("internal"))) void _init_drop(); -__attribute__((visibility("internal"))) void _init_crafting(); \ No newline at end of file +__attribute__((visibility("internal"))) void _init_drop(); \ No newline at end of file diff --git a/mods/src/input/input.cpp b/mods/src/input/input.cpp index 09a18d2..992165d 100644 --- a/mods/src/input/input.cpp +++ b/mods/src/input/input.cpp @@ -49,9 +49,6 @@ void init_input() { // Allow Attacking Mobs _init_attack(); - // Allow Opening Crafting With Controller - _init_crafting(); - // Disable Raw Mouse Motion if (feature_has("Disable Raw Mouse Motion (Not Recommended)", server_disabled)) { media_set_raw_mouse_motion_enabled(0); diff --git a/mods/src/misc/api.cpp b/mods/src/misc/api.cpp index cbe594d..5dca299 100644 --- a/mods/src/misc/api.cpp +++ b/mods/src/misc/api.cpp @@ -3,9 +3,7 @@ #include #include -#ifndef MCPI_HEADLESS_MODE #include -#endif #include #include "misc-internal.h" @@ -95,9 +93,7 @@ void misc_run_on_game_key_press(const std::function &fun // Render Fancy Background void misc_render_background(int color, Minecraft *minecraft, int x, int y, int width, int height) { // https://github.com/ReMinecraftPE/mcpe/blob/f0d65eaecec1b3fe9c2f2b251e114a890c54ab77/source/client/gui/components/RolledSelectionList.cpp#L169-L179 -#ifndef MCPI_HEADLESS_MODE glColor4f(1, 1, 1, 1); -#endif std::string texture = "gui/background.png"; minecraft->textures->loadAndBindTexture(&texture); Tesselator *t = &Tesselator::instance; diff --git a/mods/src/misc/misc.cpp b/mods/src/misc/misc.cpp index 5276802..dd15b7e 100644 --- a/mods/src/misc/misc.cpp +++ b/mods/src/misc/misc.cpp @@ -9,9 +9,7 @@ #include #include -#ifndef MCPI_HEADLESS_MODE #include -#endif #include #include @@ -143,9 +141,7 @@ static void Gui_renderChatMessages_injection(Gui_renderChatMessages_t original, // Render Selected Item Text if (render_selected_item_text) { // Fix GL Mode -#ifndef MCPI_HEADLESS_MODE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -#endif // Calculate Selected Item Text Scale Minecraft *minecraft = gui->minecraft; int32_t screen_width = minecraft->screen_width; @@ -184,37 +180,25 @@ static void Inventory_selectSlot_injection(Inventory_selectSlot_t original, Inve // Translucent Toolbar static void Gui_renderToolBar_injection(Gui_renderToolBar_t original, Gui *gui, float param_1, int32_t param_2, int32_t param_3) { // Call Original Method -#ifndef MCPI_HEADLESS_MODE - int was_blend_enabled = glIsEnabled(GL_BLEND); + bool was_blend_enabled = glIsEnabled(GL_BLEND); if (!was_blend_enabled) { glEnable(GL_BLEND); } glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -#endif original(gui, param_1, param_2, param_3); -#ifndef MCPI_HEADLESS_MODE if (!was_blend_enabled) { glDisable(GL_BLEND); } -#endif } static void Gui_renderToolBar_glColor4f_injection(GLfloat red, GLfloat green, GLfloat blue, __attribute__((unused)) GLfloat alpha) { // Fix Alpha -#ifndef MCPI_HEADLESS_MODE glColor4f(red, green, blue, 1.0f); -#else - (void) red; - (void) green; - (void) blue; -#endif } // Fix Screen Rendering When GUI is Hidden static void Screen_render_injection(Screen_render_t original, Screen *screen, int32_t param_1, int32_t param_2, float param_3) { // Fix -#ifndef MCPI_HEADLESS_MODE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -#endif // Call Original Method original(screen, param_1, param_2, param_3); } @@ -265,18 +249,21 @@ static const char *RAKNET_ERROR_NAMES[] = { "Couldn't Generate GUID", "Unknown" }; -#ifdef MCPI_SERVER_MODE -#define PRINT_RAKNET_STARTUP_FAILURE ERR -#else -#define PRINT_RAKNET_STARTUP_FAILURE WARN -#endif +#define CONDITIONAL_ERR(is_error, ...) \ + { \ + if ((is_error)) { \ + ERR(__VA_ARGS__); \ + } else { \ + WARN(__VA_ARGS__); \ + } \ + } static RakNet_StartupResult RakNetInstance_host_RakNet_RakPeer_Startup_injection(RakNet_RakPeer *rak_peer, unsigned short maxConnections, unsigned char *socketDescriptors, uint32_t socketDescriptorCount, int32_t threadPriority) { // Call Original Method RakNet_StartupResult result = rak_peer->Startup(maxConnections, socketDescriptors, socketDescriptorCount, threadPriority); // Print Error if (result != RAKNET_STARTED) { - PRINT_RAKNET_STARTUP_FAILURE("Failed To Start RakNet: %s", RAKNET_ERROR_NAMES[result]); + CONDITIONAL_ERR(reborn_is_server(), "Failed To Start RakNet: %s", RAKNET_ERROR_NAMES[result]); } // Return @@ -339,7 +326,6 @@ static int32_t FurnaceScreen_handleAddItem_injection(FurnaceScreen_handleAddItem // // The default behavior for Touch GUI is to only render the cursor when the mouse is clicking, this fixes that. // This also makes the cursor always render if the mouse is unlocked, instead of just when there is a Screen showing. -#ifndef MCPI_HEADLESS_MODE static void GameRenderer_render_injection(GameRenderer_render_t original, GameRenderer *game_renderer, float param_1) { // Call Original Method original(game_renderer, param_1); @@ -356,7 +342,6 @@ static void GameRenderer_render_injection(GameRenderer_render_t original, GameRe Common::renderCursor(x, y, minecraft); } } -#endif // Get Real Selected Slot int32_t misc_get_real_selected_slot(Player *player) { @@ -375,12 +360,12 @@ int32_t misc_get_real_selected_slot(Player *player) { return selected_slot; } -#ifndef MCPI_HEADLESS_MODE // Properly Generate Buffers static void anGenBuffers_injection(int32_t count, uint32_t *buffers) { - glGenBuffers(count, buffers); + if (!reborn_is_headless()) { + glGenBuffers(count, buffers); + } } -#endif // Fix Graphics Bug When Switching To First-Person While Sneaking static void PlayerRenderer_render_injection(PlayerRenderer *model_renderer, Entity *entity, float param_2, float param_3, float param_4, float param_5, float param_6) { @@ -564,9 +549,8 @@ static ContainerMenu *ContainerMenu_destructor_injection(ContainerMenu_destructo return original(container_menu); } -#ifndef MCPI_HEADLESS_MODE // Custom Outline Color -static void glColor4f_injection(__attribute__((unused)) GLfloat red, __attribute__((unused)) GLfloat green, __attribute__((unused)) GLfloat blue, __attribute__((unused)) GLfloat alpha) { +static void LevelRenderer_render_AABB_glColor4f_injection(__attribute__((unused)) GLfloat red, __attribute__((unused)) GLfloat green, __attribute__((unused)) GLfloat blue, __attribute__((unused)) GLfloat alpha) { // Set Color glColor4f(0, 0, 0, 0.4); @@ -591,7 +575,6 @@ static void glColor4f_injection(__attribute__((unused)) GLfloat red, __attribute // Set Line Width glLineWidth(line_width); } -#endif // Fix Furnace Visual Bug static int FurnaceTileEntity_getLitProgress_injection(FurnaceTileEntity_getLitProgress_t original, FurnaceTileEntity *furnace, int max) { @@ -756,7 +739,8 @@ static std::string AppPlatform_linux_getDateString_injection(__attribute__((unus } // Init -static void nop() { +template +static void nop(__attribute__((unused)) Args... args) { } void init_misc() { // Remove Invalid Item Background (A Red Background That Appears For Items That Are Not Included In The gui_blocks Atlas) @@ -820,13 +804,6 @@ void init_misc() { overwrite_calls(FurnaceScreen_handleAddItem, FurnaceScreen_handleAddItem_injection); } -#ifdef MCPI_HEADLESS_MODE - // Don't Render Game In Headless Mode - overwrite_manual((void *) GameRenderer_render, (void *) nop); - overwrite_manual((void *) NinecraftApp_initGLStates, (void *) nop); - overwrite_manual((void *) Gui_onConfigChanged, (void *) nop); - overwrite_manual((void *) LevelRenderer_generateSky, (void *) nop); -#else // Improved Cursor Rendering if (feature_has("Improved Cursor Rendering", server_disabled)) { // Disable Normal Cursor Rendering @@ -835,7 +812,6 @@ void init_misc() { // Add Custom Cursor Rendering overwrite_calls(GameRenderer_render, GameRenderer_render_injection); } -#endif // Disable V-Sync if (feature_has("Disable V-Sync", server_enabled)) { @@ -849,13 +825,11 @@ void init_misc() { // Remove Forced GUI Lag if (feature_has("Remove Forced GUI Lag (Can Break Joining Servers)", server_enabled)) { - overwrite_manual((void *) Common_sleepMs, (void *) nop); + overwrite_calls(Common_sleepMs, nop); } -#ifndef MCPI_HEADLESS_MODE // Properly Generate Buffers overwrite(Common_anGenBuffers, anGenBuffers_injection); -#endif // Fix Graphics Bug When Switching To First-Person While Sneaking patch_vtable(PlayerRenderer_render, PlayerRenderer_render_injection); @@ -928,15 +902,13 @@ void init_misc() { } overwrite_calls(ChestTileEntity_shouldSave, ChestTileEntity_shouldSave_injection); -#ifndef MCPI_HEADLESS_MODE // Replace Block Highlight With Outline if (feature_has("Replace Block Highlight With Outline", server_disabled)) { overwrite(LevelRenderer_renderHitSelect, LevelRenderer_renderHitOutline); unsigned char fix_outline_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop" patch((void *) 0x4d830, fix_outline_patch); - overwrite_call((void *) 0x4d764, (void *) glColor4f_injection); + overwrite_call((void *) 0x4d764, (void *) LevelRenderer_render_AABB_glColor4f_injection); } -#endif // Fix Furnace Visual Bug overwrite_calls(FurnaceTileEntity_getLitProgress, FurnaceTileEntity_getLitProgress_injection); @@ -999,4 +971,12 @@ void init_misc() { // Init Logging _init_misc_logging(); _init_misc_api(); + + // Don't Render Game In Headless Mode + if (reborn_is_headless()) { + overwrite_calls(GameRenderer_render, nop); + overwrite_calls(NinecraftApp_initGLStates, nop); + overwrite_calls(Gui_onConfigChanged, nop); + overwrite_calls(LevelRenderer_generateSky, nop); + } } diff --git a/mods/src/multiplayer/multiplayer.cpp b/mods/src/multiplayer/multiplayer.cpp index 4104962..11d543e 100644 --- a/mods/src/multiplayer/multiplayer.cpp +++ b/mods/src/multiplayer/multiplayer.cpp @@ -1,16 +1,10 @@ -// Config Needs To Load First -#include - -#ifdef MCPI_SERVER_MODE -#error "External Server Code Requires Client Mode" -#endif - #include #include #include #include #include +#include #include #include @@ -21,7 +15,7 @@ struct server_list_entry { std::string address; int port; }; -static std::vector server_list_entries; +static std::vector server_list_entries; static bool server_list_loaded = false; static void load_servers() { // Prepare @@ -54,40 +48,34 @@ static void load_servers() { std::string line; while (std::getline(server_list_file, line)) { // Check Line - if (line.length() > 0) { + if (!line.empty()) { if (line[0] == '#') { continue; } // Parse std::string address; std::string port_str; - // Add Default Port If Needed - size_t last_colon = line.find_last_of(':'); - if (last_colon == std::string::npos) { - line.append(":19132"); - last_colon = line.find_last_of(':'); - } - // Loop - for (std::string::size_type i = 0; i < line.length(); i++) { - if (i > last_colon) { - port_str.push_back(line[i]); - } else if (i < last_colon) { - address.push_back(line[i]); - } + size_t separator_pos = line.find_last_of('|'); + if (separator_pos == std::string::npos) { + port_str = "19132"; + address = line; + } else { + address = line.substr(0, separator_pos); + port_str = line.substr(separator_pos + 1); } // Check Line - if (address.length() < 1 || port_str.length() < 1 || port_str.find_first_not_of("0123456789") != std::string::npos) { + if (address.empty() || port_str.empty() || port_str.find_first_not_of("0123456789") != std::string::npos) { // Invalid Line WARN("Invalid Server: %s", line.c_str()); continue; } // Parse Port int port = std::stoi(port_str); - // Done - struct server_list_entry entry; - entry.address = address; - entry.port = port; + server_list_entry entry = { + .address = address, + .port = port + }; server_list_entries.push_back(entry); } } @@ -98,7 +86,7 @@ static void load_servers() { } // Iterare Server List -static void iterate_servers(std::function callback) { +static void iterate_servers(const std::function &callback) { // Load if (!server_list_loaded) { load_servers(); @@ -106,8 +94,7 @@ static void iterate_servers(std::function c } // Loop - for (std::vector::size_type i = 0; i < server_list_entries.size(); i++) { - struct server_list_entry entry = server_list_entries[i]; + for (const server_list_entry &entry : server_list_entries) { callback(entry.address.c_str(), entry.port); } } @@ -122,7 +109,7 @@ static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t // Add External Servers iterate_servers([rak_peer](const char *address, int port) { - rak_peer->Ping(address, port, 1, 0); + rak_peer->Ping(address, port, true, 0); }); } diff --git a/mods/src/options/info.cpp b/mods/src/options/info.cpp index b5b1e7c..b6ee17a 100644 --- a/mods/src/options/info.cpp +++ b/mods/src/options/info.cpp @@ -40,9 +40,9 @@ static std::string extra_version_info_full = !extra_version_info.empty() ? (" (" // Profile Directory static std::string profile_directory_suffix = #ifdef MCPI_IS_FLATPAK_BUILD - "/.var/app/" MCPI_APP_ID + "/.var/app/" MCPI_APP_ID + #endif - HOME_SUBDIRECTORY_FOR_GAME_DATA + std::string(get_home_subdirectory_for_game_data()) ; static std::string get_profile_directory_url() { const char *home = getenv("HOME"); diff --git a/mods/src/options/options.cpp b/mods/src/options/options.cpp index 47c33d9..db78686 100644 --- a/mods/src/options/options.cpp +++ b/mods/src/options/options.cpp @@ -57,10 +57,8 @@ static void Options_initDefaultValue_injection(Options_initDefaultValue_t origin original(options); // Default Graphics Settings -#ifndef MCPI_SERVER_MODE options->fancy_graphics = true; options->ambient_occlusion = true; -#endif // Store stored_options = options; @@ -133,26 +131,15 @@ static std::vector OptionsFile_getOptionStrings_injection(OptionsFi } // Get New options.txt Path -#ifndef MCPI_SERVER_MODE -static char *get_new_options_txt_path() { - static char *path = nullptr; +static const char *get_new_options_txt_path() { + static std::string path = ""; // Path - if (path == nullptr) { - safe_asprintf(&path, "%s/options.txt", home_get()); + if (path.empty()) { + path = !reborn_is_server() ? (std::string(home_get()) + "/options.txt") : "/dev/null"; } // Return - return path; + return path.c_str(); } -// Free -__attribute__((destructor)) static void _free_new_options_txt_path() { - free(get_new_options_txt_path()); -} -#else -static char *get_new_options_txt_path() { - // Block options.txt On Servers - return (char *) "/dev/null"; -} -#endif // Init void init_options() { diff --git a/mods/src/override/override.cpp b/mods/src/override/override.cpp index e782095..56d76ba 100644 --- a/mods/src/override/override.cpp +++ b/mods/src/override/override.cpp @@ -34,34 +34,32 @@ char *override_get_path(const char *filename) { } // Get MCPI Home Path - char *home_path = home_get(); + const std::string home_path = home_get(); // Get Asset Override Path - char *overrides = nullptr; - safe_asprintf(&overrides, "%s/overrides", home_path); + const std::string overrides = home_path + "/overrides"; // Data Prefiix - const char *data_prefix = "data/"; - int data_prefix_length = strlen(data_prefix); + const std::string data_prefix = "data/"; + int data_prefix_length = data_prefix.length(); // Folders To Check - char *asset_folders[] = { + std::string asset_folders[] = { overrides, getenv("MCPI_REBORN_ASSETS_PATH"), getenv("MCPI_VANILLA_ASSETS_PATH"), - nullptr + "" }; // Check For Override - char *new_path = nullptr; - if (starts_with(filename, data_prefix)) { + std::string new_path; + if (std::string(filename).rfind(data_prefix, 0) == 0) { // Test Asset Folders - for (int i = 0; asset_folders[i] != nullptr; i++) { - safe_asprintf(&new_path, "%s/%s", asset_folders[i], &filename[data_prefix_length]); + for (int i = 0; !asset_folders[i].empty(); i++) { + new_path = asset_folders[i] + '/' + &filename[data_prefix_length]; ensure_access(); - if (real_access(new_path, F_OK) == -1) { + if (real_access(new_path.c_str(), F_OK) == -1) { // Not Found In Asset Folder - free(new_path); - new_path = nullptr; + new_path = ""; continue; } else { // Found @@ -70,11 +68,14 @@ char *override_get_path(const char *filename) { } } - // Free - free(overrides); - // Return - return new_path; + if (new_path.empty()) { + return nullptr; + } else { + char *ret = strdup(new_path.c_str()); + ALLOC_CHECK(ret); + return ret; + } } // Hook fopen diff --git a/mods/src/screenshot/screenshot.cpp b/mods/src/screenshot/screenshot.cpp index edbcd8d..384f110 100644 --- a/mods/src/screenshot/screenshot.cpp +++ b/mods/src/screenshot/screenshot.cpp @@ -38,7 +38,12 @@ static int save_png(const char *filename, unsigned char *pixels, int line_size, // Write Image return !stbi_write_png(filename, width, height, 4, pixels, line_size); } -void screenshot_take(char *home) { +void screenshot_take(const char *home) { + // Check + if (reborn_is_headless()) { + IMPOSSIBLE(); + } + // Get Directory char *screenshots = nullptr; safe_asprintf(&screenshots, "%s/screenshots", home); diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index 5f29ce0..9556167 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -1,12 +1,5 @@ -// Config Needs To Load First -#include - -#ifndef MCPI_SERVER_MODE -#error "Server Code Requires Server Mode" -#endif - #include -#include +#include #include #include #include @@ -19,15 +12,16 @@ #include +#include #include #include - #include #include #include #include #include +#include // --only-generate: Ony Generate World And Then Exit static bool only_generate = false; @@ -79,8 +73,8 @@ static void start_world(Minecraft *minecraft) { // Specify Level Settings LevelSettings settings; settings.game_type = get_server_properties().get_int("game-mode", DEFAULT_GAME_MODE); - std::string seed_str = get_server_properties().get_string("seed", DEFAULT_SEED); - int32_t seed = seed_str.length() > 0 ? std::stoi(seed_str) : time(nullptr); + const std::string seed_str = get_server_properties().get_string("seed", DEFAULT_SEED); + const int32_t seed = get_seed_from_string(seed_str); settings.seed = seed; // Select Level @@ -89,7 +83,7 @@ static void start_world(Minecraft *minecraft) { // Don't Open Port When Using --only-generate if (!only_generate) { // Open Port - int port = get_server_properties().get_int("port", DEFAULT_PORT); + const int port = get_server_properties().get_int("port", DEFAULT_PORT); INFO("Listening On: %i", port); minecraft->hostMultiplayer(port); } @@ -130,8 +124,8 @@ static Level *get_level(Minecraft *minecraft) { } // Find Players With Username And Run Callback -typedef void (*player_callback_t)(Minecraft *minecraft, std::string username, Player *player); -static void find_players(Minecraft *minecraft, std::string target_username, player_callback_t callback, bool all_players) { +typedef void (*player_callback_t)(Minecraft *minecraft, const std::string &username, Player *player); +static void find_players(Minecraft *minecraft, const std::string &target_username, player_callback_t callback, bool all_players) { Level *level = get_level(minecraft); std::vector players = get_players_in_level(level); bool found_player = false; @@ -177,7 +171,7 @@ static char *get_player_ip(Minecraft *minecraft, Player *player) { // Ban Player static bool is_ip_in_blacklist(const char *ip); -static void ban_callback(Minecraft *minecraft, std::string username, Player *player) { +static void ban_callback(Minecraft *minecraft, const std::string &username, Player *player) { // Get IP char *ip = get_player_ip(minecraft, player); @@ -198,13 +192,13 @@ static void ban_callback(Minecraft *minecraft, std::string username, Player *pla } // Kill Player -static void kill_callback(__attribute__((unused)) Minecraft *minecraft, __attribute__((unused)) std::string username, Player *player) { +static void kill_callback(__attribute__((unused)) Minecraft *minecraft, __attribute__((unused)) const std::string &username, Player *player) { player->hurt(nullptr, INT32_MAX); INFO("Killed: %s", username.c_str()); } // List Player -static void list_callback(Minecraft *minecraft, std::string username, Player *player) { +static void list_callback(Minecraft *minecraft, const std::string &username, Player *player) { INFO(" - %s (%s)", username.c_str(), get_player_ip(minecraft, player)); } @@ -416,7 +410,7 @@ static bool is_ip_in_blacklist(const char *ip) { } else { // Check List for (std::string &x : ips) { - if (x.compare(ip) == 0) { + if (x == ip) { return true; } } diff --git a/mods/src/skin/skin.cpp b/mods/src/skin/skin.cpp index f86904d..36e7571 100644 --- a/mods/src/skin/skin.cpp +++ b/mods/src/skin/skin.cpp @@ -79,6 +79,10 @@ static int32_t Textures_loadAndBindTexture_injection(Textures *textures, __attri // Init void init_skin() { + // Not Needed On Headless Mode + if (reborn_is_headless()) { + return; + } // Check Feature Flag if (feature_has("Load Custom Skins", server_disabled)) { // LocalPlayer diff --git a/mods/src/sound/sound.cpp b/mods/src/sound/sound.cpp index cb720e3..3d6f2fd 100644 --- a/mods/src/sound/sound.cpp +++ b/mods/src/sound/sound.cpp @@ -117,6 +117,10 @@ static void SoundEngine_init_injection(SoundEngine_init_t original, SoundEngine // Init void init_sound() { + // Not Needed On Headless Mode + if (reborn_is_headless()) { + return; + } // Implement Sound Engine if (feature_has("Implement Sound Engine", server_disabled)) { overwrite(SoundEngine_playUI, SoundEngine_playUI_injection); diff --git a/mods/src/test/test.cpp b/mods/src/test/test.cpp index 46c983c..7381ac6 100644 --- a/mods/src/test/test.cpp +++ b/mods/src/test/test.cpp @@ -10,10 +10,9 @@ void run_tests() { // Test ~/.minecraft-pi Permissions { - char *path = home_get(); + const char *path = home_get(); int exists = access(path, F_OK) == 0; int can_write = exists ? access(path, R_OK | W_OK) == 0 : 1; - if (!can_write) { // Failure ERR("Invalid Data Directory Permissions"); diff --git a/mods/src/textures/headless.cpp b/mods/src/textures/headless.cpp index 20276ba..8ff4df2 100644 --- a/mods/src/textures/headless.cpp +++ b/mods/src/textures/headless.cpp @@ -1,6 +1,7 @@ #include #include -#include + +#include "textures-internal.h" // Disable Texture Loading static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) AppPlatform_linux_loadTexture_t original, __attribute__((unused)) AppPlatform_linux *app_platform, __attribute__((unused)) std::string *path, __attribute__((unused)) bool b) { @@ -17,7 +18,7 @@ static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) A } // Init -void init_textures() { +void _init_textures_headless() { // Disable Texture Loading overwrite_calls(AppPlatform_linux_loadTexture, AppPlatform_linux_loadTexture_injection); } diff --git a/mods/src/textures/textures-internal.h b/mods/src/textures/textures-internal.h index 7b4ed7c..a26e781 100644 --- a/mods/src/textures/textures-internal.h +++ b/mods/src/textures/textures-internal.h @@ -1,3 +1,4 @@ #pragma once +__attribute__((visibility("internal"))) void _init_textures_headless(); __attribute__((visibility("internal"))) void _init_textures_lava(bool animated_water, bool animated_lava, bool animated_fire); diff --git a/mods/src/textures/textures.cpp b/mods/src/textures/textures.cpp index 3dc3f11..7a6155b 100644 --- a/mods/src/textures/textures.cpp +++ b/mods/src/textures/textures.cpp @@ -213,6 +213,12 @@ static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) A // Init void init_textures() { + // Handle Headless Mode + if (reborn_is_headless()) { + _init_textures_headless(); + return; + } + // Tick Dynamic Textures (Animated Water) bool animated_water = feature_has("Animated Water", server_disabled); bool animated_lava = feature_has("Animated Lava", server_disabled); diff --git a/mods/src/title-screen/welcome.cpp b/mods/src/title-screen/welcome.cpp index 3e72a73..5a9fc12 100644 --- a/mods/src/title-screen/welcome.cpp +++ b/mods/src/title-screen/welcome.cpp @@ -10,7 +10,7 @@ #include "title-screen-internal.h" // Constants -static std::string line1 = "Welcome to " MCPI_APP_BASE_TITLE " v" MCPI_VERSION "!"; +static std::string line1 = "Welcome to " MCPI_APP_TITLE " v" MCPI_VERSION "!"; static int line_height = 8; static int button_width = 120; static int button_height = 24; diff --git a/scripts/build.mjs b/scripts/build.mjs index 4fadeda..af6aac9 100755 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -47,10 +47,6 @@ const PackageTypes = wrap(new Enum([ 'AppImage', 'Flatpak' ])); -const Variants = wrap(new Enum([ - 'Client', - 'Server' -])); const Architectures = wrap(new Enum([ 'AMD64', 'ARM64', @@ -67,7 +63,6 @@ let out = path.join(root, 'out'); // Positional Arguments let argIndex = 2; // Skip First Two Arguments -const POSITIONAL_ARGUMENT_COUNT = 3; function readArg(from, type) { // Check Argument Count if (argIndex >= process.argv.length) { @@ -84,8 +79,6 @@ function readArg(from, type) { } // Type Of Packaging const packageType = readArg(PackageTypes, 'Package Type'); -// Build Variant -const variant = readArg(Variants, 'Variant'); // Build Architecture const architecture = readArg(Architectures, 'Architecture'); // Flatpak Builds Work Best Without Custom Toolchains @@ -133,7 +126,7 @@ function updateDir(dir) { if (packageType !== PackageTypes.None) { dir = path.join(dir, packageType.name); } - return path.join(dir, variant.name, architecture.name); + return path.join(dir, architecture.name); } build = updateDir(build); let cleanOut = false; @@ -147,7 +140,6 @@ if (packageType !== PackageTypes.AppImage) { function toCmakeBool(val) { return val ? 'ON' : 'OFF'; } -options.set('MCPI_SERVER_MODE', toCmakeBool(variant === Variants.Server)); options.set('MCPI_IS_APPIMAGE_BUILD', toCmakeBool(packageType === PackageTypes.AppImage)); options.set('MCPI_IS_FLATPAK_BUILD', toCmakeBool(packageType === PackageTypes.Flatpak)); if (architecture !== Architectures.Host) { diff --git a/scripts/fix-appimage-for-docker.sh b/scripts/fix-appimage-for-docker.sh new file mode 100755 index 0000000..1c9988d --- /dev/null +++ b/scripts/fix-appimage-for-docker.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exec sed -i '0,/AI\x02/{s|AI\x02|\x00\x00\x00|}' "$1" \ No newline at end of file diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index e40a9a4..3d2fcf1 100755 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -9,75 +9,44 @@ if [ "$(id -u)" -eq 0 ]; then } fi -# Main Script -run() { - # Add ARM Repository - for arch in "$@"; do - sudo dpkg --add-architecture "$(echo "${arch}" | tr '[:upper:]' '[:lower:]')" - done +# Run APT +install_pkg() { + sudo apt-get install --no-install-recommends -y "$@" +} - # Update APT - sudo apt-get update - sudo apt-get dist-upgrade -y - - # Install Everything In One Go - PKG_QUEUE='' - queue_pkg() { - PKG_QUEUE="${PKG_QUEUE} $@" - } - - # Build System - queue_pkg \ +# Build Dependencies +run_build() { + install_pkg \ + `# Build System` \ git \ cmake \ ninja-build \ python3 \ - python3-venv - - # Host Dependencies Needed For Compile - queue_pkg \ - libwayland-bin - - # Architecture-Specific Dependencies - architecture_specific_pkg() { - # Compiler - queue_pkg crossbuild-essential-$1 - - # Dependencies - queue_pkg \ - libopenal-dev:$1 - - # GLFW Dependencies - queue_pkg \ - libwayland-dev:$1 \ - libxkbcommon-dev:$1 \ - libx11-dev:$1 \ - libxcursor-dev:$1 \ - libxi-dev:$1 \ - libxinerama-dev:$1 \ - libxrandr-dev:$1 \ - libxext-dev:$1 - - # Zenity Dependencies - queue_pkg \ - libgtk-3-dev:$1 \ - libglib2.0-dev:$1 - } - for arch in "$@"; do - architecture_specific_pkg "$(echo "${arch}" | tr '[:upper:]' '[:lower:]')" - done - - # AppStream Verification - queue_pkg \ + python3-venv \ + `# Host Dependencies Needed For Compile` \ + libwayland-bin \ + `# Compiler` \ + "crossbuild-essential-$1" \ + `# Main Dependencies` \ + "libopenal-dev:$1" \ + `# GLFW Dependencies` \ + "libwayland-dev:$1" \ + "libxkbcommon-dev:$1" \ + "libx11-dev:$1" \ + "libxcursor-dev:$1" \ + "libxi-dev:$1" \ + "libxinerama-dev:$1" \ + "libxrandr-dev:$1" \ + "libxext-dev:$1" \ + `# Zenity Dependencies` \ + "libgtk-3-dev:$1" \ + "libglib2.0-dev:$1" \ + `# AppStream Verification` \ appstream - # Install Queue - sudo apt-get install --no-install-recommends -y ${PKG_QUEUE} - # Install appimagetool - sudo rm -rf /opt/squashfs-root /opt/appimagetool.AppDir - sudo rm -f /opt/appimagetool /usr/local/bin/appimagetool - case "$(dpkg-architecture -qDEB_BUILD_ARCH)" in + sudo rm -rf /opt/squashfs-root /opt/appimagetool /usr/local/bin/appimagetool + case "$(dpkg --print-architecture)" in 'armhf') APPIMAGE_ARCH='armhf';; 'arm64') APPIMAGE_ARCH='aarch64';; 'i386') APPIMAGE_ARCH='i686';; @@ -87,19 +56,43 @@ run() { sudo wget -O /opt/appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${APPIMAGE_ARCH}.AppImage" sudo chmod +x /opt/appimagetool # Workaround AppImage Issues With Docker - cd /opt - sudo sed -i '0,/AI\x02/{s|AI\x02|\x00\x00\x00|}' ./appimagetool + sudo ./scripts/fix-appimage-for-docker.sh /opt/appimagetool # Extract - sudo ./appimagetool --appimage-extract + cd /opt + sudo ./appimagetool --appimage-extract > /dev/null sudo rm -f ./appimagetool # Link - sudo mv ./squashfs-root ./appimagetool.AppDir - sudo ln -s /opt/appimagetool.AppDir/AppRun /usr/local/bin/appimagetool + sudo mv ./squashfs-root ./appimagetool + sudo ln -s /opt/appimagetool/AppRun /usr/local/bin/appimagetool } -# Run -if [ "$#" -lt 1 ]; then - run "$(dpkg-architecture -qDEB_BUILD_ARCH)" -else - run "$@" -fi +# Test Dependencies +run_test() { + sudo apt-get install --no-install-recommends -y \ + "libc6:$1" \ + "libopenal1:$1" \ + "libglib2.0-0:$1" +} + +# Example Mods Dependencies +run_example_mods() { + install_pkg \ + cmake \ + ninja-build \ + g++-arm-linux-gnueabihf \ + gcc-arm-linux-gnueabihf +} + +# Variables +MODE="$1" +ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" + +# Add ARM Repository +sudo dpkg --add-architecture "${ARCH}" + +# Update APT +sudo apt-get update +sudo apt-get dist-upgrade -y + +# Install Packages +"run_${MODE}" "${ARCH}" diff --git a/scripts/simulate-ci.sh b/scripts/simulate-ci.sh deleted file mode 100755 index 77c194a..0000000 --- a/scripts/simulate-ci.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -set -e - -# Change Directory -cd "$(dirname "$0")/../" - -# Run -act push -W '.gitea/workflows/build.yml' diff --git a/scripts/test.sh b/scripts/test.sh index 57299fb..47c10d0 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -7,29 +7,35 @@ cd "$(dirname "$0")/../" # Variables MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH='host' +ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" +APPIMAGE="$(pwd)/out/minecraft-pi-reborn-$(cat VERSION)-${ARCH}.AppImage" -# Build -./scripts/build.mjs none "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON - -# Add To PATH -export PATH="$(pwd)/out/${MODE}/${ARCH}/usr/bin:${PATH}" +# Check If File Exists +if [ ! -f "${APPIMAGE}" ]; then + echo 'Missing AppImage!' > /dev/stderr + exit 1 +fi # Make Test Directory TEST_WORKING_DIR="$(pwd)/.testing-tmp" rm -rf "${TEST_WORKING_DIR}" mkdir -p "${TEST_WORKING_DIR}" +ROOT="$(pwd)" +cd "${TEST_WORKING_DIR}" + +# Prepare AppImage For Docker +cp "${APPIMAGE}" tmp.AppImage +"${ROOT}/scripts/fix-appimage-for-docker.sh" tmp.AppImage +chmod +x tmp.AppImage # Run if [ "${MODE}" = "server" ]; then # Server Test - cd "${TEST_WORKING_DIR}" - minecraft-pi-reborn-server --only-generate + ./tmp.AppImage --appimage-extract-and-run --server --only-generate else # Client Test - export _MCPI_SKIP_ROOT_CHECK=1 export HOME="${TEST_WORKING_DIR}" - minecraft-pi-reborn-client --default --no-cache --benchmark + ./tmp.AppImage --appimage-extract-and-run --default --no-cache --benchmark --force-headless fi # Clean Up