From 46a53ba3cf57c2ef3579f08b2956d07d894f2aba Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Wed, 27 Apr 2022 23:38:30 -0400 Subject: [PATCH] New Create World Dialog --- dependencies/zenity/src | 2 +- .../available-feature-flags | 1 + launcher/src/client/launcher.cpp | 13 +- mods/CMakeLists.txt | 12 +- mods/src/chat/chat.cpp | 9 +- mods/src/chat/ui.c | 18 +- mods/src/game-mode/game-mode.c | 7 +- mods/src/game-mode/game-mode.cpp | 59 ---- mods/src/game-mode/game-mode.h | 4 +- mods/src/game-mode/ui.cpp | 305 ++++++++++++++++++ mods/src/server/server.cpp | 6 +- symbols/include/symbols/minecraft.h | 2 +- 12 files changed, 352 insertions(+), 86 deletions(-) delete mode 100644 mods/src/game-mode/game-mode.cpp create mode 100644 mods/src/game-mode/ui.cpp diff --git a/dependencies/zenity/src b/dependencies/zenity/src index cdcc55e55..3dbcdbb34 160000 --- a/dependencies/zenity/src +++ b/dependencies/zenity/src @@ -1 +1 @@ -Subproject commit cdcc55e55f08956f6c5a5f3d63fce4614c75e8d4 +Subproject commit 3dbcdbb34a0c92297155de48ed491ea3e587b208 diff --git a/launcher/data/client/lib/minecraft-pi-reborn-client/available-feature-flags b/launcher/data/client/lib/minecraft-pi-reborn-client/available-feature-flags index f7224e1e8..8a63f3310 100644 --- a/launcher/data/client/lib/minecraft-pi-reborn-client/available-feature-flags +++ b/launcher/data/client/lib/minecraft-pi-reborn-client/available-feature-flags @@ -36,3 +36,4 @@ TRUE Fix Pause Menu TRUE Improved Title Background TRUE Force Touch GUI Button Behavior TRUE Improved Button Hover Behavior +TRUE Implement Create World Dialog diff --git a/launcher/src/client/launcher.cpp b/launcher/src/client/launcher.cpp index e68844c50..405c3e689 100644 --- a/launcher/src/client/launcher.cpp +++ b/launcher/src/client/launcher.cpp @@ -123,6 +123,7 @@ static void run_zenity_and_set_env(const char *env_name, std::vector command; command.push_back("--entry"); command.push_back("--text"); - command.push_back("Minecraft Username:"); + command.push_back("Enter Minecraft Username:"); command.push_back("--entry-text"); command.push_back("StevePi"); // Run diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index 6ffe03f31..bd2cb27be 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -19,18 +19,23 @@ add_library(version SHARED src/version/version.cpp) target_link_libraries(version reborn-patch symbols) add_library(chat SHARED src/chat/chat.cpp src/chat/ui.c) -target_link_libraries(chat reborn-patch symbols feature pthread) +target_link_libraries(chat reborn-patch symbols feature) add_library(creative SHARED src/creative/creative.cpp) target_link_libraries(creative reborn-patch symbols feature) +add_library(game-mode SHARED src/game-mode/game-mode.c src/game-mode/ui.cpp) +target_link_libraries(game-mode reborn-patch symbols feature) + if(MCPI_SERVER_MODE) add_library(server SHARED src/server/server.cpp src/server/server_properties.cpp) target_link_libraries(server reborn-patch symbols feature home misc compat dl media-layer-core pthread) else() target_link_libraries(compat input sign chat home dl) - target_link_libraries(chat input) + target_link_libraries(chat input media-layer-core pthread) + + target_link_libraries(game-mode pthread media-layer-core) add_library(multiplayer SHARED src/multiplayer/multiplayer.cpp) target_link_libraries(multiplayer reborn-patch symbols home feature) @@ -65,9 +70,6 @@ else() endif() endif() -add_library(game-mode SHARED src/game-mode/game-mode.c src/game-mode/game-mode.cpp) -target_link_libraries(game-mode reborn-patch symbols feature) - add_library(death SHARED src/death/death.cpp) target_link_libraries(death reborn-patch symbols feature) diff --git a/mods/src/chat/chat.cpp b/mods/src/chat/chat.cpp index 17c5719f7..5ade22e9e 100644 --- a/mods/src/chat/chat.cpp +++ b/mods/src/chat/chat.cpp @@ -2,10 +2,15 @@ #include #include #include +#ifndef MCPI_SERVER_MODE #include +#endif #include #include +#ifndef MCPI_SERVER_MODE +#include +#endif #include "../init/init.h" #include "../feature/feature.h" @@ -103,8 +108,8 @@ static void send_queued_messages(unsigned char *minecraft) { // If Message Was Submitted, No Other Chat Windows Are Open, And The Game Is Not Paused, Then Re-Lock Cursor unsigned int new_chat_counter = chat_get_counter(); if (old_chat_counter > new_chat_counter && new_chat_counter == 0 && (*(unsigned char **) (minecraft + Minecraft_screen_property_offset)) == NULL) { - // Grab Mouse - input_set_mouse_grab_state(-1); + // Unlock UI + media_set_interactable(1); } old_chat_counter = new_chat_counter; // Loop diff --git a/mods/src/chat/ui.c b/mods/src/chat/ui.c index c303f4dc6..0cd6b7b7f 100644 --- a/mods/src/chat/ui.c +++ b/mods/src/chat/ui.c @@ -5,9 +5,9 @@ #include #include +#include #include "chat.h" -#include "../input/input.h" // Run Command static char *run_command_proper(const char *command[], int *return_code) { @@ -21,16 +21,24 @@ static char *run_command_proper(const char *command[], int *return_code) { // Count Chat Windows static pthread_mutex_t chat_counter_lock = PTHREAD_MUTEX_INITIALIZER; -static unsigned int chat_counter = 0; +static volatile unsigned int chat_counter = 0; unsigned int chat_get_counter() { return chat_counter; } // Chat Thread +#define DIALOG_TITLE "Chat" static void *chat_thread(__attribute__((unused)) void *nop) { // Open int return_code; - const char *command[] = {"zenity", "--title", "Chat", "--class", GUI_TITLE, "--entry", "--text", "Enter Chat Message:", NULL}; + const char *command[] = { + "zenity", + "--title", DIALOG_TITLE, + "--class", GUI_TITLE, + "--entry", + "--text", "Enter Chat Message:", + NULL + }; char *output = run_command_proper(command, &return_code); // Handle Message if (output != NULL) { @@ -62,8 +70,8 @@ static void *chat_thread(__attribute__((unused)) void *nop) { // Create Chat Thead void chat_open() { if (_chat_enabled) { - // Release Mouse - input_set_mouse_grab_state(1); + // Lock UI + media_set_interactable(0); // Update Counter pthread_mutex_lock(&chat_counter_lock); diff --git a/mods/src/game-mode/game-mode.c b/mods/src/game-mode/game-mode.c index 04d4b592c..17abb1ab2 100644 --- a/mods/src/game-mode/game-mode.c +++ b/mods/src/game-mode/game-mode.c @@ -62,9 +62,12 @@ void init_game_mode() { // Disable CreatorMode-Specific API Features (Polling Block Hits) In SurvivalMode, This Is Preferable To Crashing overwrite_calls((void *) Minecraft_getCreator, (void *) Minecraft_getCreator_injection); + } - // Init C++ - _init_game_mode_cpp(); + // Create World Dialog + if (feature_has("Implement Create World Dialog", server_disabled)) { + // Init UI + _init_game_mode_ui(); } // Allow Joining Survival Servers diff --git a/mods/src/game-mode/game-mode.cpp b/mods/src/game-mode/game-mode.cpp deleted file mode 100644 index 0fa98a929..000000000 --- a/mods/src/game-mode/game-mode.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include - -#include "game-mode.h" - -#include - -// Get Minecraft From Screen -static unsigned char *get_minecraft_from_screen(unsigned char *screen) { - return *(unsigned char **) (screen + Screen_minecraft_property_offset); -} - -// Redirect Create World Button To SimpleLevelChooseScreen -#define WORLD_NAME "world" -static void SelectWorldScreen_tick_injection(unsigned char *screen) { - bool create_world = *(bool *) (screen + SelectWorldScreen_should_create_world_property_offset); - if (create_world) { - // Get New World Name - std::string new_name = (*SelectWorldScreen_getUniqueLevelName)(screen, WORLD_NAME); - // Create SimpleLevelChooseScreen - unsigned char *new_screen = (unsigned char *) ::operator new(SIMPLE_LEVEL_CHOOSE_SCREEN_SIZE); - ALLOC_CHECK(new_screen); - (*SimpleChooseLevelScreen)(new_screen, new_name); - // Set Screen - unsigned char *minecraft = get_minecraft_from_screen(screen); - (*Minecraft_setScreen)(minecraft, new_screen); - // Finish - *(bool *) (screen + SelectWorldScreen_world_created_property_offset) = true; - } else { - (*SelectWorldScreen_tick)(screen); - } -} -static void Touch_SelectWorldScreen_tick_injection(unsigned char *screen) { - bool create_world = *(bool *) (screen + Touch_SelectWorldScreen_should_create_world_property_offset); - if (create_world) { - // Get New World Name - std::string new_name = (*Touch_SelectWorldScreen_getUniqueLevelName)(screen, WORLD_NAME); - // Create SimpleLevelChooseScreen - unsigned char *new_screen = (unsigned char *) ::operator new(SIMPLE_LEVEL_CHOOSE_SCREEN_SIZE); - ALLOC_CHECK(new_screen); - (*SimpleChooseLevelScreen)(new_screen, new_name); - // Set Screen - unsigned char *minecraft = get_minecraft_from_screen(screen); - (*Minecraft_setScreen)(minecraft, new_screen); - // Finish - *(bool *) (screen + Touch_SelectWorldScreen_world_created_property_offset) = true; - } else { - (*Touch_SelectWorldScreen_tick)(screen); - } -} - -void _init_game_mode_cpp() { - // Hijack Create World Button - patch_address(SelectWorldScreen_tick_vtable_addr, (void *) SelectWorldScreen_tick_injection); - patch_address(Touch_SelectWorldScreen_tick_vtable_addr, (void *) Touch_SelectWorldScreen_tick_injection); - // Make The SimpleChooseLevelScreen Back Button Go To SelectWorldScreen Instead Of StartMenuScreen - unsigned char simple_choose_level_screen_back_button_patch[4] = {0x05, 0x10, 0xa0, 0xe3}; // "mov r1, #0x5" - patch((void *) 0x31144, simple_choose_level_screen_back_button_patch); - patch((void *) 0x3134c, simple_choose_level_screen_back_button_patch); -} diff --git a/mods/src/game-mode/game-mode.h b/mods/src/game-mode/game-mode.h index 4538ec3ce..237ec26d6 100644 --- a/mods/src/game-mode/game-mode.h +++ b/mods/src/game-mode/game-mode.h @@ -4,8 +4,8 @@ extern "C" { #endif -__attribute__((visibility("internal"))) void _init_game_mode_cpp(); +__attribute__((visibility("internal"))) void _init_game_mode_ui(); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/mods/src/game-mode/ui.cpp b/mods/src/game-mode/ui.cpp new file mode 100644 index 000000000..b7462a6f7 --- /dev/null +++ b/mods/src/game-mode/ui.cpp @@ -0,0 +1,305 @@ +#ifndef MCPI_SERVER_MODE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "game-mode.h" + +// Run Command +static char *run_command_proper(const char *command[], bool allow_empty) { + // Prepare Environment + RESET_ENVIRONMENTAL_VARIABLE("LD_LIBRARY_PATH"); + RESET_ENVIRONMENTAL_VARIABLE("LD_PRELOAD"); + + // Run + int return_code; + char *output = run_command(command, &return_code); + + // Handle Message + if (output != NULL) { + // Check Return Code + if (return_code == 0) { + // 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 return_code != 0 ? NULL : run_command_proper(command, allow_empty); +} + +// 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; +typedef struct { + 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; +} create_world_state_t; +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 + { + // World Name + char *world_name = NULL; + { + // Open + const char *command[] = { + "zenity", + "--title", DIALOG_TITLE, + "--class", GUI_TITLE, + "--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, + "--class", GUI_TITLE, + "--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; + } + } + + // Seed + int32_t seed = 0; + get_seed: + { + // Open + const char *command[] = { + "zenity", + "--title", DIALOG_TITLE, + "--class", GUI_TITLE, + "--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; + } + } + + // Update State + pthread_mutex_lock(&create_world_state_lock); + reset_create_world_state(); + create_world_state.dialog_state = DIALOG_SUCCESS; + create_world_state.name = world_name; + create_world_state.game_mode = game_mode; + create_world_state.seed = seed; + pthread_mutex_unlock(&create_world_state_lock); + // Return + return NULL; + } + + fail: + // Update State + pthread_mutex_lock(&create_world_state_lock); + reset_create_world_state(); + pthread_mutex_unlock(&create_world_state_lock); + // Return + return NULL; +} + +// 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); +} + +// Get Minecraft From Screen +static unsigned char *get_minecraft_from_screen(unsigned char *screen) { + return *(unsigned char **) (screen + Screen_minecraft_property_offset); +} + +// Create World +static void create_world(unsigned char *host_screen, std::string world_name) { + // Get Minecraft + unsigned char *minecraft = get_minecraft_from_screen(host_screen); + + // Settings + LevelSettings settings; + settings.game_type = create_world_state.game_mode; + settings.seed = create_world_state.seed; + + // Create World + (*Minecraft_selectLevel)(minecraft, world_name, world_name, settings); + + // Multiplayer + (*Minecraft_hostMultiplayer)(minecraft, 19132); + + // Open ProgressScreen + unsigned char *screen = (unsigned char *) ::operator new(PROGRESS_SCREEN_SIZE); + ALLOC_CHECK(screen); + screen = (*ProgressScreen)(screen); + (*Minecraft_setScreen)(minecraft, screen); + + // Reset + reset_create_world_state(); +} + +// Redirect Create World Button +#define create_SelectWorldScreen_tick_injection(prefix) \ + static void prefix##SelectWorldScreen_tick_injection(unsigned char *screen) { \ + /* Lock */ \ + pthread_mutex_lock(&create_world_state_lock); \ + \ + bool *should_create_world = (bool *) (screen + prefix##SelectWorldScreen_should_create_world_property_offset); \ + if (*should_create_world) { \ + /* Check State */ \ + if (create_world_state.dialog_state == DIALOG_CLOSED) { \ + /* Open Dialog */ \ + open_create_world(); \ + } \ + \ + /* Finish */ \ + *should_create_world = false; \ + } else { \ + /* Call Original Method */ \ + (*prefix##SelectWorldScreen_tick)(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, 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_) + +// Init +void _init_game_mode_ui() { + // Hijack Create World Button + patch_address(SelectWorldScreen_tick_vtable_addr, (void *) SelectWorldScreen_tick_injection); + patch_address(Touch_SelectWorldScreen_tick_vtable_addr, (void *) Touch_SelectWorldScreen_tick_injection); +} + +#else +void _init_game_mode_ui() { +} +#endif diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index c9c1a390c..2f9dbd053 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -91,10 +91,10 @@ static void start_world(unsigned char *minecraft) { } // Open ProgressScreen - void *screen = ::operator new(PROGRESS_SCREEN_SIZE); + unsigned char *screen = (unsigned char *) ::operator new(PROGRESS_SCREEN_SIZE); ALLOC_CHECK(screen); - screen = (*ProgressScreen)((unsigned char *) screen); - (*Minecraft_setScreen)(minecraft, (unsigned char *) screen); + screen = (*ProgressScreen)(screen); + (*Minecraft_setScreen)(minecraft, screen); } // Check If Running In Whitelist Mode diff --git a/symbols/include/symbols/minecraft.h b/symbols/include/symbols/minecraft.h index 3409757cf..53f4c97f9 100644 --- a/symbols/include/symbols/minecraft.h +++ b/symbols/include/symbols/minecraft.h @@ -448,7 +448,7 @@ static void *TextEditScreen_updateEvents_vtable_addr = (void *) 0x10531c; #define PROGRESS_SCREEN_SIZE 0x4c -typedef void *(*ProgressScreen_t)(unsigned char *obj); +typedef unsigned char *(*ProgressScreen_t)(unsigned char *obj); static ProgressScreen_t ProgressScreen = (ProgressScreen_t) 0x37044; // OptionsScreen