2022-06-11 01:59:57 +00:00
|
|
|
// Config Needs To Load First
|
|
|
|
#include <libreborn/libreborn.h>
|
|
|
|
|
|
|
|
// Game Mode UI Code Is Useless In Headless Mode
|
2022-04-28 03:38:30 +00:00
|
|
|
#ifndef MCPI_SERVER_MODE
|
|
|
|
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <cstring>
|
|
|
|
#include <ctime>
|
|
|
|
#include <string>
|
|
|
|
#include <stdexcept>
|
|
|
|
|
|
|
|
#include <symbols/minecraft.h>
|
|
|
|
#include <media-layer/core.h>
|
|
|
|
|
2022-06-25 21:30:08 +00:00
|
|
|
#include "game-mode-internal.h"
|
2022-04-28 03:38:30 +00:00
|
|
|
|
|
|
|
// Run Command
|
|
|
|
static char *run_command_proper(const char *command[], bool allow_empty) {
|
|
|
|
// Run
|
|
|
|
int return_code;
|
|
|
|
char *output = run_command(command, &return_code);
|
|
|
|
|
|
|
|
// Handle Message
|
|
|
|
if (output != NULL) {
|
|
|
|
// Check Return Code
|
2022-05-15 17:51:28 +00:00
|
|
|
if (is_exit_status_success(return_code)) {
|
2022-04-28 03:38:30 +00:00
|
|
|
// 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
|
2022-05-15 17:51:28 +00:00
|
|
|
return !is_exit_status_success(return_code) ? NULL : run_command_proper(command, allow_empty);
|
2022-04-28 03:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
2022-06-10 01:31:40 +00:00
|
|
|
"--name", MCPI_APP_TITLE,
|
2022-04-28 03:38:30 +00:00
|
|
|
"--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,
|
2022-06-10 01:31:40 +00:00
|
|
|
"--name", MCPI_APP_TITLE,
|
2022-04-28 03:38:30 +00:00
|
|
|
"--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,
|
2022-06-10 01:31:40 +00:00
|
|
|
"--name", MCPI_APP_TITLE,
|
2022-04-28 03:38:30 +00:00
|
|
|
"--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
|
2022-04-29 00:54:00 +00:00
|
|
|
static void create_world(unsigned char *host_screen, std::string folder_name) {
|
2022-04-28 03:38:30 +00:00
|
|
|
// 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
|
2022-04-29 00:54:00 +00:00
|
|
|
std::string world_name = (char *) create_world_state.name;
|
|
|
|
(*Minecraft_selectLevel)(minecraft, folder_name, world_name, settings);
|
2022-04-28 03:38:30 +00:00
|
|
|
|
|
|
|
// 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
|