diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 5af1cab..d8db7e4 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -1,4 +1,4 @@ -name: 'Build' +name: 'CI' on: push: @@ -34,12 +34,13 @@ jobs: run: ./scripts/install-dependencies.sh ${{ matrix.arch }} # Build - name: Build - run: ./scripts/package.sh ${{ matrix.mode }} ${{ matrix.arch }} + run: ./scripts/build.mjs appimage ${{ matrix.mode }} ${{ matrix.arch }} - name: Upload Artifacts uses: actions/upload-artifact@v3 with: - name: ${{ matrix.mode }}-${{ matrix.arch }} + name: ${{ matrix.mode }} (${{ matrix.arch }}) path: ./out/*.AppImage* + if-no-files-found: error # Test Project test: strategy: @@ -59,12 +60,42 @@ jobs: # Dependencies - name: Install Dependencies run: ./scripts/install-dependencies.sh - - name: Install ARM Toolchain - if: ${{ matrix.mode == 'Client' }} - run: apt-get install --no-install-recommends -y g++-arm-linux-gnueabihf gcc-arm-linux-gnueabihf # Test - name: Test run: ./scripts/test.sh ${{ matrix.mode }} + # Example Mods + example-mods: + name: Build Example Mods + 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 + - 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/build.mjs none client host + export _MCPI_SKIP_ROOT_CHECK=1 + export DISPLAY= + ./out/client/host/usr/bin/minecraft-pi-reborn-client --copy-sdk + # Build Example Mods + - name: Build Example Mods + run: | + cd example-mods + ./build.sh + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: Example Mods + path: ./example-mods/out/* + if-no-files-found: error # Create Release release: if: startsWith(github.ref, 'refs/tags/') @@ -87,7 +118,7 @@ jobs: - name: Create Release uses: https://gitea.com/actions/release-action@main with: - files: ./out + files: ./out/*/*.AppImage* api_key: ${{ secrets.RELEASE_TOKEN }} title: v${{ github.ref_name }} body: "[View Changelog](https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/CHANGELOG.md)" diff --git a/.gitignore b/.gitignore index 05aa6e2..d241a27 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ /*.AppImage /core* /qemu_* +/example-mods/out +/.testing-tmp diff --git a/CMakeLists.txt b/CMakeLists.txt index 350e8ec..8df5635 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ endif() include(cmake/options/core-options.cmake) # Build Mode -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release") +if(NOT DEFINED CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) endif() # Start Project diff --git a/cmake/options/extra-options.cmake b/cmake/options/extra-options.cmake index 17b2c62..896aec8 100644 --- a/cmake/options/extra-options.cmake +++ b/cmake/options/extra-options.cmake @@ -2,6 +2,9 @@ mcpi_option(OPEN_SOURCE_ONLY "Only Install Open-Source Code (Will Result In Broken Install)" BOOL FALSE) mcpi_option(IS_APPIMAGE_BUILD "AppImage Build" BOOL FALSE) mcpi_option(IS_FLATPAK_BUILD "Flatpak Build" BOOL FALSE) +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) @@ -61,3 +64,13 @@ mcpi_option(APP_TITLE "App Title" STRING "${DEFAULT_APP_TITLE}") # Skin Server mcpi_option(SKIN_SERVER "Skin Server" STRING "https://raw.githubusercontent.com/MCPI-Revival/Skins/data") + +# QEMU +if(BUILD_NATIVE_COMPONENTS) + include(CheckSymbolExists) + check_symbol_exists("__ARM_ARCH" "" MCPI_IS_ARM32_OR_ARM64_TARGETING) + set(MCPI_USE_QEMU TRUE) + if(MCPI_IS_ARM32_OR_ARM64_TARGETING) + set(MCPI_USE_QEMU FALSE) + endif() +endif() diff --git a/cmake/toolchain/base-toolchain.cmake b/cmake/toolchain/base-toolchain.cmake index dd8eba1..5060ca2 100644 --- a/cmake/toolchain/base-toolchain.cmake +++ b/cmake/toolchain/base-toolchain.cmake @@ -9,6 +9,7 @@ macro(setup_toolchain target) add_target_variant(unknown) add_target_variant(none) add_target_variant(pc) + # Find Compiler macro(find_compiler output name) set(possible_names "") @@ -26,13 +27,11 @@ macro(setup_toolchain target) endmacro() find_compiler(CMAKE_C_COMPILER "gcc") find_compiler(CMAKE_CXX_COMPILER "g++") + # Extra set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # Custom Search Paths - if(NOT DEFINED ENV{MCPI_TOOLCHAIN_USE_DEFAULT_SEARCH_PATHS}) - # Find Root - set(CMAKE_FIND_ROOT_PATH "/usr/${target}" "/usr/lib/${target}" "/usr") - # pkg-config - set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/${target}/pkgconfig:/usr/${target}/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig") - endif() + set(CMAKE_FIND_ROOT_PATH "/usr/${target}" "/usr/lib/${target}" "/usr") + # pkg-config + set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/${target}/pkgconfig:/usr/${target}/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig") endmacro() diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index f4cc313..c613304 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -13,11 +13,11 @@ if(BUILD_NATIVE_COMPONENTS AND NOT MCPI_SERVER_MODE) add_subdirectory(zenity) endif() # LIEF -if(BUILD_NATIVE_COMPONENTS OR (BUILD_ARM_COMPONENTS AND NOT MCPI_SERVER_MODE AND NOT MCPI_USE_MEDIA_LAYER_PROXY)) +if(BUILD_NATIVE_COMPONENTS OR (BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE)) add_subdirectory(LIEF) endif() # QEMU -if(BUILD_NATIVE_COMPONENTS AND NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "arm*" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")) +if(BUILD_NATIVE_COMPONENTS AND MCPI_USE_QEMU) add_subdirectory(qemu) endif() # GLFW diff --git a/docs/BUILDING.md b/docs/BUILDING.md index b1cf79b..61654b4 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -9,14 +9,5 @@ ## Instructions ```sh -./scripts/build.sh +./scripts/build.mjs [--clean] [--install] [-Dvar=value...] ``` - -### Custom CMake Arguments -```sh -./scripts/setup.sh -./scripts/build.sh -``` - -### Environment Variables -* `MCPI_TOOLCHAIN_USE_DEFAULT_SEARCH_PATHS`: Use Default CMake Search Paths Rather Than Guessing diff --git a/example-mods/build.sh b/example-mods/build.sh new file mode 100755 index 0000000..2fc61e6 --- /dev/null +++ b/example-mods/build.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +set -e + +# Create Output Directory +ROOT="$(pwd)" +OUT="${ROOT}/out" +rm -rf "${OUT}" +mkdir -p "${OUT}" + +# Build +build() { + cd "${ROOT}/$1" + # Build + rm -rf build + mkdir build + cd build + cmake -GNinja .. + cmake --build . + # Copy Result + cp lib*.so "${OUT}" +} +build chat-commands +build expanded-creative +build recipes diff --git a/launcher/src/bootstrap.c b/launcher/src/bootstrap.c index e5e9ee4..000289e 100644 --- a/launcher/src/bootstrap.c +++ b/launcher/src/bootstrap.c @@ -10,10 +10,6 @@ #define MCPI_BINARY "minecraft-pi" #define QEMU_BINARY "qemu-arm" -#ifndef __ARM_ARCH -#define USE_QEMU -#endif - #define REQUIRED_PAGE_SIZE 4096 #define _STR(x) #x #define STR(x) _STR(x) @@ -163,7 +159,7 @@ void pre_bootstrap(int argc, char *argv[]) { sigaction(SIGTERM, &act_sigterm, NULL); // Check Page Size (Not Needed When Using QEMU) -#ifndef USE_QEMU +#ifndef MCPI_USE_QEMU long page_size = sysconf(_SC_PAGESIZE); if (page_size != REQUIRED_PAGE_SIZE) { ERR("Invalid page size! A page size of %ld bytes is required, but the system size is %ld bytes.", (long) REQUIRED_PAGE_SIZE, page_size); @@ -338,7 +334,7 @@ void bootstrap(int argc, char *argv[]) { new_args[argv_start] = new_mcpi_exe_path; // Non-ARM Systems Need QEMU -#ifdef USE_QEMU +#ifdef MCPI_USE_QEMU argv_start--; new_args[argv_start] = QEMU_BINARY; // Use 4k Page Size @@ -349,7 +345,7 @@ void bootstrap(int argc, char *argv[]) { setup_exec_environment(1); // Pass LD_* Variables Through QEMU -#ifdef USE_QEMU +#ifdef MCPI_USE_QEMU char *qemu_set_env = NULL; #define pass_variable_through_qemu(name) string_append(&qemu_set_env, "%s%s=%s", qemu_set_env == NULL ? "" : ",", name, getenv(name)); for_each_special_environmental_variable(pass_variable_through_qemu); diff --git a/launcher/src/client/available-feature-flags b/launcher/src/client/available-feature-flags index c0acdcb..b49f12d 100644 --- a/launcher/src/client/available-feature-flags +++ b/launcher/src/client/available-feature-flags @@ -53,4 +53,5 @@ TRUE 3D Chest Model TRUE Replace Block Highlight With Outline TRUE Add Cake TRUE Use Java Beta 1.3 Light Ramp -TRUE Send Full Level When Hosting Game \ No newline at end of file +TRUE Send Full Level When Hosting Game +FALSE Food Overlay diff --git a/libreborn/include/libreborn/config.h.in b/libreborn/include/libreborn/config.h.in index a31eb17..3d0c5bc 100644 --- a/libreborn/include/libreborn/config.h.in +++ b/libreborn/include/libreborn/config.h.in @@ -12,3 +12,4 @@ #cmakedefine MCPI_VARIANT_NAME "@MCPI_VARIANT_NAME@" #cmakedefine MCPI_SDK_DIR "@MCPI_SDK_DIR@" #cmakedefine MCPI_SKIN_SERVER "@MCPI_SKIN_SERVER@" +#cmakedefine MCPI_USE_QEMU diff --git a/media-layer/core/src/media.c b/media-layer/core/src/media.c index 759c01e..68c5fd6 100644 --- a/media-layer/core/src/media.c +++ b/media-layer/core/src/media.c @@ -219,7 +219,7 @@ static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int c memset(str, 0, str_size); codepoint_to_utf8((unsigned char *) str, codepoint); char *cp437_str = to_cp437(str); - // Send EventĀ· + // Send Event for (int i = 0; cp437_str[i] != '\0'; i++) { character_event(cp437_str[i]); } diff --git a/mods/include/mods/chat/chat.h b/mods/include/mods/chat/chat.h index 3ff276c..f8a1001 100644 --- a/mods/include/mods/chat/chat.h +++ b/mods/include/mods/chat/chat.h @@ -13,10 +13,6 @@ std::string chat_send_api_command(Minecraft *minecraft, std::string str); extern "C" { #endif -#ifndef MCPI_SERVER_MODE -void chat_open(); -#endif - // Override using the HOOK() macro to provide customized chat behavior. void chat_send_message(ServerSideNetworkHandler *server_side_network_handler, char *username, char *message); void chat_handle_packet_send(Minecraft *minecraft, ChatPacket *packet); diff --git a/mods/include/mods/input/input.h b/mods/include/mods/input/input.h index c738365..78b1a4b 100644 --- a/mods/include/mods/input/input.h +++ b/mods/include/mods/input/input.h @@ -10,8 +10,6 @@ typedef void (*input_tick_function_t)(Minecraft *minecraft); void input_run_on_tick(input_tick_function_t function); void input_set_is_right_click(int val); -void input_hide_gui(); -void input_third_person(); int input_back(); void input_drop(int drop_slot); void input_open_crafting(); diff --git a/mods/include/mods/misc/misc.h b/mods/include/mods/misc/misc.h index dedaca8..493d270 100644 --- a/mods/include/mods/misc/misc.h +++ b/mods/include/mods/misc/misc.h @@ -23,6 +23,8 @@ typedef void (*misc_update_function_void_t)(void *obj); void misc_run_on_tiles_setup(misc_update_function_void_t function); // obj == NULL void misc_run_on_items_setup(misc_update_function_void_t function); // obj == NULL void misc_run_on_language_setup(misc_update_function_void_t function); // obj == NULL +typedef bool (*misc_update_function_key_press_t)(Minecraft *minecrtaft, int key); +void misc_run_on_game_key_press(misc_update_function_key_press_t function); // In-Game Key Presses Only void Level_saveLevelData_injection(Level *level); diff --git a/mods/include/mods/text-input-box/TextInputBox.h b/mods/include/mods/text-input-box/TextInputBox.h index 2f3410b..16f62bf 100644 --- a/mods/include/mods/text-input-box/TextInputBox.h +++ b/mods/include/mods/text-input-box/TextInputBox.h @@ -3,22 +3,9 @@ #include struct TextInputBox { - GuiComponent super; + static TextInputBox *create(const std::string &placeholder = "", const std::string &text = ""); - int m_ID; - int m_xPos; - int m_yPos; - int m_width; - int m_height; - std::string m_placeholder; - std::string m_text; - bool m_bFocused; - bool m_bEnabled; - bool m_bCursorOn; - int m_insertHead; - int m_lastFlashed; - Font *m_pFont; - int m_maxLength; + GuiComponent super; void setSize(int x, int y, int width = 200, int height = 12); void init(Font *pFont); @@ -30,6 +17,25 @@ struct TextInputBox { void setFocused(bool b); void onClick(int x, int y); bool clicked(int x, int y); + std::string getText(); + bool isFocused(); + void setMaxLength(int max_length); - static TextInputBox *create(int id, const std::string &placeholder = "", const std::string &text = ""); +private: + void recalculateScroll(); + + std::string m_text; + bool m_bFocused; + int m_xPos; + int m_yPos; + int m_width; + int m_height; + std::string m_placeholder; + bool m_bEnabled; + bool m_bCursorOn; + int m_insertHead; + int m_lastFlashed; + Font *m_pFont; + int m_maxLength; + int m_scrollPos; }; diff --git a/mods/include/mods/touch/touch.h b/mods/include/mods/touch/touch.h new file mode 100644 index 0000000..e375b4c --- /dev/null +++ b/mods/include/mods/touch/touch.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +Button *touch_create_button(int id, std::string text); diff --git a/mods/src/chat/chat-internal.h b/mods/src/chat/chat-internal.h index 293c6a0..128e478 100644 --- a/mods/src/chat/chat-internal.h +++ b/mods/src/chat/chat-internal.h @@ -1,19 +1,21 @@ #pragma once +#include + #include -#ifdef __cplusplus -extern "C" { -#endif +// Message Limitations +#define MAX_CHAT_MESSAGE_LENGTH 256 +// Message Prefix +__attribute__((visibility("internal"))) std::string _chat_get_prefix(char *username); + +// Queue Message For Sending #ifndef MCPI_SERVER_MODE __attribute__((visibility("internal"))) void _chat_queue_message(const char *message); #endif +// Init Chat UI #ifndef MCPI_HEADLESS_MODE __attribute__((visibility("internal"))) void _init_chat_ui(); #endif - -#ifdef __cplusplus -} -#endif diff --git a/mods/src/chat/chat.cpp b/mods/src/chat/chat.cpp index 1fef0d4..563d670 100644 --- a/mods/src/chat/chat.cpp +++ b/mods/src/chat/chat.cpp @@ -19,9 +19,6 @@ #include "chat-internal.h" #include -// Message Limitations -#define MAX_CHAT_MESSAGE_LENGTH 512 - // Send API Command std::string chat_send_api_command(Minecraft *minecraft, std::string str) { struct ConnectedClient client; @@ -47,9 +44,12 @@ static void send_api_chat_command(Minecraft *minecraft, char *str) { #endif // Send Message To Players +std::string _chat_get_prefix(char *username) { + return std::string("<") + username + "> "; +} void chat_send_message(ServerSideNetworkHandler *server_side_network_handler, char *username, char *message) { char *full_message = NULL; - safe_asprintf(&full_message, "<%s> %s", username, message); + safe_asprintf(&full_message, "%s%s", _chat_get_prefix(username).c_str(), message); sanitize_string(&full_message, MAX_CHAT_MESSAGE_LENGTH, 0); std::string cpp_string = full_message; free(full_message); @@ -123,5 +123,8 @@ void init_chat() { // Init UI _init_chat_ui(); #endif + // Disable Built-In Chat Message Limiting + unsigned char message_limit_patch[4] = {0x03, 0x00, 0x53, 0xe1}; // "cmp r4, r4" + patch((void *) 0x6b4c0, message_limit_patch); } } diff --git a/mods/src/chat/ui.cpp b/mods/src/chat/ui.cpp index c7d6000..4aa18c1 100644 --- a/mods/src/chat/ui.cpp +++ b/mods/src/chat/ui.cpp @@ -8,6 +8,7 @@ #include #include #include +#include // Structure struct ChatScreen { @@ -23,24 +24,16 @@ CUSTOM_VTABLE(chat_screen, Screen) { original_init(super); ChatScreen *self = (ChatScreen *) super; // Text Input - self->chat = TextInputBox::create(1); + self->chat = TextInputBox::create(); self->super.m_textInputs->push_back(self->chat); self->chat->init(super->font); self->chat->setFocused(true); + // Determine Max Length + std::string prefix = _chat_get_prefix(Strings_default_username); + int max_length = MAX_CHAT_MESSAGE_LENGTH - prefix.length(); + self->chat->setMaxLength(max_length); // Send Button - if (Minecraft_isTouchscreen(super->minecraft)) { - self->send = (Button *) new Touch_TButton; - } else { - self->send = new Button; - } - ALLOC_CHECK(self->send); - int send_id = 2; - std::string send_text = "Send"; - if (Minecraft_isTouchscreen(super->minecraft)) { - Touch_TButton_constructor((Touch_TButton *) self->send, send_id, &send_text); - } else { - Button_constructor(self->send, send_id, &send_text); - } + self->send = touch_create_button(1, "Send"); super->rendered_buttons.push_back(self->send); super->selectable_buttons.push_back(self->send); // Hide Chat Messages @@ -69,7 +62,7 @@ CUSTOM_VTABLE(chat_screen, Screen) { vtable->setupPositions = [](Screen *super) { Screen_setupPositions_non_virtual(super); ChatScreen *self = (ChatScreen *) super; - self->send->height = 20; + self->send->height = 24; self->send->width = 40; int x = 0; int y = super->height - self->send->height; @@ -83,9 +76,9 @@ CUSTOM_VTABLE(chat_screen, Screen) { vtable->keyPressed = [](Screen *super, int key) { // Handle Enter ChatScreen *self = (ChatScreen *) super; - if (key == 0x0d && self->chat->m_bFocused) { - if (self->chat->m_text.length() > 0) { - _chat_queue_message(self->chat->m_text.c_str()); + if (key == 0x0d && self->chat->isFocused()) { + if (self->chat->getText().length() > 0) { + _chat_queue_message(self->chat->getText().c_str()); } Minecraft_setScreen(super->minecraft, NULL); } @@ -118,19 +111,17 @@ static Screen *create_chat_screen() { return (Screen *) screen; } -// Open Screen -static bool open_chat_screen = false; -void chat_open() { - open_chat_screen = true; -} - // Init void _init_chat_ui() { - misc_run_on_tick([](Minecraft *minecraft) { - if (open_chat_screen && Minecraft_isLevelGenerated(minecraft) && minecraft->screen == NULL) { - Minecraft_setScreen(minecraft, create_chat_screen()); + misc_run_on_game_key_press([](Minecraft *minecraft, int key) { + if (key == 0x54) { + if (Minecraft_isLevelGenerated(minecraft) && minecraft->screen == NULL) { + Minecraft_setScreen(minecraft, create_chat_screen()); + } + return true; + } else { + return false; } - open_chat_screen = false; }); } diff --git a/mods/src/compat/compat.c b/mods/src/compat/compat.c index aa8f351..fd016cb 100644 --- a/mods/src/compat/compat.c +++ b/mods/src/compat/compat.c @@ -63,15 +63,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) { } else if (event->key.keysym.sym == SDLK_F2) { screenshot_take(home_get()); handled = 1; - } else if (event->key.keysym.sym == SDLK_F1) { - input_hide_gui(); - handled = 1; - } else if (event->key.keysym.sym == SDLK_F5) { - input_third_person(); - handled = 1; - } else if (event->key.keysym.sym == SDLK_t) { - chat_open(); - handled = 1; } else if (event->key.keysym.sym == SDLK_ESCAPE) { // Treat Escape As Back Button Press (This Fixes Issues With Signs) handled = input_back(); diff --git a/mods/src/game-mode/ui.cpp b/mods/src/game-mode/ui.cpp index e1ceff8..9dd5932 100644 --- a/mods/src/game-mode/ui.cpp +++ b/mods/src/game-mode/ui.cpp @@ -1,233 +1,226 @@ // Config Needs To Load First #include - -// Game Mode UI Code Is Useless In Headless Mode -#ifndef MCPI_SERVER_MODE - -#include -#include -#include -#include -#include - -#include -#include - #include "game-mode-internal.h" -// Run Command -static char *run_command_proper(const char *command[], bool allow_empty) { - // Run - int return_code; - char *output = run_command(command, &return_code, NULL); +// Game Mode UI Code Is Useless In Headless Mode +#ifndef MCPI_HEADLESS_MODE - // Handle Message - if (output != NULL) { - // Check Return Code - if (is_exit_status_success(return_code)) { - // Remove Ending Newline - int length = strlen(output); - if (output[length - 1] == '\n') { - output[length - 1] = '\0'; - } - length = strlen(output); - // Don't Allow Empty Strings - if (allow_empty || length > 0) { - // Return - return output; - } - } - // Free Output - free(output); - } - // Return - return !is_exit_status_success(return_code) ? NULL : run_command_proper(command, allow_empty); -} +#include +#include -// Track Create World State -static pthread_mutex_t create_world_state_lock = PTHREAD_MUTEX_INITIALIZER; -typedef enum { - DIALOG_CLOSED, - DIALOG_OPEN, - DIALOG_SUCCESS -} create_world_state_dialog_t; -struct create_world_state_t { - volatile create_world_state_dialog_t dialog_state = DIALOG_CLOSED; - volatile char *name = NULL; - volatile int32_t game_mode = 0; - volatile int32_t seed = 0; +#include + +#include +#include + +// Strings +#define GAME_MODE_STR(mode) ("Game Mode: " mode) +#define SURVIVAL_STR GAME_MODE_STR("Survival") +#define CREATIVE_STR GAME_MODE_STR("Creative") + +// Structure +struct CreateWorldScreen { + TextInputScreen super; + TextInputBox *name; + TextInputBox *seed; + Button *game_mode; + Button *create; + Button *back; }; -static create_world_state_t create_world_state; -// Destructor -__attribute__((destructor)) static void _free_create_world_state_name() { - free((void *) create_world_state.name); -} - -// Reset State (Assume Lock) -static void reset_create_world_state() { - create_world_state.dialog_state = DIALOG_CLOSED; - if (create_world_state.name != NULL) { - free((void *) create_world_state.name); - } - create_world_state.name = NULL; - create_world_state.game_mode = 0; - create_world_state.seed = 0; -} - -// Chat Thread -#define DEFAULT_WORLD_NAME "Unnamed world" -#define DIALOG_TITLE "Create World" -#define GAME_MODE_DIALOG_SIZE "200" -static void *create_world_thread(__attribute__((unused)) void *nop) { - // Run Dialogs - char *world_name = NULL; - { - // World Name - { - // Open - const char *command[] = { - "zenity", - "--title", DIALOG_TITLE, - "--name", MCPI_APP_ID, - "--entry", - "--text", "Enter World Name:", - "--entry-text", DEFAULT_WORLD_NAME, - NULL - }; - char *output = run_command_proper(command, false); - // Handle Message - if (output != NULL) { - // Store - world_name = strdup(output); - ALLOC_CHECK(world_name); - // Free - free(output); - } else { - // Fail - goto fail; - } - } - - // Game Mode - int game_mode = 0; - { - // Open - const char *command[] = { - "zenity", - "--title", DIALOG_TITLE, - "--name", MCPI_APP_ID, - "--list", - "--radiolist", - "--width", GAME_MODE_DIALOG_SIZE, - "--height", GAME_MODE_DIALOG_SIZE, - "--text", "Select Game Mode:", - "--column","Selected", - "--column", "Name", - "TRUE", "Creative", - "FALSE", "Survival", - NULL - }; - char *output = run_command_proper(command, false); - // Handle Message - if (output != NULL) { - // Store - game_mode = strcmp(output, "Creative") == 0; - // Free - free(output); - } else { - // Fail - goto fail; - } - } - +static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed); +CUSTOM_VTABLE(create_world_screen, Screen) { + TextInputScreen::setup(vtable); + // Constants + static int line_height = 8; + static int bottom_padding = 4; + static int inner_padding = 4; + static int description_padding = 4; + static int title_padding = 8; + // Init + static Screen_init_t original_init = vtable->init; + vtable->init = [](Screen *super) { + original_init(super); + CreateWorldScreen *self = (CreateWorldScreen *) super; + // Name + self->name = TextInputBox::create("World Name", "Unnamed world"); + self->super.m_textInputs->push_back(self->name); + self->name->init(super->font); + self->name->setFocused(true); // Seed - int32_t seed = 0; - get_seed: - { - // Open - const char *command[] = { - "zenity", - "--title", DIALOG_TITLE, - "--name", MCPI_APP_ID, - "--entry", - "--only-numerical", - "--text", "Enter Seed (Leave Blank For Random):", - NULL - }; - char *output = run_command_proper(command, true); - // Handle Message - if (output != NULL) { - // Store - bool valid = true; - try { - seed = strlen(output) == 0 ? time(NULL) : std::stoi(output); - } catch (std::invalid_argument &e) { - // Invalid Seed - WARN("Invalid Seed: %s", output); - valid = false; - } catch (std::out_of_range &e) { - // Out-Of-Range Seed - WARN("Seed Out-Of-Range: %s", output); - valid = false; - } - // Free - free(output); - // Retry If Invalid - if (!valid) { - goto get_seed; - } - } else { - // Fail - goto fail; - } + self->seed = TextInputBox::create("Seed"); + self->super.m_textInputs->push_back(self->seed); + self->seed->init(super->font); + self->seed->setFocused(false); + // Game Mode + self->game_mode = touch_create_button(1, CREATIVE_STR); + super->rendered_buttons.push_back(self->game_mode); + super->selectable_buttons.push_back(self->game_mode); + // Create + self->create = touch_create_button(2, "Create"); + super->rendered_buttons.push_back(self->create); + super->selectable_buttons.push_back(self->create); + // Back + self->back = touch_create_button(3, "Back"); + super->rendered_buttons.push_back(self->back); + super->selectable_buttons.push_back(self->back); + }; + // Removal + static Screen_removed_t original_removed = vtable->removed; + vtable->removed = [](Screen *super) { + original_removed(super); + CreateWorldScreen *self = (CreateWorldScreen *) super; + delete self->name; + delete self->seed; + self->game_mode->vtable->destructor_deleting(self->game_mode); + self->back->vtable->destructor_deleting(self->back); + self->create->vtable->destructor_deleting(self->create); + }; + // Rendering + static Screen_render_t original_render = vtable->render; + vtable->render = [](Screen *super, int x, int y, float param_1) { + // Background + super->vtable->renderBackground(super); + // Call Original Method + original_render(super, x, y, param_1); + // Title + std::string title = "Create world"; + Screen_drawCenteredString(super, super->font, &title, super->width / 2, title_padding, 0xffffffff); + // Game Mode Description + CreateWorldScreen *self = (CreateWorldScreen *) super; + bool is_creative = self->game_mode->text == CREATIVE_STR; + std::string description = is_creative ? Strings_creative_mode_description : Strings_survival_mode_description; + Screen_drawString(super, super->font, &description, self->game_mode->x, self->game_mode->y + self->game_mode->height + description_padding, 0xa0a0a0); + }; + // Positioning + vtable->setupPositions = [](Screen *super) { + Screen_setupPositions_non_virtual(super); + CreateWorldScreen *self = (CreateWorldScreen *) super; + // Height/Width + int width = 120; + int height = 24; + self->create->width = self->back->width = self->game_mode->width = width; + int seed_width = self->game_mode->width; + int name_width = width * 1.5f; + self->create->height = self->back->height = self->game_mode->height = height; + int text_box_height = self->game_mode->height; + // Find Center Y + int top = (title_padding * 2) + line_height; + int bottom = super->height - self->create->height - (bottom_padding * 2); + int center_y = ((bottom - top) / 2) + top; + center_y -= (description_padding + line_height) / 2; + // X/Y + self->create->y = self->back->y = super->height - bottom_padding - height; + self->create->x = self->game_mode->x = (super->width / 2) - inner_padding - width; + self->back->x = (super->width / 2) + inner_padding; + int seed_x = self->back->x; + int name_x = (super->width / 2) - (name_width / 2); + int name_y = center_y - inner_padding - height; + self->game_mode->y = center_y + inner_padding; + int seed_y = self->game_mode->y; + // Update Text Boxes + self->name->setSize(name_x, name_y, name_width, text_box_height); + self->seed->setSize(seed_x, seed_y, seed_width, text_box_height); + }; + // ESC + vtable->handleBackEvent = [](Screen *super, bool do_nothing) { + if (!do_nothing) { + ScreenChooser_setScreen(&super->minecraft->screen_chooser, 5); } + return true; + }; + // Button Click + vtable->buttonClicked = [](Screen *super, Button *button) { + CreateWorldScreen *self = (CreateWorldScreen *) super; + bool is_creative = self->game_mode->text == CREATIVE_STR; + if (button == self->game_mode) { + // Toggle Game Mode + self->game_mode->text = is_creative ? SURVIVAL_STR : CREATIVE_STR; + } else if (button == self->back) { + // Back + super->vtable->handleBackEvent(super, false); + } else if (button == self->create) { + // Create + create_world(super->minecraft, self->name->getText(), is_creative, self->seed->getText()); + } + }; +} +static Screen *create_create_world_screen() { + // Construct + CreateWorldScreen *screen = new CreateWorldScreen; + ALLOC_CHECK(screen); + Screen_constructor(&screen->super.super); - // Update State - pthread_mutex_lock(&create_world_state_lock); - reset_create_world_state(); - create_world_state.dialog_state = DIALOG_SUCCESS; - char *safe_name = to_cp437(world_name); - create_world_state.name = safe_name; - free(world_name); - create_world_state.game_mode = game_mode; - create_world_state.seed = seed; - pthread_mutex_unlock(&create_world_state_lock); - // Return - return NULL; - } + // Set VTable + screen->super.super.vtable = get_create_world_screen_vtable(); - fail: - // Update State - pthread_mutex_lock(&create_world_state_lock); - reset_create_world_state(); - pthread_mutex_unlock(&create_world_state_lock); - free(world_name); // Return - return NULL; + return (Screen *) screen; } -// Create Chat Thead -static void open_create_world() { - // Update State (Assume Lock) - create_world_state.dialog_state = DIALOG_OPEN; - // Start Thread - pthread_t thread; - pthread_create(&thread, NULL, create_world_thread, NULL); +// Unique Level Name (https://github.com/ReMinecraftPE/mcpe/blob/d7a8b6baecf8b3b050538abdbc976f690312aa2d/source/client/gui/screens/CreateWorldScreen.cpp#L65-L83) +static std::string getUniqueLevelName(LevelStorageSource *source, const std::string &in) { + std::set maps; + std::vector vls; + source->vtable->getLevelList(source, &vls); + for (int i = 0; i < int(vls.size()); i++) { + const LevelSummary &ls = vls[i]; + maps.insert(ls.folder); + } + std::string out = in; + while (maps.find(out) != maps.end()) { + out += "-"; + } + return out; } // Create World -static void create_world(Screen *host_screen, std::string folder_name) { - // Get Minecraft - Minecraft *minecraft = host_screen->minecraft; +static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed_str) { + // Get Seed + int seed; + seed_str = Util_stringTrim(&seed_str); + if (!seed_str.empty()) { + int num; + if (sscanf(seed_str.c_str(), "%d", &num) > 0) { + seed = num; + } else { + seed = Util_hashCode(&seed_str); + } + } else { + seed = Common_getEpochTimeS(); + } + + // Get Folder Name + name = Util_stringTrim(&name); + std::string folder = ""; + for (char c : name) { + if ( + c >= ' ' && c <= '~' && + c != '/' && + c != '\\' && + c != '`' && + c != '?' && + c != '*' && + c != '<' && + c != '>' && + c != '|' && + c != '"' && + c != ':' + ) { + folder += c; + } + } + if (folder.empty()) { + folder = "World"; + } + folder = getUniqueLevelName(Minecraft_getLevelSource(minecraft), folder); // Settings LevelSettings settings; - settings.game_type = create_world_state.game_mode; - settings.seed = create_world_state.seed; + settings.game_type = is_creative; + settings.seed = seed; // Create World - std::string world_name = (char *) create_world_state.name; - minecraft->vtable->selectLevel(minecraft, &folder_name, &world_name, &settings); + minecraft->vtable->selectLevel(minecraft, &folder, &name, &settings); // Multiplayer Minecraft_hostMultiplayer(minecraft, 19132); @@ -237,55 +230,20 @@ static void create_world(Screen *host_screen, std::string folder_name) { ALLOC_CHECK(screen); screen = ProgressScreen_constructor(screen); Minecraft_setScreen(minecraft, (Screen *) screen); - - // Reset - reset_create_world_state(); } // Redirect Create World Button #define create_SelectWorldScreen_tick_injection(prefix) \ static void prefix##SelectWorldScreen_tick_injection(prefix##SelectWorldScreen *screen) { \ - /* Lock */ \ - pthread_mutex_lock(&create_world_state_lock); \ - \ - bool *should_create_world = &screen->should_create_world; \ - if (*should_create_world) { \ - /* Check State */ \ - if (create_world_state.dialog_state == DIALOG_CLOSED) { \ - /* Open Dialog */ \ - open_create_world(); \ - } \ - \ + if (screen->should_create_world) { \ + /* Open Screen */ \ + Minecraft_setScreen(screen->minecraft, create_create_world_screen()); \ /* Finish */ \ - *should_create_world = false; \ + screen->should_create_world = false; \ } else { \ /* Call Original Method */ \ prefix##SelectWorldScreen_tick_non_virtual(screen); \ } \ - \ - /* Create World If Dialog Succeeded */ \ - if (create_world_state.dialog_state == DIALOG_SUCCESS) { \ - /* Create World Dialog Finished */ \ - \ - /* Get New World Name */ \ - std::string name = (char *) create_world_state.name; \ - std::string new_name = prefix##SelectWorldScreen_getUniqueLevelName(screen, &name); \ - \ - /* Create World */ \ - create_world((Screen *) screen, new_name); \ - } \ - \ - /* Lock/Unlock UI */ \ - if (create_world_state.dialog_state != DIALOG_OPEN) { \ - /* Dialog Closed, Unlock UI */ \ - media_set_interactable(1); \ - } else { \ - /* Dialog Open, Lock UI */ \ - media_set_interactable(0); \ - } \ - \ - /* Unlock */ \ - pthread_mutex_unlock(&create_world_state_lock); \ } create_SelectWorldScreen_tick_injection() create_SelectWorldScreen_tick_injection(Touch_) diff --git a/mods/src/input/misc.c b/mods/src/input/misc.c index 1dc74f6..ac0a57a 100644 --- a/mods/src/input/misc.c +++ b/mods/src/input/misc.c @@ -35,7 +35,7 @@ static void _handle_back(Minecraft *minecraft) { } // Fix OptionsScreen Ignoring The Back Button -static int32_t OptionsScreen_handleBackEvent_injection(OptionsScreen *screen, bool do_nothing) { +static bool OptionsScreen_handleBackEvent_injection(OptionsScreen *screen, bool do_nothing) { if (!do_nothing) { Minecraft *minecraft = screen->minecraft; Minecraft_setScreen(minecraft, NULL); diff --git a/mods/src/input/toggle.c b/mods/src/input/toggle.c index 2d11793..cbfe1a7 100644 --- a/mods/src/input/toggle.c +++ b/mods/src/input/toggle.c @@ -4,40 +4,29 @@ #include "input-internal.h" #include #include - -// Enable Toggles -static int enable_toggles = 0; - -// Store Function Input -static int hide_gui_toggle = 0; -void input_hide_gui() { - hide_gui_toggle++; -} -static int third_person_toggle = 0; -void input_third_person() { - third_person_toggle++; -} +#include // Handle Toggle Options -static void _handle_toggle_options(Minecraft *minecraft) { - if (enable_toggles) { - // Handle Functions - Options *options = &minecraft->options; - if (hide_gui_toggle % 2 != 0) { - // Toggle Hide GUI - options->hide_gui = options->hide_gui ^ 1; - } - hide_gui_toggle = 0; - if (third_person_toggle % 2 != 0) { - // Toggle Third Person - options->third_person = (options->third_person + 1) % 3; - } - third_person_toggle = 0; - // Fix Broken Value From Third-Person OptionsButton Toggle - // (Because Front-Facing Code Repurposes A Boolean As A Ternary) - if (options->third_person == 3) { - options->third_person = 0; - } +static bool _handle_toggle_options(Minecraft *minecraft, int key) { + Options *options = &minecraft->options; + if (key == 0x70) { + // Toggle Hide GUI + options->hide_gui = options->hide_gui ^ 1; + return 1; + } else if (key == 0x74) { + // Toggle Third Person + options->third_person = (options->third_person + 1) % 3; + return 1; + } else { + return 0; + } +} +static void _fix_third_person(Minecraft *minecraft) { + // Fix Broken Value From Third-Person OptionsButton Toggle + // (Because Front-Facing Code Repurposes A Boolean As A Ternary) + Options *options = &minecraft->options; + if (options->third_person == 3) { + options->third_person = 0; } } @@ -99,11 +88,11 @@ static void ParticleEngine_render_injection(ParticleEngine *particle_engine, Ent // Init void _init_toggle() { - enable_toggles = feature_has("Bind Common Toggleable Options To Function Keys", server_disabled); - input_run_on_tick(_handle_toggle_options); + if (feature_has("Bind Common Toggleable Options To Function Keys", server_disabled)) { + misc_run_on_game_key_press(_handle_toggle_options); + misc_run_on_update(_fix_third_person); - // Font-Facing View - if (enable_toggles) { + // Font-Facing View overwrite_calls((void *) GameRenderer_setupCamera, (void *) GameRenderer_setupCamera_injection); overwrite_calls((void *) ParticleEngine_render, (void *) ParticleEngine_render_injection); } diff --git a/mods/src/misc/api.cpp b/mods/src/misc/api.cpp index c3022d0..d947cae 100644 --- a/mods/src/misc/api.cpp +++ b/mods/src/misc/api.cpp @@ -7,18 +7,20 @@ #include "misc-internal.h" // Callbacks -#define SETUP_CALLBACK(name, type) \ -static std::vector &get_misc_##name##_functions() { \ +#define STORE_CALLBACK(name, type) \ + static std::vector &get_misc_##name##_functions() { \ static std::vector functions; \ return functions; \ } \ + void misc_run_on_##name(misc_update_function_##type##_t function) { \ + get_misc_##name##_functions().push_back(function); \ + } +#define SETUP_CALLBACK(name, type) \ + STORE_CALLBACK(name, type) \ static void handle_misc_##name(type *obj) { \ for (misc_update_function_##type##_t function : get_misc_##name##_functions()) { \ function(obj); \ } \ - } \ - void misc_run_on_##name(misc_update_function_##type##_t function) { \ - get_misc_##name##_functions().push_back(function); \ } // Run Functions On Update @@ -115,6 +117,27 @@ static void I18n_loadLanguage_injection(AppPlatform *app, std::string language_n handle_misc_language_setup(NULL); } +// Run Functions On GUI Key Press +STORE_CALLBACK(game_key_press, key_press) +static bool handle_misc_game_key_press(Minecraft *minecraft, int key) { + for (misc_update_function_key_press_t function : get_misc_game_key_press_functions()) { + if (function(minecraft, key)) { + return true; + } + } + return false; +} +// Handle Key Presses +static void Gui_handleKeyPressed_injection(Gui *self, int key) { + // Run Functions + if (handle_misc_game_key_press(self->minecraft, key)) { + return; + } + + // Call Original Method + Gui_handleKeyPressed(self, key); +} + // Init void _init_misc_api() { // Handle Custom Update Behavior @@ -131,4 +154,6 @@ void _init_misc_api() { overwrite_calls((void *) Item_initItems, (void *) Item_initItems_injection); // Handle Custom Language Entries overwrite_calls((void *) I18n_loadLanguage, (void *) I18n_loadLanguage_injection); + // Handle Key Presses + overwrite_calls((void *) Gui_handleKeyPressed, (void *) Gui_handleKeyPressed_injection); } diff --git a/mods/src/misc/misc.c b/mods/src/misc/misc.c index 1ffcedc..aac9da4 100644 --- a/mods/src/misc/misc.c +++ b/mods/src/misc/misc.c @@ -3,6 +3,7 @@ #include #include #include +#include #ifndef MCPI_HEADLESS_MODE #include @@ -19,6 +20,60 @@ #include "misc-internal.h" #include +// Heart food overlay +static int heal_amount = 0, heal_amount_drawing = 0; +void Gui_renderHearts_injection(Gui *gui) { + // Get heal_amount + heal_amount = heal_amount_drawing = 0; + + Inventory *inventory = gui->minecraft->player->inventory; + ItemInstance *held_ii = Inventory_getSelected(inventory); + if (held_ii) { + Item *held = Item_items[held_ii->id]; + if (held->vtable->isFood(held) && held_ii->id) { + int nutrition = ((FoodItem *) held)->nutrition; + int cur_health = gui->minecraft->player->health; + int heal_num = fmin(cur_health + nutrition, 20) - cur_health; + heal_amount = heal_amount_drawing = heal_num; + } + } + + // Call original + Gui_renderHearts(gui); +} + +#define PINK_HEART_FULL 70 +#define PINK_HEART_HALF 79 +Gui_blit_t Gui_blit_renderHearts_original = NULL; +void Gui_renderHearts_GuiComponent_blit_overlay_empty_injection(Gui *gui, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t w1, int32_t h1, int32_t w2, int32_t h2) { + // Call original + Gui_blit_renderHearts_original(gui, x1, y1, x2, y2, w1, h1, w2, h2); + // Render the overlay + if (heal_amount_drawing == 1) { + // Half heart + Gui_blit_renderHearts_original(gui, x1, y1, PINK_HEART_HALF, 0, w1, h1, w2, h2); + heal_amount_drawing = 0; + } else if (heal_amount_drawing > 0) { + // Full heart + Gui_blit_renderHearts_original(gui, x1, y1, PINK_HEART_FULL, 0, w1, h1, w2, h2); + heal_amount_drawing -= 2; + } +} + +void Gui_renderHearts_GuiComponent_blit_overlay_hearts_injection(Gui *gui, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t w1, int32_t h1, int32_t w2, int32_t h2) { + // Offset the overlay + if (x2 == 52) { + heal_amount_drawing += 2; + } else if (x2 == 61 && heal_amount) { + // Half heart, flipped + Gui_blit_renderHearts_original(gui, x1, y1, PINK_HEART_FULL, 0, w1, h1, w2, h2); + heal_amount_drawing += 1; + }; + // Call original + Gui_blit_renderHearts_original(gui, x1, y1, x2, y2, w1, h1, w2, h2); + heal_amount_drawing = fmin(heal_amount_drawing, heal_amount); +} + // Classic HUD #define DEFAULT_HUD_PADDING 2 #define NEW_HUD_PADDING 1 @@ -581,14 +636,24 @@ void init_misc() { patch((void *) 0x63c98, invalid_item_background_patch); } + // Classic HUD + Gui_blit_renderHearts_original = Gui_blit; if (feature_has("Classic HUD", server_disabled)) { use_classic_hud = 1; - overwrite_call((void *) 0x266f8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection); overwrite_call((void *) 0x26758, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection); - overwrite_call((void *) 0x267c8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection); overwrite_call((void *) 0x2656c, (void *) Gui_renderHearts_GuiComponent_blit_armor_injection); overwrite_call((void *) 0x268c4, (void *) Gui_renderBubbles_GuiComponent_blit_injection); + overwrite_call((void *) 0x266f8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection); + overwrite_call((void *) 0x267c8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection); + Gui_blit_renderHearts_original = Gui_renderHearts_GuiComponent_blit_hearts_injection; + } + + // Food overlay + if (feature_has("Food Overlay", server_disabled)) { + overwrite_calls((void *) Gui_renderHearts, Gui_renderHearts_injection); + overwrite_call((void *) 0x266f8, (void *) Gui_renderHearts_GuiComponent_blit_overlay_empty_injection); + overwrite_call((void *) 0x267c8, (void *) Gui_renderHearts_GuiComponent_blit_overlay_hearts_injection); } // Render Selected Item Text + Hide Chat Messages diff --git a/mods/src/options/options.cpp b/mods/src/options/options.cpp index 7bf1bdb..8e4019b 100644 --- a/mods/src/options/options.cpp +++ b/mods/src/options/options.cpp @@ -127,6 +127,12 @@ static void OptionsPane_unknown_toggle_creating_function_injection(OptionsPane * std::string cpp_string = "3D Anaglyph"; OptionsPane_unknown_toggle_creating_function(options_pane, group_id, &cpp_string, &Options_Option_ANAGLYPH); } + + // Add Peaceful Mode + if (option == &Options_Option_SERVER_VISIBLE) { + std::string cpp_string = "Peaceful mode"; + OptionsPane_unknown_toggle_creating_function(options_pane, group_id, &cpp_string, &Options_Option_DIFFICULTY); + } } // Add Missing Options To Options::getBooleanValue @@ -134,12 +140,28 @@ static bool Options_getBooleanValue_injection(Options *options, Options_Option * // Check if (option == &Options_Option_GRAPHICS) { return options->fancy_graphics; + } else if (option == &Options_Option_DIFFICULTY) { + return options->game_difficulty == 0; } else { // Call Original Method return Options_getBooleanValue(options, option); } } +// Fix Difficulty When Toggling +static void OptionButton_toggle_Options_save_injection(Options *self) { + // Fix Value + if (self->game_difficulty == 1) { + // Disable Peaceful + self->game_difficulty = 2; + } else if (self->game_difficulty == 3) { + // Switch To Peaceful + self->game_difficulty = 0; + } + // Call Original Method + Options_save(self); +} + // Init C++ void _init_options_cpp() { // NOP @@ -164,6 +186,9 @@ void _init_options_cpp() { // Add Missing Options To Options::getBooleanValue overwrite_calls((void *) Options_getBooleanValue, (void *) Options_getBooleanValue_injection); + + // Fix Difficulty When Toggling + overwrite_call((void *) 0x1cd00, (void *) OptionButton_toggle_Options_save_injection); } // Actually Save options.txt diff --git a/mods/src/sign/sign.cpp b/mods/src/sign/sign.cpp index 11cbac8..eeb61ff 100644 --- a/mods/src/sign/sign.cpp +++ b/mods/src/sign/sign.cpp @@ -19,6 +19,10 @@ static int32_t sdl_key_to_minecraft_key_injection(int32_t sdl_key) { return 0x25; } else if (sdl_key == SDLK_RIGHT) { return 0x27; + } else if (sdl_key == SDLK_F1) { + return 0x70; + } else if (sdl_key == SDLK_F5) { + return 0x74; } else { // Call Original Method return Common_sdl_key_to_minecraft_key(sdl_key); diff --git a/mods/src/text-input-box/TextInputBox.cpp b/mods/src/text-input-box/TextInputBox.cpp index e91ede1..22d0c8e 100644 --- a/mods/src/text-input-box/TextInputBox.cpp +++ b/mods/src/text-input-box/TextInputBox.cpp @@ -2,13 +2,12 @@ #include -TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const std::string &text) { +TextInputBox *TextInputBox::create(const std::string &placeholder, const std::string &text) { // Construct TextInputBox *self = new TextInputBox; GuiComponent_constructor(&self->super); // Setup - self->m_ID = id; self->m_xPos = 0; self->m_yPos = 0; self->m_width = 0; @@ -22,6 +21,7 @@ TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const self->m_lastFlashed = 0; self->m_pFont = nullptr; self->m_maxLength = -1; + self->m_scrollPos = 0; // Return return self; @@ -32,6 +32,7 @@ void TextInputBox::setSize(int x, int y, int width, int height) { m_yPos = y; m_width = width; m_height = height; + recalculateScroll(); } void TextInputBox::init(Font *pFont) { @@ -61,6 +62,7 @@ void TextInputBox::keyPressed(int key) { } m_text.erase(m_text.begin() + m_insertHead - 1, m_text.begin() + m_insertHead); m_insertHead--; + recalculateScroll(); break; } case 0x2e: { @@ -83,6 +85,7 @@ void TextInputBox::keyPressed(int key) { if (m_insertHead < 0) { m_insertHead = 0; } + recalculateScroll(); break; } case 0x27: { @@ -95,6 +98,7 @@ void TextInputBox::keyPressed(int key) { } else { m_insertHead = 0; } + recalculateScroll(); break; } case 0x0d: { @@ -130,6 +134,7 @@ void TextInputBox::setFocused(bool b) { m_lastFlashed = Common_getTimeMs(); m_bCursorOn = true; m_insertHead = int(m_text.size()); + recalculateScroll(); } } @@ -143,38 +148,64 @@ void TextInputBox::charPressed(int k) { return; } - // note: the width will increase by the same amount no matter where K is appended - std::string test_str = m_text + char(k); - if (m_maxLength != -1 && int(test_str.length()) > m_maxLength) { + // Ignore Newlines + if (k == '\n') { return; } - int width = Font_width(m_pFont, &test_str); - if (width < (m_width - PADDING)) { - m_text.insert(m_text.begin() + m_insertHead, k); - m_insertHead++; + + // Check Max Length + if (m_maxLength != -1 && int(m_text.length()) >= m_maxLength) { + return; } + + // Insert + m_text.insert(m_text.begin() + m_insertHead, k); + m_insertHead++; + recalculateScroll(); } +static std::string get_rendered_text(Font *font, int width, int scroll_pos, std::string text) { + std::string rendered_text = text.substr(scroll_pos); + int max_width = width - (PADDING * 2); + while (Font_width(font, &rendered_text) > max_width) { + rendered_text.pop_back(); + } + return rendered_text; +} + +static char CURSOR_CHAR = '_'; + void TextInputBox::render() { GuiComponent_fill(&super, m_xPos, m_yPos, m_xPos + m_width, m_yPos + m_height, 0xFFAAAAAA); GuiComponent_fill(&super, m_xPos + 1, m_yPos + 1, m_xPos + m_width - 1, m_yPos + m_height - 1, 0xFF000000); - int textYPos = (m_height - 8) / 2; - + int text_color; + int scroll_pos; + std::string rendered_text; if (m_text.empty()) { - GuiComponent_drawString(&super, m_pFont, &m_placeholder, m_xPos + PADDING, m_yPos + textYPos, 0x404040); + rendered_text = m_placeholder; + text_color = 0x404040; + scroll_pos = 0; } else { - GuiComponent_drawString(&super, m_pFont, &m_text, m_xPos + PADDING, m_yPos + textYPos, 0xFFFFFF); + rendered_text = m_text; + text_color = 0xffffff; + scroll_pos = m_scrollPos; } + rendered_text = get_rendered_text(m_pFont, m_width, scroll_pos, rendered_text); + + int textYPos = (m_height - 8) / 2; + GuiComponent_drawString(&super, m_pFont, &rendered_text, m_xPos + PADDING, m_yPos + textYPos, text_color); if (m_bCursorOn) { - int xPos = 5; + int cursor_pos = m_insertHead - m_scrollPos; + if (cursor_pos >= 0 && cursor_pos <= int(rendered_text.length())) { + std::string substr = rendered_text.substr(0, cursor_pos); + int xPos = PADDING + Font_width(m_pFont, &substr); - std::string substr = m_text.substr(0, m_insertHead); - xPos += Font_width(m_pFont, &substr); - - std::string str = "_"; - GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xFFFFFF); + std::string str; + str += CURSOR_CHAR; + GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xffffff); + } } } @@ -198,3 +229,66 @@ bool TextInputBox::clicked(int xPos, int yPos) { return true; } + +void TextInputBox::recalculateScroll() { + // Skip If Size Unset + if (m_width == 0) { + return; + } + // Ensure Cursor Is Visible + bool is_cursor_at_end = m_insertHead == int(m_text.length()); + if (m_scrollPos >= m_insertHead && m_scrollPos > 0) { + // Cursor Is At Scroll Position + // Move Back Scroll As Far As Possible + while (true) { + int test_scroll_pos = m_scrollPos - 1; + std::string rendered_text = m_text; + if (is_cursor_at_end) { + rendered_text += CURSOR_CHAR; + } + rendered_text = get_rendered_text(m_pFont, m_width, test_scroll_pos, rendered_text); + int cursor_pos = m_insertHead - test_scroll_pos; + if (cursor_pos >= int(rendered_text.length())) { + break; + } else { + m_scrollPos = test_scroll_pos; + if (m_scrollPos == 0) { + break; + } + } + } + } else { + // Cursor After Scroll Area + // Increase Scroll So Cursor Is Visible + while (true) { + std::string rendered_text = m_text; + if (is_cursor_at_end) { + rendered_text += CURSOR_CHAR; + } + rendered_text = get_rendered_text(m_pFont, m_width, m_scrollPos, rendered_text); + int cursor_pos = m_insertHead - m_scrollPos; + if (cursor_pos < int(rendered_text.length())) { + break; + } else { + if (m_scrollPos == int(m_text.length())) { + WARN("Text Box Is Too Small"); + break; + } else { + m_scrollPos++; + } + } + } + } +} + +std::string TextInputBox::getText() { + return m_text; +} + +bool TextInputBox::isFocused() { + return m_bFocused; +} + +void TextInputBox::setMaxLength(int max_length) { + m_maxLength = max_length; +} diff --git a/mods/src/touch/touch.cpp b/mods/src/touch/touch.cpp index 67d20f9..d88352e 100644 --- a/mods/src/touch/touch.cpp +++ b/mods/src/touch/touch.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -28,7 +29,7 @@ static int32_t Button_hovered_injection(__attribute__((unused)) Button *button, int32_t button_y2 = button_y1 + button->height; // Check - return x >= button_x1 && x <= button_x2 && y >= button_y1 && y <= button_y2; + return x >= button_x1 && x < button_x2 && y >= button_y1 && y < button_y2; } static void LargeImageButton_render_GuiComponent_drawCenteredString_injection(GuiComponent *component, Font *font, std::string *text, int32_t x, int32_t y, int32_t color) { // Change Color On Hover @@ -40,9 +41,27 @@ static void LargeImageButton_render_GuiComponent_drawCenteredString_injection(Gu GuiComponent_drawCenteredString(component, font, text, x, y, color); } +// Create Button +static int touch_gui = 0; +Button *touch_create_button(int id, std::string text) { + Button *button = nullptr; + if (touch_gui) { + button = (Button *) new Touch_TButton; + } else { + button = new Button; + } + ALLOC_CHECK(button); + if (touch_gui) { + Touch_TButton_constructor((Touch_TButton *) button, id, &text); + } else { + Button_constructor(button, id, &text); + } + return button; +} + // Init void init_touch() { - int touch_gui = feature_has("Full Touch GUI", server_disabled); + touch_gui = feature_has("Full Touch GUI", server_disabled); int touch_buttons = touch_gui; if (touch_gui) { // Main UI diff --git a/scripts/build.mjs b/scripts/build.mjs new file mode 100755 index 0000000..4fadeda --- /dev/null +++ b/scripts/build.mjs @@ -0,0 +1,208 @@ +#!/usr/bin/env node +import * as path from 'node:path'; +import * as url from 'node:url'; +import * as fs from 'node:fs'; +import * as child_process from 'node:child_process'; + +// Logging +const EXIT_FAILURE = 1; +function fail(message) { + console.error(message); + process.exit(EXIT_FAILURE); +} +function err(message) { + fail('ERROR: ' + message); +} +function info(message) { + console.log('INFO: ' + message); +} + +// Enums +function Enum(values) { + for (const value of values) { + this[value] = {name: value.toLowerCase()}; + } +} +Enum.prototype.get = function (name) { + for (const value in this) { + if (value.toLowerCase() === name.toLowerCase()) { + return this[value]; + } + } + return null; +}; +function wrap(obj) { + return new Proxy(obj, { + get(target, property) { + if (property in target) { + return target[property]; + } else { + err('Undefined Value: ' + property); + } + } + }); +} +const PackageTypes = wrap(new Enum([ + 'None', + 'AppImage', + 'Flatpak' +])); +const Variants = wrap(new Enum([ + 'Client', + 'Server' +])); +const Architectures = wrap(new Enum([ + 'AMD64', + 'ARM64', + 'ARMHF', + 'Host' +])); + +// Folders +const __filename = url.fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const root = path.join(__dirname, '..'); +let build = path.join(root, 'build'); +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) { + err('Expecting ' + type); + } + // Read Argument + const arg = process.argv[argIndex++]; + const value = from.get(arg); + if (value === null) { + err(`Invalid ${type}: ${arg}`); + } + // Return + return value; +} +// 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 +if (packageType === PackageTypes.Flatpak && architecture !== Architectures.Host) { + err('Flatpak Builds Do Not Support Custom Toolchains'); +} + +// CMake Build Options +const options = new Map(); + +// Other Arguments +let clean = false; +let install = false; +for (; argIndex < process.argv.length; argIndex++) { + const arg = process.argv[argIndex]; + if (arg.startsWith('-D')) { + // Pass Build Option To CMake + let parsedArg = arg.substring(2); + const split = parsedArg.indexOf('='); + if (split === -1) { + err('Unable To Parse Build Option: ' + arg); + } + const name = parsedArg.substring(0, split); + const value = parsedArg.substring(split + 1); + if (!/^[a-zA-Z_]+$/.test(name) || name.length === 0) { + err('Invalid Build Option Name: ' + name); + } + options.set(name, value); + } else if (arg === '--clean') { + // Remove Existing Build Directory + clean = true; + } else if (arg === '--install') { + // Install To System Instead Of Output Directory + if (packageType === PackageTypes.AppImage) { + err('AppImages Cannot Be Installed'); + } + install = true; + } else { + err('Invalid Argument: ' + arg); + } +} + +// Update Folders +function updateDir(dir) { + if (packageType !== PackageTypes.None) { + dir = path.join(dir, packageType.name); + } + return path.join(dir, variant.name, architecture.name); +} +build = updateDir(build); +let cleanOut = false; +// AppImages Are Placed Directly In ./out +if (packageType !== PackageTypes.AppImage) { + cleanOut = true; + out = updateDir(out); +} + +// Configure Build Options +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) { + options.set('CMAKE_TOOLCHAIN_FILE', path.join(root, 'cmake', 'toolchain', architecture.name + '-toolchain.cmake')); +} else { + options.delete('CMAKE_TOOLCHAIN_FILE'); +} + +// Make Build Directory +function createDir(dir, clean) { + if (clean) { + fs.rmSync(dir, {recursive: true, force: true}); + } + fs.mkdirSync(dir, {recursive: true}); +} +createDir(build, clean); +if (!install) { + createDir(out, cleanOut); +} + +// Run CMake +function run(command) { + try { + info('Running: ' + command.join(' ')); + child_process.execFileSync(command[0], command.slice(1), {cwd: build, stdio: 'inherit'}); + } catch (e) { + err(e); + } +} +const cmake = ['cmake', '-GNinja']; +options.forEach((value, key, map) => { + cmake.push(`-D${key}=${value}`); +}); +cmake.push(root); +run(cmake); + +// Build +run(['cmake', '--build', '.']); + +// Package +if (packageType !== PackageTypes.AppImage) { + if (!install) { + process.env.DESTDIR = out; + } + run(['cmake', '--install', '.']); +} else { + run(['cmake', '--build', '.', '--target', 'package']); + // Copy Generated Files + const files = fs.readdirSync(build); + for (const file of files) { + if (file.includes('.AppImage')) { + info('Copying: ' + file); + const src = path.join(build, file); + const dst = path.join(out, file); + fs.copyFileSync(src, dst); + } + } +} diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index ea3e8a2..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -set -e - -# Variables -MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" - -# Run CMake If Needed -if [ ! -f "build/${MODE}-${ARCH}/build.ninja" ]; then - ./scripts/setup.sh "${MODE}" "${ARCH}" -fi -# Use Build Dir -cd "build/${MODE}-${ARCH}" - -# Create Prefix -if [ -z "${DESTDIR+x}" ]; then - export DESTDIR="$(cd ../../; pwd)/out/${MODE}-${ARCH}" - rm -rf "${DESTDIR}" - mkdir -p "${DESTDIR}" -fi - -# Build -cmake --build . -cmake --install . - -# Exit -cd ../../ diff --git a/scripts/package.sh b/scripts/package.sh deleted file mode 100755 index e00c99a..0000000 --- a/scripts/package.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -set -e - -# Prepare -NAME='minecraft-pi-reborn' -MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" - -# Build -./scripts/setup.sh "${MODE}" "${ARCH}" -DMCPI_IS_APPIMAGE_BUILD=ON -./scripts/build.sh "${MODE}" "${ARCH}" - -# Package -cd "build/${MODE}-${ARCH}" -rm -f *.AppImage* -cmake --build . --target package - -# Copy Output -cp *.AppImage* ../../out diff --git a/scripts/setup.sh b/scripts/setup.sh deleted file mode 100755 index 3bb312e..0000000 --- a/scripts/setup.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -set -e - -# Variables -MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" -shift 2 - -# Verify Mode -if [ "${MODE}" != "client" ] && [ "${MODE}" != "server" ]; then - echo "Invalid Mode: ${MODE}" > /dev/stderr - exit 1 -fi - -# Find Toolchain -toolchain_file="$(pwd)/cmake/toolchain/${ARCH}-toolchain.cmake" -if [ ! -f "${toolchain_file}" ]; then - echo "Invalid Architecture: ${ARCH}" > /dev/stderr - exit 1 -fi - -# Create Build Dir -rm -rf "build/${MODE}-${ARCH}" -mkdir -p "build/${MODE}-${ARCH}" -cd "build/${MODE}-${ARCH}" - -# Server Build -server_mode='OFF' -if [ "${MODE}" = "server" ]; then - server_mode='ON' -fi - -# Build Components -cmake -GNinja -DCMAKE_TOOLCHAIN_FILE="${toolchain_file}" -DMCPI_SERVER_MODE="${server_mode}" "$@" ../../ - -# Exit -cd ../../ diff --git a/scripts/test.sh b/scripts/test.sh index 009388f..8f208fa 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -4,39 +4,30 @@ set -e # Variables MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(dpkg-architecture -qDEB_BUILD_ARCH)" +ARCH='host' # Build -./scripts/setup.sh "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON -./scripts/build.sh "${MODE}" "${ARCH}" +./scripts/build.mjs none "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON # Add To PATH -export PATH="$(pwd)/out/${MODE}-${ARCH}/usr/bin:${PATH}" +export PATH="$(pwd)/out/${MODE}/${ARCH}/usr/bin:${PATH}" # Make Test Directory -rm -rf build/test -mkdir -p build/test +TEST_WORKING_DIR="$(pwd)/.testing-tmp" +rm -rf "${TEST_WORKING_DIR}" +mkdir -p "${TEST_WORKING_DIR}" # Run if [ "${MODE}" = "server" ]; then # Server Test - cd build/test + cd "${TEST_WORKING_DIR}" minecraft-pi-reborn-server --only-generate - cd ../../ else # Client Test export _MCPI_SKIP_ROOT_CHECK=1 - export HOME="$(pwd)/build/test" + export HOME="${TEST_WORKING_DIR}" minecraft-pi-reborn-client --default --no-cache --benchmark - - # Build Example Mods - for project in example-mods/*/; do - cd "${project}" - rm -rf build - mkdir build - cd build - cmake -GNinja .. - cmake --build . - cd ../../../ - done fi + +# Clean Up +rm -rf "${TEST_WORKING_DIR}" diff --git a/symbols/CMakeLists.txt b/symbols/CMakeLists.txt index 7822775..3eb63fb 100644 --- a/symbols/CMakeLists.txt +++ b/symbols/CMakeLists.txt @@ -77,6 +77,7 @@ set(SRC src/level/ServerLevel.def src/level/Dimension.def src/level/MultiPlayerLevel.def + src/level/LevelSummary.def src/item/ItemRenderer.def src/item/ItemInHandRenderer.def src/item/AuxDataTileItem.def @@ -103,6 +104,7 @@ set(SRC src/gui/screens/ProgressScreen.def src/gui/screens/Touch_SelectWorldScreen.def src/gui/screens/PaneCraftingScreen.def + src/gui/screens/ScreenChooser.def src/gui/Font.def src/gui/components/ImageButton.def src/gui/components/OptionButton.def @@ -147,6 +149,7 @@ set(SRC src/misc/Config.def src/misc/Random.def src/misc/Mth.def + src/misc/Util.def src/input/IMoveInput.def src/input/IBuildInput.def src/input/MouseBuildInput.def diff --git a/symbols/src/game/Minecraft.def b/symbols/src/game/Minecraft.def index 637a92f..04707c0 100644 --- a/symbols/src/game/Minecraft.def +++ b/symbols/src/game/Minecraft.def @@ -41,6 +41,7 @@ property int progress = 0xc60; property PerfRenderer *perf_renderer = 0xcbc; property CommandServer *command_server = 0xcc0; property Font *font = 0x16c; +property ScreenChooser screen_chooser = 0x168; // Smooth Lighting static-property bool useAmbientOcclusion = 0x136b90; diff --git a/symbols/src/game/options/Options.def b/symbols/src/game/options/Options.def index f9027e8..556ba56 100644 --- a/symbols/src/game/options/Options.def +++ b/symbols/src/game/options/Options.def @@ -3,6 +3,7 @@ size 0x110; method void initDefaultValue() = 0x18a54; method bool getBooleanValue(Options_Option *option) = 0x1cd74; method void addOptionToSaveOutput(std::vector *data, std::string option, int value) = 0x195e4; +method void save() = 0x1966c; property OptionsFile options_file = 0x10c; property bool fancy_graphics = 0x17; diff --git a/symbols/src/game/options/Options_Option.def b/symbols/src/game/options/Options_Option.def index 230cd17..64eb732 100644 --- a/symbols/src/game/options/Options_Option.def +++ b/symbols/src/game/options/Options_Option.def @@ -1,3 +1,5 @@ static-property Options_Option GRAPHICS = 0x136c2c; static-property Options_Option AMBIENT_OCCLUSION = 0x136c38; static-property Options_Option ANAGLYPH = 0x136c08; +static-property Options_Option DIFFICULTY = 0x136c20; +static-property Options_Option SERVER_VISIBLE = 0x136c68; diff --git a/symbols/src/gui/Gui.def b/symbols/src/gui/Gui.def index dc477cd..f676ea6 100644 --- a/symbols/src/gui/Gui.def +++ b/symbols/src/gui/Gui.def @@ -12,6 +12,7 @@ method void addMessage(std::string *text) = 0x27820; method void getSlotPos(int slot, int *x, int *y) = 0x25548; method void renderSlot(int slot, int x, int y, float alpha) = 0x25cc0; method void renderSlotText(ItemInstance *item, float x, float y, bool finite, bool shadow) = 0x25df8; +method void handleKeyPressed(int key) = 0x25a08; method void renderHearts() = 0x2641c; property Minecraft *minecraft = 0x9f4; diff --git a/symbols/src/gui/components/Button.def b/symbols/src/gui/components/Button.def index cf2bb7b..1b3d078 100644 --- a/symbols/src/gui/components/Button.def +++ b/symbols/src/gui/components/Button.def @@ -9,3 +9,4 @@ property int width = 0x14; property int height = 0x18; property int x = 0xc; property int y = 0x10; +property std::string text = 0x1c; diff --git a/symbols/src/gui/screens/Screen.def b/symbols/src/gui/screens/Screen.def index 8f2ecff..0842c95 100644 --- a/symbols/src/gui/screens/Screen.def +++ b/symbols/src/gui/screens/Screen.def @@ -10,7 +10,7 @@ virtual-method void init() = 0xc; virtual-method void render(int x, int y, float param_1) = 0x8; virtual-method void setupPositions() = 0x10; virtual-method void updateEvents() = 0x14; -virtual-method bool handleBackEvent(bool param_1) = 0x24; +virtual-method bool handleBackEvent(bool do_nothing) = 0x24; virtual-method void tick() = 0x28; virtual-method void removed() = 0x2c; virtual-method void renderBackground() = 0x30; diff --git a/symbols/src/gui/screens/ScreenChooser.def b/symbols/src/gui/screens/ScreenChooser.def new file mode 100644 index 0000000..bb2984b --- /dev/null +++ b/symbols/src/gui/screens/ScreenChooser.def @@ -0,0 +1 @@ +method void setScreen(uint id) = 0x29490; diff --git a/symbols/src/level/LevelStorageSource.def b/symbols/src/level/LevelStorageSource.def index 58e62e6..723f01b 100644 --- a/symbols/src/level/LevelStorageSource.def +++ b/symbols/src/level/LevelStorageSource.def @@ -1 +1,2 @@ virtual-method void deleteLevel(std::string *level_name) = 0x20; +virtual-method void getLevelList(std::vector *level_list) = 0xc; diff --git a/symbols/src/level/LevelSummary.def b/symbols/src/level/LevelSummary.def new file mode 100644 index 0000000..fc82426 --- /dev/null +++ b/symbols/src/level/LevelSummary.def @@ -0,0 +1,7 @@ +size 0x14; + +property std::string folder = 0x0; +property std::string name = 0x4; +property int seed = 0x8; +property int game_mode = 0xc; +property int param_5 = 0x10; diff --git a/symbols/src/misc/Common.def b/symbols/src/misc/Common.def index 1d75668..de81022 100644 --- a/symbols/src/misc/Common.def +++ b/symbols/src/misc/Common.def @@ -6,3 +6,4 @@ static-method void sleepMs(int x) = 0x13cf4; static-method int sdl_key_to_minecraft_key(int sdl_key) = 0x1243c; static-method void anGenBuffers(int count, uint *buffers) = 0x5f28c; static-method int getTimeMs() = 0x13cd4; +static-method int getEpochTimeS() = 0x13d00; diff --git a/symbols/src/misc/PerfRenderer.def b/symbols/src/misc/PerfRenderer.def index eea41e6..9af6b06 100644 --- a/symbols/src/misc/PerfRenderer.def +++ b/symbols/src/misc/PerfRenderer.def @@ -1 +1,2 @@ -method void debugFpsMeterKeyPress(int key) = 0x79118; \ No newline at end of file +method void debugFpsMeterKeyPress(int key) = 0x79118; +method void renderFpsMeter(float param_1) = 0x79280; diff --git a/symbols/src/misc/Strings.def b/symbols/src/misc/Strings.def index f83046f..10898c0 100644 --- a/symbols/src/misc/Strings.def +++ b/symbols/src/misc/Strings.def @@ -7,4 +7,6 @@ static-property char *options_txt_fopen_mode_when_loading = 0x19d24; // w static-property char **feedback_vibration_options_txt_name_1 = 0x198a0; // feedback_vibration static-property char **feedback_vibration_options_txt_name_2 = 0x194bc; // feedback_vibration static-property char **gfx_lowquality_options_txt_name = 0x194c4; // gfx_lowquality -static-property char *classic_create_button_text = 0x39bec; // Create \ No newline at end of file +static-property char *classic_create_button_text = 0x39bec; // Create +static-property-array char creative_mode_description = 0x104492; // Unlimited resources and flying +static-property-array char survival_mode_description = 0x104470; // Mobs, health and gather resources diff --git a/symbols/src/misc/Util.def b/symbols/src/misc/Util.def new file mode 100644 index 0000000..4e6c6d4 --- /dev/null +++ b/symbols/src/misc/Util.def @@ -0,0 +1,3 @@ +static-method std::string stringTrim(std::string *str) = 0x77c40; +static-method int hashCode(std::string *str) = 0x77a50; +static-method std::string *stringReplace(std::string *str, std::string *what, std::string *with, int param_1) = 0x779f0; diff --git a/symbols/src/network/packet/ChatPacket.def b/symbols/src/network/packet/ChatPacket.def index aed55cc..35b8833 100644 --- a/symbols/src/network/packet/ChatPacket.def +++ b/symbols/src/network/packet/ChatPacket.def @@ -1,3 +1,5 @@ extends Packet; +vtable 0x108a98; + property char *message = 0xc;