The Great Unification

This commit is contained in:
TheBrokenRail 2024-06-15 08:52:15 -04:00
parent 460cd38ddf
commit 38d7dda1a7
71 changed files with 1049 additions and 1578 deletions

View File

@ -14,9 +14,6 @@ jobs:
strategy:
fail-fast: false
matrix:
mode:
- Client
- Server
arch:
- AMD64
- ARM64
@ -31,40 +28,19 @@ jobs:
submodules: true
# Dependencies
- name: Install Dependencies
run: ./scripts/install-dependencies.sh ${{ matrix.arch }}
run: ./scripts/install-dependencies.sh build ${{ matrix.arch }}
# Build
- name: Build
run: ./scripts/build.mjs appimage ${{ matrix.mode }} ${{ matrix.arch }}
run: ./scripts/build.mjs appimage ${{ matrix.arch }}
- name: Upload Artifacts
uses: christopherhx/gitea-upload-artifact@v4
with:
name: ${{ matrix.mode }} (${{ matrix.arch }})
name: ${{ matrix.arch }}
path: ./out/*.AppImage*
if-no-files-found: error
# Test Project
test:
strategy:
fail-fast: false
matrix:
mode:
- Client
- Server
name: Test
runs-on: ubuntu-latest
container: node:lts-bullseye
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
submodules: true
# Dependencies
- name: Install Dependencies
run: ./scripts/install-dependencies.sh
# Test
- name: Test
run: ./scripts/test.sh ${{ matrix.mode }}
# Test Project On ARM
rpi-test:
needs: build
strategy:
fail-fast: false
matrix:
@ -72,10 +48,11 @@ jobs:
- Client
- Server
arch:
- AMD64
- ARM64
- ARMHF
name: Raspberry Pi Test
runs-on: raspberry-pi
name: Test
runs-on: ${{ startsWith(matrix.arch, 'ARM') && 'raspberry-pi' || 'ubuntu-latest' }}
container: node:lts-bullseye
steps:
- name: Checkout Repository
@ -84,12 +61,19 @@ jobs:
submodules: true
# Dependencies
- name: Install Dependencies
run: ./scripts/install-dependencies.sh ${{ matrix.arch }}
run: ./scripts/install-dependencies.sh test ${{ matrix.arch }}
# Download Artifact
- name: Download Artifact
uses: christopherhx/gitea-download-artifact@v4
with:
name: ${{ matrix.arch }}
path: out
# Test
- name: Test
run: ./scripts/test.sh ${{ matrix.mode }}
run: ./scripts/test.sh ${{ matrix.mode }} ${{ matrix.arch }}
# Example Mods
example-mods:
needs: build
name: Build Example Mods
runs-on: ubuntu-latest
container: node:lts-bullseye
@ -100,16 +84,18 @@ jobs:
submodules: true
# Dependencies
- name: Install Dependencies
run: ./scripts/install-dependencies.sh
- name: Install ARM Toolchain
run: apt-get install --no-install-recommends -y g++-arm-linux-gnueabihf gcc-arm-linux-gnueabihf
# Build SDK
- name: Build SDK
run: ./scripts/install-dependencies.sh example_mods amd64
# SDK
- name: Download SDK
uses: christopherhx/gitea-download-artifact@v4
with:
name: AMD64
path: out
- name: Extract SDK
run: |
./scripts/build.mjs none client host
export _MCPI_SKIP_ROOT_CHECK=1
export DISPLAY=
./out/client/host/usr/bin/minecraft-pi-reborn-client --copy-sdk
./scripts/fix-appimage-for-docker.sh ./out/*.AppImage
chmod +x ./out/*.AppImage
./out/*.AppImage --copy-sdk
# Build Example Mods
- name: Build Example Mods
run: ./example-mods/build.sh
@ -122,7 +108,10 @@ jobs:
# Create Release
release:
if: startsWith(github.ref, 'refs/tags/')
needs: build
needs:
- build
- test
- example-mods
name: Release
runs-on: ubuntu-latest
container: node:lts-bullseye

View File

@ -6,10 +6,6 @@ if(MCPI_IS_APPIMAGE_BUILD AND MCPI_IS_FLATPAK_BUILD)
message(FATAL_ERROR "Invalid Build Configuration")
endif()
# Server/Headless Builds
mcpi_option(SERVER_MODE "Server Mode" BOOL FALSE)
mcpi_option(HEADLESS_MODE "Headless Mode" BOOL "${MCPI_SERVER_MODE}")
# Prebuilt ARMHF Toolchain
if(BUILD_NATIVE_COMPONENTS)
set(MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN FALSE)
@ -22,16 +18,12 @@ if(BUILD_NATIVE_COMPONENTS)
endif()
# Media Layer
if(NOT MCPI_HEADLESS_MODE)
set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE FALSE)
if(BUILD_NATIVE_COMPONENTS AND NOT IS_ARM_TARGETING)
set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE TRUE)
endif()
mcpi_option(USE_MEDIA_LAYER_TRAMPOLINE "Whether To Enable The Media Layer Trampoline" BOOL "${DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE}")
mcpi_option(USE_GLES1_COMPATIBILITY_LAYER "Whether To Enable The GLESv1_CM Compatibility Layer" BOOL TRUE)
else()
set(MCPI_USE_MEDIA_LAYER_TRAMPOLINE FALSE)
set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE FALSE)
if(BUILD_NATIVE_COMPONENTS AND NOT IS_ARM_TARGETING)
set(DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE TRUE)
endif()
mcpi_option(USE_MEDIA_LAYER_TRAMPOLINE "Whether To Enable The Media Layer Trampoline" BOOL "${DEFAULT_USE_MEDIA_LAYER_TRAMPOLINE}")
mcpi_option(USE_GLES1_COMPATIBILITY_LAYER "Whether To Enable The GLESv1_CM Compatibility Layer" BOOL TRUE)
if(MCPI_USE_MEDIA_LAYER_TRAMPOLINE)
set(BUILD_MEDIA_LAYER_CORE "${BUILD_NATIVE_COMPONENTS}")
else()
@ -40,30 +32,12 @@ endif()
# Specify Variant Name
set(MCPI_VARIANT_NAME "minecraft-pi-reborn")
if(MCPI_SERVER_MODE)
string(APPEND MCPI_VARIANT_NAME "-server")
else()
string(APPEND MCPI_VARIANT_NAME "-client")
endif()
# App ID
set(DEFAULT_APP_ID "com.thebrokenrail.MCPIReborn")
if(MCPI_SERVER_MODE)
string(APPEND DEFAULT_APP_ID "Server")
else()
string(APPEND DEFAULT_APP_ID "Client")
endif()
mcpi_option(APP_ID "App ID" STRING "${DEFAULT_APP_ID}")
mcpi_option(APP_ID "App ID" STRING "com.thebrokenrail.MCPIReborn")
# App Title
mcpi_option(APP_BASE_TITLE "Base App Title" STRING "Minecraft: Pi Edition: Reborn")
set(DEFAULT_APP_TITLE "${MCPI_APP_BASE_TITLE}")
if(MCPI_SERVER_MODE)
string(APPEND DEFAULT_APP_TITLE " (Server)")
else()
string(APPEND DEFAULT_APP_TITLE " (Client)")
endif()
mcpi_option(APP_TITLE "App Title" STRING "${DEFAULT_APP_TITLE}")
mcpi_option(APP_TITLE "App Title" STRING "Minecraft: Pi Edition: Reborn")
# Skin Server
mcpi_option(SKIN_SERVER "Skin Server" STRING "https://raw.githubusercontent.com/MCPI-Revival/Skins/data")

View File

@ -1,7 +1,7 @@
project(dependencies)
# stb_image
if(BUILD_ARM_COMPONENTS AND NOT MCPI_HEADLESS_MODE)
if(BUILD_ARM_COMPONENTS)
add_subdirectory(stb_image)
endif()
# Minecraft: Pi Edition
@ -9,21 +9,21 @@ if(BUILD_ARM_COMPONENTS AND NOT MCPI_OPEN_SOURCE_ONLY)
add_subdirectory(minecraft-pi)
endif()
# Zenity (Minimal Build)
if(BUILD_NATIVE_COMPONENTS AND NOT MCPI_SERVER_MODE)
if(BUILD_NATIVE_COMPONENTS)
add_subdirectory(zenity)
endif()
# LIEF
if(BUILD_NATIVE_COMPONENTS OR (BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE))
if(BUILD_NATIVE_COMPONENTS OR BUILD_MEDIA_LAYER_CORE)
add_subdirectory(LIEF)
endif()
# Extra Runtime
add_subdirectory(runtime)
# GLFW
if(BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE)
if(BUILD_MEDIA_LAYER_CORE)
add_subdirectory(glfw)
endif()
# GLES Compatibility Layer
if(BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE AND MCPI_USE_GLES1_COMPATIBILITY_LAYER)
if(BUILD_MEDIA_LAYER_CORE AND MCPI_USE_GLES1_COMPATIBILITY_LAYER)
add_subdirectory(gles-compatibility-layer)
endif()
# UTF8-CPP

View File

@ -17,13 +17,5 @@ install(
DESTINATION "${MCPI_INSTALL_DIR}/game"
USE_SOURCE_PERMISSIONS
REGEX "api" EXCLUDE
REGEX "data" EXCLUDE
)
if(NOT MCPI_HEADLESS_MODE)
install(
DIRECTORY "${minecraft-pi_SOURCE_DIR}/data/"
DESTINATION "${MCPI_INSTALL_DIR}/game/data"
USE_SOURCE_PERMISSIONS
)
endif()
install_symlink("game/minecraft-pi" "${MCPI_INSTALL_DIR}/minecraft-pi")

@ -1 +1 @@
Subproject commit 790e7918b1d63102b7a7f39dc1db006b2a5abf48
Subproject commit aa874884072a700750956e241c7a1ce91dbfa74c

View File

@ -32,6 +32,8 @@
* Fix Furnace Visual Bug When Using Lava Bucket As Fuel
* Add Splash Text To Start Screen
* `overwrite_calls` Now Scans VTables
* Unify Server/Client Builds
* Use `|` For Separator In `servers.txt` Instead Of `:`
**2.5.3**
* Add `Replace Block Highlight With Outline` Feature Flag (Enabled By Default)

View File

@ -4,11 +4,11 @@ The dedicated server is a version of Minecraft: Pi Edition modified to run in a
This server is also compatible with MCPE Alpha v0.6.1[^1].
## Setup
To use, install and run the `minecraft-pi-reborn-server` AppImage. It will generate the world and `server.properties` in the current directory.
To use, run the normal AppImage with the `--server` argument. It will generate the world and `server.properties` in the current directory.
## Server Limitations
* Player data is not saved because of limitations with MCPE LAN worlds
* An easy workaround is to place your inventory in a chest before logging off
* Survival Mode servers are incompatible with unmodded MCPI
* Survival Mode servers are incompatible with un-modded MCPI
[^1]: The exception to this is buckets, those will crash MCPE players.
[^1]: The exception to this is buckets and other modded items, those will crash MCPE players.

View File

@ -27,7 +27,7 @@ The AppImage requires Debian Bullseye or higher. This is equivalent to Ubuntu 20
It also requires some additional packages. To install them, run:
```sh
sudo apt install -y libfuse2 libgtk-3-0 libopenal1
sudo apt install -y libfuse2 libgtk-3-0 libopenal1 libglib2.0-0
```
</details>

View File

@ -1,19 +1,11 @@
# Example Mods
This is an example of a mod that can be built using the modding SDK.
These are example mods that can be built using the modding SDK.
* **Expanded Creative Mod**: This specific mod adds even more items and blocks to the Creative Inventory. It was originally by [@Bigjango13](https://github.com/bigjango13).
* **Chat Commands Mod**: This specific mod makes an chat message starting with a ``/`` handled by the MCPI API.
* **Chat Commands Mod**: This specific mod makes a chat message starting with `/` handled by the MCPI API.
* **Recipes Mod**: This specific mod demos custom recipes.
## The SDK
The modding SDK is a collection of exported CMake targets that allows anyone to create their own MCPI mod!
The SDK is copied to ``~/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake`` whenever MCPI-Reborn is started.
## How do I use this?
```sh
mkdir build
cd build
cmake ..
cp libexpanded-creative.so ~/.minecraft-pi/mods
```
The SDK is copied to `~/.minecraft-pi/sdk` whenever MCPI-Reborn is started.

View File

@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_PROCESSOR "arm")
project(chat-commands)
# Include SDK
include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake")
include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn/sdk/sdk.cmake")
# Build
add_library(chat-commands SHARED chat-commands.cpp)

View File

@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_PROCESSOR "arm")
project(expanded-creative)
# Include SDK
include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake")
include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn/sdk/sdk.cmake")
# Build
add_library(expanded-creative SHARED expanded-creative.cpp)

View File

@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_PROCESSOR "arm")
project(recipes)
# Include SDK
include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn-client/sdk/sdk.cmake")
include("$ENV{HOME}/.minecraft-pi/sdk/lib/minecraft-pi-reborn/sdk/sdk.cmake")
# Build
add_library(recipes SHARED recipes.cpp)

View File

@ -1,21 +1,17 @@
project(images)
# Title Background
if(NOT MCPI_HEADLESS_MODE)
install(
FILES "background.png"
DESTINATION "${MCPI_INSTALL_DIR}/data/images/gui"
RENAME "titleBG.png"
)
endif()
install(
FILES "background.png"
DESTINATION "${MCPI_INSTALL_DIR}/data/images/gui"
RENAME "titleBG.png"
)
# Chest Model
if(NOT MCPI_HEADLESS_MODE)
install(
FILES "chest.png"
DESTINATION "${MCPI_INSTALL_DIR}/data/images/item"
)
endif()
install(
FILES "chest.png"
DESTINATION "${MCPI_INSTALL_DIR}/data/images/item"
)
# Icon
install(

View File

@ -10,15 +10,11 @@ add_executable(launcher
src/mods.cpp
src/options/parser.cpp
src/main.cpp
src/client/configuration.cpp
src/client/cache.cpp
src/client/available-feature-flags # Show In IDE
)
if(NOT MCPI_SERVER_MODE)
embed_resource(launcher src/client/available-feature-flags)
target_sources(launcher PRIVATE
src/client/configuration.cpp
src/client/cache.cpp
src/client/available-feature-flags # Show In IDE
)
endif()
embed_resource(launcher src/client/available-feature-flags)
target_link_libraries(launcher reborn-util LIB_LIEF trampoline-headers)
# RPath
set_target_properties(launcher PROPERTIES INSTALL_RPATH "$ORIGIN/lib/native")
@ -38,18 +34,10 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop"
"Type=Application\n"
"Categories=Game;\n"
)
if(MCPI_HEADLESS_MODE)
file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop"
"Terminal=true\n"
"NoDisplay=true\n"
)
else()
file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop"
"Terminal=false\n"
"StartupNotify=false\n"
"StartupWMClass=${MCPI_APP_ID}\n"
)
endif()
file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop"
"Terminal=true\n"
"NoDisplay=true\n"
)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/launcher.desktop"
DESTINATION "${MCPI_SHARE_DIR}/applications"
@ -100,17 +88,11 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml"
" <releases>\n"
" <release version=\"${MCPI_VERSION}\" date=\"${MCPI_VERSION_DATE}\"></release>\n"
" </releases>\n"
)
if(NOT MCPI_HEADLESS_MODE)
file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml"
" <screenshots>\n"
" <screenshot type=\"default\">\n"
" <image>https://gitea.thebrokenrail.com/TheBrokenRail/minecraft-pi-reborn/raw/branch/master/images/start.png</image>\n"
" </screenshot>\n"
" </screenshots>\n"
)
endif()
file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/appstream.xml"
" <screenshots>\n"
" <screenshot type=\"default\">\n"
" <image>https://gitea.thebrokenrail.com/TheBrokenRail/minecraft-pi-reborn/raw/branch/master/images/start.png</image>\n"
" </screenshot>\n"
" </screenshots>\n"
"</component>\n"
)
install(

View File

@ -17,7 +17,7 @@ static std::string get_cache_path() {
if (home == nullptr) {
IMPOSSIBLE();
}
return std::string(home) + HOME_SUBDIRECTORY_FOR_GAME_DATA "/.launcher-cache";
return std::string(home) + get_home_subdirectory_for_game_data() + "/.launcher-cache";
}
// Load
@ -165,7 +165,8 @@ void wipe_cache() {
INFO("Wiping Launcher Cache...");
// Unlink File
if (unlink(get_cache_path().c_str()) != 0) {
const int ret = unlink(get_cache_path().c_str());
if (ret != 0 && errno != ENOENT) {
WARN("Failure While Wiping Cache: %s", strerror(errno));
}
}

View File

@ -108,6 +108,7 @@ static void run_command_and_set_env(const char *env_name, const char *command[])
// Use Zenity To Set Environmental Variable
#define DIALOG_TITLE "Launcher"
static void run_zenity_and_set_env(const char *env_name, std::vector<std::string> command) {
reborn_check_display();
// Create Full Command
std::vector<std::string> full_command;
full_command.push_back("zenity");
@ -146,31 +147,16 @@ void handle_non_launch_client_only_commands(const options_t &options) {
});
exit(EXIT_SUCCESS);
}
}
// Check Environment
void check_environment_client() {
// Don't Run As Root
if (getenv("_MCPI_SKIP_ROOT_CHECK") == nullptr && (getuid() == 0 || geteuid() == 0)) {
ERR("Don't Run As Root");
// Wipe Cache If Needed
if (options.wipe_cache) {
wipe_cache();
exit(EXIT_SUCCESS);
}
// Check For Display
#ifndef MCPI_HEADLESS_MODE
if (getenv("DISPLAY") == nullptr && getenv("WAYLAND_DISPLAY") == nullptr) {
ERR("No display attached! Make sure $DISPLAY or $WAYLAND_DISPLAY is set.");
}
#endif
}
// Configure Client Options
#define LIST_DIALOG_SIZE "400"
void configure_client(const options_t &options) {
// Wipe Cache If Needed
if (options.wipe_cache) {
wipe_cache();
}
// Load Cache
launcher_cache cache = options.no_cache ? empty_cache : load_cache();

View File

@ -16,8 +16,5 @@ void load_available_feature_flags(const std::function<void(std::string)> &callba
// Handle Non-Launch Commands
void handle_non_launch_client_only_commands(const options_t &options);
// Check Environment
void check_environment_client();
// Configure Client Options
void configure_client(const options_t &options);

View File

@ -15,7 +15,6 @@
#include "crash-report.h"
// Show Crash Report Dialog
#ifndef MCPI_HEADLESS_MODE
#define DIALOG_TITLE "Crash Report"
#define CRASH_REPORT_DIALOG_WIDTH "640"
#define CRASH_REPORT_DIALOG_HEIGHT "480"
@ -35,7 +34,7 @@ static void show_report(const char *log_filename) {
"--width", CRASH_REPORT_DIALOG_WIDTH,
"--height", CRASH_REPORT_DIALOG_HEIGHT,
"--text-info",
"--text", MCPI_APP_BASE_TITLE " has crashed!\n\nNeed help? Consider asking on the <a href=\"" MCPI_DISCORD_INVITE "\">Discord server</a>! <i>If you believe this is a problem with " MCPI_APP_BASE_TITLE " itself, please upload this crash report to the #bugs Discord channel.</i>",
"--text", MCPI_APP_TITLE " has crashed!\n\nNeed help? Consider asking on the <a href=\"" MCPI_DISCORD_INVITE "\">Discord server</a>! <i>If you believe this is a problem with " MCPI_APP_TITLE " itself, please upload this crash report to the #bugs Discord channel.</i>",
"--filename", log_filename,
"--no-wrap",
"--font", "Monospace",
@ -46,7 +45,6 @@ static void show_report(const char *log_filename) {
safe_execvpe(command, (const char *const *) environ);
}
}
#endif
// Exit Handler
static void exit_handler(__attribute__((unused)) int signal) {
@ -248,11 +246,9 @@ void setup_crash_report() {
unsetenv(MCPI_LOG_ENV);
// Show Crash Log
#ifndef MCPI_HEADLESS_MODE
if (is_crash) {
if (is_crash && !reborn_is_headless()) {
show_report(log_filename);
}
#endif
// Exit
exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);

View File

@ -6,9 +6,7 @@
#include "options/parser.h"
#include "crash-report.h"
#include "util.h"
#ifndef MCPI_SERVER_MODE
#include "client/configuration.h"
#endif
// Bind Options To Environmental Variable
static void bind_to_env(const char *env, const bool value) {
@ -19,16 +17,14 @@ static void bind_to_env(const char *env, const bool value) {
}
static void setup_environment(const options_t &options) {
// Passthrough Options To Game
#ifndef MCPI_SERVER_MODE
bind_to_env(MCPI_SERVER_MODE_ENV, options.server_mode);
bind_to_env("_MCPI_BENCHMARK", options.benchmark);
#else
bind_to_env("_MCPI_ONLY_GENERATE", options.only_generate);
#endif
bind_to_env(MCPI_FORCE_HEADLESS_ENV, options.force_headless);
bind_to_env(MCPI_FORCE_NON_HEADLESS_ENV, options.force_non_headless);
// GTK Dark Mode
#ifndef MCPI_HEADLESS_MODE
set_and_print_env("GTK_THEME", "Adwaita:dark");
#endif
// Configure PATH
{
@ -72,9 +68,6 @@ static void start_game(const options_t &options) {
// Disable stdout Buffering
setvbuf(stdout, nullptr, _IONBF, 0);
// Environemntal Variable Options
setup_environment(options);
// Setup Crash Reporting
if (!options.disable_crash_report) {
setup_log_file();
@ -92,35 +85,35 @@ static void start_game(const options_t &options) {
sigaction(SIGTERM, &act_sigterm, nullptr);
// Setup Home
#ifndef MCPI_SERVER_MODE
// Ensure $HOME
const char *home = getenv("HOME");
if (home == nullptr) {
ERR("$HOME Isn't Set");
}
// Create If Needed
{
std::string minecraft_folder = std::string(home) + HOME_SUBDIRECTORY_FOR_GAME_DATA;
struct stat tmp_stat = {};
bool exists = stat(minecraft_folder.c_str(), &tmp_stat) != 0 ? false : S_ISDIR(tmp_stat.st_mode);
if (!exists) {
// Doesn't Exist
if (mkdir(minecraft_folder.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
ERR("Unable To Create Data Directory: %s", strerror(errno));
if (!reborn_is_server()) {
// Ensure $HOME
const char *home = getenv("HOME");
if (home == nullptr) {
ERR("$HOME Is Not Set");
}
// Create If Needed
{
std::string minecraft_folder = std::string(home) + get_home_subdirectory_for_game_data();
struct stat tmp_stat = {};
bool exists = stat(minecraft_folder.c_str(), &tmp_stat) != 0 ? false : S_ISDIR(tmp_stat.st_mode);
if (!exists) {
// Doesn't Exist
if (mkdir(minecraft_folder.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
ERR("Unable To Create Data Directory: %s", strerror(errno));
}
}
}
} else {
// Set Home To Current Directory, So World Data Is Stored There
char *launch_directory = getcwd(nullptr, 0);
set_and_print_env("HOME", launch_directory);
free(launch_directory);
}
#else
// Set Home To Current Directory, So World Data Is Stored There
char *launch_directory = getcwd(NULL, 0);
set_and_print_env("HOME", launch_directory);
free(launch_directory);
#endif
// Configure Client Options
#ifndef MCPI_SERVER_MODE
configure_client(options);
#endif
if (!reborn_is_server()) {
configure_client(options);
}
// Bootstrap
bootstrap();
@ -138,17 +131,12 @@ int main(int argc, char *argv[]) {
unsetenv(MCPI_LOG_ENV);
bind_to_env(MCPI_DEBUG_ENV, options.debug);
// Setup Environment
setup_environment(options);
// Handle Non-Launch Commands (Copy SDK, Print Feature Flags, Etc)
handle_non_launch_commands(options);
#ifndef MCPI_SERVER_MODE
handle_non_launch_client_only_commands(options);
#endif
// Check Environment
#ifndef MCPI_SERVER_MODE
// Code After This Can Safely Open A Window
check_environment_client();
#endif
// Start The Game
start_game(options);

View File

@ -60,7 +60,7 @@ std::string bootstrap_mods(const std::string &binary_directory) {
// ~/.minecraft-pi/mods
{
// Get Mods Folder
std::string mods_folder = std::string(getenv("HOME")) + HOME_SUBDIRECTORY_FOR_GAME_DATA SUBDIRECTORY_FOR_MODS;
std::string mods_folder = std::string(getenv("HOME")) + get_home_subdirectory_for_game_data() + SUBDIRECTORY_FOR_MODS;
// Load Mods From ./mods
load(preload, mods_folder);
}

View File

@ -1,12 +1,12 @@
OPTION(debug, "debug", 'd', "Enable Debug Logging (" MCPI_DEBUG_ENV ")")
OPTION(copy_sdk, "copy-sdk", -2, "Extract Modding SDK And Exit")
OPTION(disable_crash_report, "disable-crash-report", -1, "Disable Crash Report Dialog")
#ifndef MCPI_SERVER_MODE
OPTION(use_default, "default", -3, "Skip Configuration Dialogs")
OPTION(no_cache, "no-cache", -4, "Disable Configuration Cache")
OPTION(wipe_cache, "wipe-cache", -5, "Wipe Cached Configuration")
OPTION(print_available_feature_flags, "print-available-feature-flags", -6, "Print Available Feature Flags")
OPTION(benchmark, "benchmark", -7, "Run Benchmark")
#else
OPTION(only_generate, "only-generate", -8, "Generate World And Exit")
#endif
OPTION(use_default, "default", -3, "Skip Client-Mode Configuration Dialogs")
OPTION(no_cache, "no-cache", -4, "Disable Client-Mode Configuration Cache")
OPTION(wipe_cache, "wipe-cache", -5, "Wipe Cached Client-Mode Configuration And Exit")
OPTION(print_available_feature_flags, "print-available-feature-flags", -6, "Print Available Client-Mode Feature Flags")
OPTION(benchmark, "benchmark", -7, "Run Client-Mode Benchmark")
OPTION(only_generate, "only-generate", -8, "Generate World And Exit (Server-Mode Only)")
OPTION(force_headless, "force-headless", -9, "Force Disable Game Rendering")
OPTION(force_non_headless, "force-non-headless", -10, "Force Enable Game Rendering")
OPTION(server_mode, "server", -11, "Run In Server-Mode")

View File

@ -14,7 +14,7 @@
}
// Copy SDK Into ~/.minecraft-pi
#define HOME_SUBDIRECTORY_FOR_SDK HOME_SUBDIRECTORY_FOR_GAME_DATA "/sdk"
#define HOME_SUBDIRECTORY_FOR_SDK (std::string(get_home_subdirectory_for_game_data()) + "/sdk")
void copy_sdk(const std::string &binary_directory, const bool log_with_debug) {
// Ensure SDK Directory
std::string sdk_path;

View File

@ -1,7 +1,5 @@
#pragma once
#cmakedefine MCPI_SERVER_MODE
#cmakedefine MCPI_HEADLESS_MODE
#cmakedefine MCPI_IS_APPIMAGE_BUILD
#cmakedefine MCPI_IS_FLATPAK_BUILD
#cmakedefine MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN

View File

@ -1,10 +0,0 @@
#pragma once
// Minecraft Pi Game Data Root
#ifndef MCPI_SERVER_MODE
// Store Game Data In "~/.minecraft-pi" Instead Of "~/.minecraft" To Avoid Conflicts
#define HOME_SUBDIRECTORY_FOR_GAME_DATA "/.minecraft-pi"
#else
// Store Game Data In $HOME Root (In Server Mode, $HOME Is Changed To The Launch Directory)
#define HOME_SUBDIRECTORY_FOR_GAME_DATA ""
#endif

View File

@ -5,5 +5,4 @@
#include "util.h"
#include "string.h"
#include "exec.h"
#include "home.h"
#include "patch.h"

View File

@ -51,10 +51,19 @@ int lock_file(const char *file);
void unlock_file(const char *file, int fd);
// Access Configuration At Runtime
#define MCPI_SERVER_MODE_ENV "_MCPI_SERVER_MODE"
#define MCPI_FORCE_HEADLESS_ENV "_MCPI_FORCE_HEADLESS"
#define MCPI_FORCE_NON_HEADLESS_ENV "_MCPI_FORCE_NON_HEADLESS"
const char *reborn_get_version();
int reborn_is_headless();
int reborn_is_server();
// Check $DISPLAY
void reborn_check_display();
// Get Home Subdirectory
const char *get_home_subdirectory_for_game_data();
// Customize VTable
#define CUSTOM_VTABLE(name, parent) \
void _setup_##name##_vtable(parent##_vtable *vtable); \

View File

@ -50,16 +50,43 @@ const char *reborn_get_version() {
return MCPI_VERSION;
}
int reborn_is_headless() {
#ifdef MCPI_HEADLESS_MODE
return 1;
#else
return 0;
#endif
static int ret;
static int is_set = 0;
if (!is_set) {
ret = reborn_is_server();
if (getenv(MCPI_FORCE_HEADLESS_ENV)) {
ret = 1;
} else if (getenv(MCPI_FORCE_NON_HEADLESS_ENV)) {
ret = 0;
}
is_set = 1;
}
return ret;
}
int reborn_is_server() {
#ifdef MCPI_SERVER_MODE
return 1;
#else
return 0;
#endif
static int ret;
static int is_set = 0;
if (!is_set) {
ret = getenv(MCPI_SERVER_MODE_ENV) != NULL;
is_set = 1;
}
return ret;
}
// Check $DISPLAY
void reborn_check_display() {
if (!getenv("DISPLAY") && !getenv("WAYLAND_DISPLAY")) {
ERR("No display attached! Make sure $DISPLAY or $WAYLAND_DISPLAY is set.");
}
}
// Home Subdirectory
const char *get_home_subdirectory_for_game_data() {
if (!reborn_is_server()) {
// Store Game Data In "~/.minecraft-pi" Instead Of "~/.minecraft" To Avoid Conflicts
return "/.minecraft-pi";
} else {
// Store Game Data In $HOME Root (In Server Mode, $HOME Is Changed To The Launch Directory)
return "";
}
}

View File

@ -1,17 +1,17 @@
project(media-layer-core)
# OpenGL
if(NOT MCPI_HEADLESS_MODE)
add_subdirectory(gles)
endif()
add_subdirectory(gles)
# Configuration
set(CORE_SRC src/base.cpp src/media.c $<TARGET_OBJECTS:media-layer-extras>) # SDL Re-Implementation Using GLFW
if(NOT MCPI_HEADLESS_MODE)
list(APPEND CORE_SRC src/audio/api.cpp src/audio/engine.c src/audio/file.cpp)
else()
list(APPEND CORE_SRC src/audio/stubs.c)
endif()
# SDL Re-Implementation Using GLFW
set(CORE_SRC
src/base.cpp
src/media.cpp
src/audio/api.cpp
src/audio/engine.c
src/audio/file.cpp
$<TARGET_OBJECTS:media-layer-extras>
)
# Build
add_library(media-layer-core-real SHARED ${CORE_SRC}) # Dependencies Are Setup Later
@ -23,10 +23,14 @@ endif()
install(TARGETS media-layer-core-real DESTINATION "${MCPI_LIB_DIR}")
# Link
target_link_libraries(media-layer-core-real PUBLIC media-layer-headers PUBLIC reborn-util PUBLIC dl)
if(NOT MCPI_HEADLESS_MODE)
# OpenAL
find_library(OPENAL_LIBRARY NAMES openal REQUIRED)
# Link
target_link_libraries(media-layer-core-real PRIVATE "${OPENAL_LIBRARY}" PRIVATE m PRIVATE glfw PUBLIC GLESv1_CM PRIVATE LIB_LIEF)
endif()
find_library(OPENAL_LIBRARY NAMES openal REQUIRED)
target_link_libraries(media-layer-core-real
PUBLIC media-layer-headers
PUBLIC reborn-util
PRIVATE "${OPENAL_LIBRARY}"
PRIVATE m
PRIVATE glfw
PUBLIC GLESv1_CM
PRIVATE LIB_LIEF
PUBLIC dl
)

View File

@ -1,6 +0,0 @@
#include <media-layer/audio.h>
void media_audio_update(__attribute__((unused)) float volume, __attribute__((unused)) float x, __attribute__((unused)) float y, __attribute__((unused)) float z, __attribute__((unused)) float yaw) {
}
void media_audio_play(__attribute__((unused)) const char *source, __attribute__((unused)) const char *name, __attribute__((unused)) float x, __attribute__((unused)) float y, __attribute__((unused)) float z, __attribute__((unused)) float pitch, __attribute__((unused)) float volume, __attribute__((unused)) int is_ui) {
}

View File

@ -1,861 +0,0 @@
#include <unistd.h>
#include <SDL/SDL.h>
#include <libreborn/libreborn.h>
#ifndef MCPI_HEADLESS_MODE
#include <time.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#endif
#include <media-layer/core.h>
#include <media-layer/internal.h>
#ifndef MCPI_HEADLESS_MODE
#include "audio/engine.h"
#endif
// Allow Disabling Interaction
static void update_cursor();
#ifndef MCPI_HEADLESS_MODE
static void emit_events_after_is_interactable_change();
#endif
static int is_interactable = 1;
void media_set_interactable(int toggle) {
if (toggle != is_interactable) {
is_interactable = toggle;
update_cursor();
#ifndef MCPI_HEADLESS_MODE
emit_events_after_is_interactable_change();
#endif
}
}
// Track Media Layer State
static volatile int is_running = 0;
// Store Cursor State
static int cursor_grabbed = 0;
static int cursor_visible = 1;
// Track If Raw Mouse Motion Is Enabled
static int raw_mouse_motion_enabled = 1;
// GLFW Code Not Needed In Headless Mode
#ifndef MCPI_HEADLESS_MODE
static GLFWwindow *glfw_window = NULL;
// Handle GLFW Error
static void glfw_error(__attribute__((unused)) int error, const char *description) {
WARN("GLFW Error: %s", description);
}
// Pass Character Event
static void character_event(char c) {
// SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = USER_EVENT_CHARACTER;
event.user.data1 = (int) c;
SDL_PushEvent(&event);
}
// Convert GLFW Key To SDL Key
#define IMAGINARY_GLFW_CRAFTING_KEY GLFW_KEY_LAST
static SDLKey glfw_key_to_sdl_key(int key) {
switch (key) {
// Movement
case GLFW_KEY_W:
return SDLK_w;
case GLFW_KEY_A:
return SDLK_a;
case GLFW_KEY_S:
return SDLK_s;
case GLFW_KEY_D:
return SDLK_d;
case GLFW_KEY_SPACE:
return SDLK_SPACE;
case GLFW_KEY_LEFT_SHIFT:
return SDLK_LSHIFT;
case GLFW_KEY_RIGHT_SHIFT:
return SDLK_RSHIFT;
// Inventory
case GLFW_KEY_E:
return SDLK_e;
// Drop Item
case GLFW_KEY_Q:
return SDLK_q;
// Toolbar
case GLFW_KEY_1:
return SDLK_1;
case GLFW_KEY_2:
return SDLK_2;
case GLFW_KEY_3:
return SDLK_3;
case GLFW_KEY_4:
return SDLK_4;
case GLFW_KEY_5:
return SDLK_5;
case GLFW_KEY_6:
return SDLK_6;
case GLFW_KEY_7:
return SDLK_7;
case GLFW_KEY_8:
return SDLK_8;
case GLFW_KEY_9:
return SDLK_9;
case GLFW_KEY_0:
return SDLK_0;
// UI Control
case GLFW_KEY_ESCAPE:
return SDLK_ESCAPE;
case GLFW_KEY_UP:
return SDLK_UP;
case GLFW_KEY_DOWN:
return SDLK_DOWN;
case GLFW_KEY_LEFT:
return SDLK_LEFT;
case GLFW_KEY_RIGHT:
return SDLK_RIGHT;
case GLFW_KEY_TAB:
return SDLK_TAB;
case GLFW_KEY_ENTER:
return SDLK_RETURN;
case GLFW_KEY_BACKSPACE:
return SDLK_BACKSPACE;
case GLFW_KEY_DELETE:
return SDLK_DELETE;
// Fullscreen
case GLFW_KEY_F11:
return SDLK_F11;
// Screenshot
case GLFW_KEY_F2:
return SDLK_F2;
// Hide GUI
case GLFW_KEY_F1:
return SDLK_F1;
// Third Person
case GLFW_KEY_F5:
return SDLK_F5;
// Chat
case GLFW_KEY_T:
return SDLK_t;
// Crafting
case IMAGINARY_GLFW_CRAFTING_KEY:
return SDLK_WORLD_0;
// Unknown
default:
return SDLK_UNKNOWN;
}
}
// Convert GLFW Key Modifier To SDL Key Modifier
static SDLMod glfw_modifier_to_sdl_modifier(int mods) {
SDLMod ret = KMOD_NONE;
// Control
if ((mods & GLFW_MOD_CONTROL) != 0) {
ret |= KMOD_CTRL;
}
// Shift
if ((mods & GLFW_MOD_SHIFT) != 0) {
ret |= KMOD_SHIFT;
}
// Alt
if ((mods & GLFW_MOD_ALT) != 0) {
ret |= KMOD_ALT;
}
// Return
return ret;
}
// Pass Key Presses To SDL
static void glfw_key_raw(int key, int scancode, int action, int mods) {
SDL_Event event1;
int up = action == GLFW_RELEASE;
event1.type = up ? SDL_KEYUP : SDL_KEYDOWN;
event1.key.state = up ? SDL_RELEASED : SDL_PRESSED;
event1.key.keysym.scancode = scancode;
event1.key.keysym.mod = glfw_modifier_to_sdl_modifier(mods);
event1.key.keysym.sym = glfw_key_to_sdl_key(key);
SDL_PushEvent(&event1);
// Allow MCPI To Access Original GLFW Keycode
SDL_Event event2;
event2.type = SDL_USEREVENT;
event2.user.code = USER_EVENT_REAL_KEY;
event2.user.data1 = event1.key.state;
event2.user.data2 = key;
SDL_PushEvent(&event2);
}
static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, int mods) {
if (is_interactable) {
glfw_key_raw(key, scancode, action, mods);
}
}
// Pass Text To Minecraft
static void codepoint_to_utf8(unsigned char *const buffer, const unsigned int code) {
// https://stackoverflow.com/a/42013433/16198887
if (code <= 0x7f) {
buffer[0] = code;
} else if (code <= 0x7ff) {
buffer[0] = 0xc0 | (code >> 6); // 110xxxxx
buffer[1] = 0x80 | (code & 0x3f); // 10xxxxxx
} else if (code <= 0xffff) {
buffer[0] = 0xe0 | (code >> 12); // 1110xxxx
buffer[1] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx
buffer[2] = 0x80 | (code & 0x3f); // 10xxxxxx
} else if (code <= 0x10ffff) {
buffer[0] = 0xf0 | (code >> 18); // 11110xxx
buffer[1] = 0x80 | ((code >> 12) & 0x3f); // 10xxxxxx
buffer[2] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx
buffer[3] = 0x80 | (code & 0x3f); // 10xxxxxx
}
}
static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int codepoint) {
if (is_interactable) {
// Convert
size_t str_size = 4 /* Maximum UTF-8 character size */ + 1 /* NULL-terminator */;
char str[str_size];
memset(str, 0, str_size);
codepoint_to_utf8((unsigned char *) str, codepoint);
char *cp437_str = to_cp437(str);
// Send Event
for (int i = 0; cp437_str[i] != '\0'; i++) {
character_event(cp437_str[i]);
}
// Free
free(cp437_str);
}
}
// Last Mouse Location
static double last_mouse_x = 0;
static double last_mouse_y = 0;
// Ignore Relative Cursor Motion
static int ignore_relative_motion = 0;
// Convert Screen Coordinates To Pixels
static void convert_to_pixels(GLFWwindow *window, double *xpos, double *ypos) {
// Skip If Cursor Is Grabbed
if (cursor_grabbed && raw_mouse_motion_enabled) {
return;
}
// Get Window Size
int window_width;
int window_height;
glfwGetWindowSize(window, &window_width, &window_height);
// Get Framebuffer Size
int framebuffer_width;
int framebuffer_height;
glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height);
// Calculate Ratios
double width_ratio = ((double) framebuffer_width) / ((double) window_width);
double height_ratio = ((double) framebuffer_height) / ((double) window_height);
// Multiply
*xpos *= width_ratio;
*ypos *= height_ratio;
}
// Pass Mouse Movement To SDL
static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) {
convert_to_pixels(window, &xpos, &ypos);
if (is_interactable) {
SDL_Event event;
event.type = SDL_MOUSEMOTION;
event.motion.x = xpos;
event.motion.y = ypos;
event.motion.xrel = !ignore_relative_motion ? (xpos - last_mouse_x) : 0;
event.motion.yrel = !ignore_relative_motion ? (ypos - last_mouse_y) : 0;
SDL_PushEvent(&event);
}
ignore_relative_motion = 0;
last_mouse_x = xpos;
last_mouse_y = ypos;
}
// Create And Push SDL Mouse Click Event
static void click_event(int button, int up) {
SDL_Event event;
event.type = up ? SDL_MOUSEBUTTONUP : SDL_MOUSEBUTTONDOWN;
event.button.x = last_mouse_x;
event.button.y = last_mouse_y;
event.button.state = up ? SDL_RELEASED : SDL_PRESSED;
event.button.button = button;
SDL_PushEvent(&event);
}
// Pass Mouse Click To SDL
static void glfw_click_raw(int button, int action) {
int up = action == GLFW_RELEASE;
int sdl_button = button == GLFW_MOUSE_BUTTON_RIGHT ? SDL_BUTTON_RIGHT : (button == GLFW_MOUSE_BUTTON_LEFT ? SDL_BUTTON_LEFT : SDL_BUTTON_MIDDLE);
click_event(sdl_button, up);
}
static void glfw_click(__attribute__((unused)) GLFWwindow *window, int button, int action, __attribute__((unused)) int mods) {
if (is_interactable) {
glfw_click_raw(button, action);
}
}
// Pass Mouse Scroll To SDL
static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) {
if (is_interactable && yoffset != 0) {
int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN;
click_event(sdl_button, 0);
click_event(sdl_button, 1);
}
}
// Controller Events
static SDLKey glfw_controller_button_to_key(int button) {
switch (button) {
// Jump
case GLFW_GAMEPAD_BUTTON_A:
return GLFW_KEY_SPACE;
// Drop Item
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
return GLFW_KEY_Q;
// Inventory
case GLFW_GAMEPAD_BUTTON_Y:
return GLFW_KEY_E;
// Third-Person
case GLFW_GAMEPAD_BUTTON_DPAD_UP:
return GLFW_KEY_F5;
// Sneak
case GLFW_GAMEPAD_BUTTON_B:
return GLFW_KEY_LEFT_SHIFT;
// Chat
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
return GLFW_KEY_T;
// Pause
case GLFW_GAMEPAD_BUTTON_START:
case GLFW_GAMEPAD_BUTTON_BACK:
return GLFW_KEY_ESCAPE;
// Crafting
case GLFW_GAMEPAD_BUTTON_X:
return IMAGINARY_GLFW_CRAFTING_KEY;
// Unknown
default:
return GLFW_KEY_UNKNOWN;
}
}
static void glfw_controller_button(int button, int action) {
int key = glfw_controller_button_to_key(button);
if (key != GLFW_KEY_UNKNOWN) {
// Press Key
glfw_key_raw(key, glfwGetKeyScancode(key), action, 0);
} else {
// Scrolling
if (button == GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) {
key = SDL_BUTTON_WHEELUP;
} else if (button == GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) {
key = SDL_BUTTON_WHEELDOWN;
}
if (key != GLFW_KEY_UNKNOWN) {
click_event(key, action == GLFW_PRESS);
}
}
}
// Controller Movement Axis
static int controller_horizontal_key = GLFW_KEY_UNKNOWN;
static int controller_vertical_key = GLFW_KEY_UNKNOWN;
static void release_and_press_key(int *old_key, int new_key) {
if (*old_key != new_key) {
if (*old_key != GLFW_KEY_UNKNOWN) {
glfw_key_raw(*old_key, glfwGetKeyScancode(*old_key), GLFW_RELEASE, 0);
}
if (new_key != GLFW_KEY_UNKNOWN) {
glfw_key_raw(new_key, glfwGetKeyScancode(new_key), GLFW_PRESS, 0);
}
}
*old_key = new_key;
}
#define verify_controller_axis_value(value, threshold) \
if ((value < (threshold) && value > 0) || (value > -(threshold) && value < 0)) { \
value = 0; \
}
#define CONTROLLER_MOVEMENT_AXIS_THRESHOLD 0.5f
static void glfw_controller_movement(float x, float y) {
// Verify
verify_controller_axis_value(x, CONTROLLER_MOVEMENT_AXIS_THRESHOLD);
verify_controller_axis_value(y, CONTROLLER_MOVEMENT_AXIS_THRESHOLD);
// Horizontal Movement
if (x > 0) {
release_and_press_key(&controller_horizontal_key, GLFW_KEY_D);
} else if (x < 0) {
release_and_press_key(&controller_horizontal_key, GLFW_KEY_A);
} else {
release_and_press_key(&controller_horizontal_key, GLFW_KEY_UNKNOWN);
}
// Vertical Movement
if (y < 0) {
release_and_press_key(&controller_vertical_key, GLFW_KEY_W);
} else if (y > 0) {
release_and_press_key(&controller_vertical_key, GLFW_KEY_S);
} else {
release_and_press_key(&controller_vertical_key, GLFW_KEY_UNKNOWN);
}
}
// Get Time
#define NANOSECONDS_IN_SECOND 1000000000ll
static long long int get_time() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
long long int a = (long long int) ts.tv_nsec;
long long int b = ((long long int) ts.tv_sec) * NANOSECONDS_IN_SECOND;
return a + b;
}
// Controller Look Axis
#define CONTROLLER_LOOK_EVENT_PERIOD 50000000ll // 1/20 Seconds
#define CONTROLLER_LOOK_AXIS_THRESHOLD 0.2f
#define CONTROLLER_LOOK_AXIS_SENSITIVITY 70
static void glfw_controller_look(float x, float y) {
// Current Time
long long int current_time = get_time();
// Last Time
static long long int last_time = 0;
static int is_last_time_set = 0;
if (!is_last_time_set) {
is_last_time_set = 1;
last_time = current_time;
}
// Check If Period Has Passed
if ((current_time - last_time) > CONTROLLER_LOOK_EVENT_PERIOD) {
// Reset Last Time
last_time = current_time;
// Verify
verify_controller_axis_value(x, CONTROLLER_LOOK_AXIS_THRESHOLD);
verify_controller_axis_value(y, CONTROLLER_LOOK_AXIS_THRESHOLD);
// Send Event
if (is_interactable) {
SDL_Event event;
event.type = SDL_MOUSEMOTION;
event.motion.x = last_mouse_x;
event.motion.y = last_mouse_y;
event.motion.xrel = x * CONTROLLER_LOOK_AXIS_SENSITIVITY;
event.motion.yrel = y * CONTROLLER_LOOK_AXIS_SENSITIVITY;
SDL_PushEvent(&event);
}
}
}
// Controller Place/Mine Triggers
#define CONTROLLER_TRIGGER_THRESHOLD 0
#define CONTROLLER_TRIGGER_COUNT 2
static void glfw_controller_trigger(int trigger, int action) {
glfw_click_raw(trigger == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER ? GLFW_MOUSE_BUTTON_LEFT : GLFW_MOUSE_BUTTON_RIGHT, action);
}
// Current Controller
static int current_controller = -1;
// Track Controller State
static void update_controller_state() {
// Store Button/Trigger State
static int controller_buttons[GLFW_GAMEPAD_BUTTON_LAST + 1];
static int controller_triggers[CONTROLLER_TRIGGER_COUNT];
// Get State
GLFWgamepadstate state;
int controller_enabled = cursor_grabbed && is_interactable;
int controller_valid = controller_enabled && current_controller != -1 && glfwGetGamepadState(current_controller, &state);
if (!controller_valid) {
// Invalid Controller
// Generate Blank State
for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
state.buttons[i] = GLFW_RELEASE;
}
for (int i = GLFW_GAMEPAD_AXIS_LEFT_X; i <= GLFW_GAMEPAD_AXIS_LAST; i++) {
int is_trigger = i == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || i == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER;
state.axes[i] = is_trigger ? -1 : 0;
}
}
// Check Buttons
for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
int old_state = controller_buttons[i];
controller_buttons[i] = state.buttons[i];
if (old_state != controller_buttons[i]) {
// State Changed
glfw_controller_button(i, controller_buttons[i]);
}
}
// Handle Movement & Look
glfw_controller_movement(state.axes[GLFW_GAMEPAD_AXIS_LEFT_X], state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
glfw_controller_look(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X], state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
// Check Triggers
for (int i = 0; i < CONTROLLER_TRIGGER_COUNT; i++) {
int old_state = controller_triggers[i];
int trigger_id = i == 0 ? GLFW_GAMEPAD_AXIS_LEFT_TRIGGER : GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER;
controller_triggers[i] = state.axes[trigger_id] < CONTROLLER_TRIGGER_THRESHOLD ? GLFW_RELEASE : GLFW_PRESS;
if (old_state != controller_triggers[i]) {
// State Changed
glfw_controller_trigger(trigger_id, controller_triggers[i]);
}
}
}
// Pick Controller
static int joysticks[GLFW_JOYSTICK_LAST + 1];
static void pick_new_controller() {
current_controller = -1;
for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
if (joysticks[i] == 1) {
current_controller = i;
DEBUG("Using Controller: %s (%s)", glfwGetGamepadName(i), glfwGetJoystickName(i));
break;
}
}
}
static void find_controllers() {
for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
joysticks[i] = glfwJoystickIsGamepad(i);
}
pick_new_controller();
}
static void glfw_joystick(int jid, int event) {
if (event == GLFW_CONNECTED && glfwJoystickIsGamepad(jid)) {
joysticks[jid] = 1;
pick_new_controller();
} else if (event == GLFW_DISCONNECTED) {
joysticks[jid] = 0;
if (jid == current_controller) {
DEBUG("Controller Disconnected");
pick_new_controller();
}
}
}
// Release all keys/buttons when interaction is disabled and vice versa.
static void emit_events_after_is_interactable_change() {
if (is_running) {
for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; i++) {
int state = glfwGetKey(glfw_window, i);
if (state == GLFW_PRESS) {
glfw_key_raw(i, glfwGetKeyScancode(i), is_interactable ? GLFW_PRESS : GLFW_RELEASE, 0);
}
}
for (int i = GLFW_MOUSE_BUTTON_1; i <= GLFW_MOUSE_BUTTON_LAST; i++) {
int state = glfwGetMouseButton(glfw_window, i);
if (state == GLFW_PRESS) {
glfw_click_raw(i, is_interactable ? GLFW_PRESS : GLFW_RELEASE);
}
}
}
}
#endif
// Enable/Disable Raw Mouse Motion
void media_set_raw_mouse_motion_enabled(int enabled) {
raw_mouse_motion_enabled = enabled;
#ifndef MCPI_HEADLESS_MODE
if (is_running) {
glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
}
#endif
if (!raw_mouse_motion_enabled) {
WARN("Raw mouse motion has been DISABLED, this IS NOT recommended, and should only ever be used on systems that don't support or have broken raw mouse motion.");
}
}
// Disable V-Sync
static int disable_vsync = 0;
void media_disable_vsync() {
disable_vsync = 1;
#ifndef MCPI_HEADLESS_MODE
if (is_running) {
glfwSwapInterval(0);
}
#endif
}
// Force EGL
static int force_egl = 0;
void media_force_egl() {
if (force_egl == -1) {
IMPOSSIBLE();
}
force_egl = 1;
}
// Init Media Layer
#define GL_VERSION 0x1f02
typedef const unsigned char *(*glGetString_t)(unsigned int name);
void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *icon) {
// Don't Enable GLFW In Headless Mode
#ifndef MCPI_HEADLESS_MODE
// Init GLFW
glfwSetErrorCallback(glfw_error);
if (!glfwInit()) {
ERR("Unable To Initialize GLFW");
}
// Create OpenGL ES Context
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
#endif
// Use EGL
if (force_egl) {
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
}
force_egl = -1;
// Extra Settings
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
glfwWindowHint(GLFW_ALPHA_BITS, 0); // Fix Transparent Window On Wayland
// App ID
glfwWindowHintString(GLFW_X11_CLASS_NAME, MCPI_APP_ID);
glfwWindowHintString(GLFW_WAYLAND_APP_ID, MCPI_APP_ID);
// Create Window
glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL);
if (!glfw_window) {
ERR("Unable To Create GLFW Window");
}
// Event Handlers
glfwSetKeyCallback(glfw_window, glfw_key);
glfwSetCharCallback(glfw_window, glfw_char);
glfwSetCursorPosCallback(glfw_window, glfw_motion);
glfwSetMouseButtonCallback(glfw_window, glfw_click);
glfwSetScrollCallback(glfw_window, glfw_scroll);
// Setup Controller Support
find_controllers();
glfwSetJoystickCallback(glfw_joystick);
// Make Window Context Current
glfwMakeContextCurrent(glfw_window);
// Setup Compatibility Layer
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
extern void init_gles_compatibility_layer(void *);
init_gles_compatibility_layer(glfwGetProcAddress);
#endif
// Debug
glGetString_t glGetString = (glGetString_t) glfwGetProcAddress("glGetString");
DEBUG("Using %s", (*glGetString)(GL_VERSION));
// Init OpenAL
_media_audio_init();
#else
(void) title; // Mark As Used
#endif
// Set State
is_running = 1;
// Update State
update_cursor();
if (disable_vsync) {
media_disable_vsync();
}
// Always Cleanup Media Layer
atexit(media_cleanup);
}
void media_swap_buffers() {
#ifndef MCPI_HEADLESS_MODE
// Don't Swap Buffers In A Context-Less Window
glfwSwapBuffers(glfw_window);
#endif
}
// Fullscreen Not Needed In Headless Mode
#ifndef MCPI_HEADLESS_MODE
static int is_fullscreen = 0;
// Old Size And Position To Use When Exiting Fullscreen
static int old_width = -1;
static int old_height = -1;
static int old_x = -1;
static int old_y = -1;
// Toggle Fullscreen
void media_toggle_fullscreen() {
if (is_fullscreen) {
glfwSetWindowMonitor(glfw_window, NULL, old_x, old_y, old_width, old_height, GLFW_DONT_CARE);
old_width = -1;
old_height = -1;
old_x = -1;
old_y = -1;
} else {
glfwGetWindowSize(glfw_window, &old_width, &old_height);
glfwGetWindowPos(glfw_window, &old_x, &old_y);
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(glfw_window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE);
}
is_fullscreen = !is_fullscreen;
}
#else
void media_toggle_fullscreen() {
}
#endif
// Intercept SDL Events
void _media_handle_SDL_PollEvent() {
// GLFW And Audio Are Disabled Disabled In Headless Mode
#ifndef MCPI_HEADLESS_MODE
// Process GLFW Events
glfwPollEvents();
// Controller
update_controller_state();
// Close Window
if (glfwWindowShouldClose(glfw_window)) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
glfwSetWindowShouldClose(glfw_window, GLFW_FALSE);
}
#endif
}
// Cleanup Media Layer
void media_cleanup() {
if (is_running) {
// GLFW And Audio Are Disabled In Headless Mode
#ifndef MCPI_HEADLESS_MODE
// Ignore GLFW Errors During Termination
glfwSetErrorCallback(NULL);
// Terminate GLFW
glfwDestroyWindow(glfw_window);
glfwTerminate();
// Cleanup OpenAL
_media_audio_cleanup();
#endif
// Update State
is_running = 0;
}
}
// Update GLFW Cursor State (Client Only)
static void update_cursor() {
#ifndef MCPI_HEADLESS_MODE
if (is_running) {
// Get New State
int new_cursor_visible = is_interactable ? cursor_visible : 1;
int new_cursor_grabbed = is_interactable ? cursor_grabbed : 0;
// Store Old Mode
int old_mode = glfwGetInputMode(glfw_window, GLFW_CURSOR);
// Handle Cursor Visibility
int new_mode;
if (!new_cursor_visible) {
if (new_cursor_grabbed) {
new_mode = GLFW_CURSOR_DISABLED;
} else {
new_mode = GLFW_CURSOR_HIDDEN;
}
} else {
new_mode = GLFW_CURSOR_NORMAL;
}
if (new_mode != old_mode) {
// Ignore Relative Cursor Motion When Locking
if (new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) {
ignore_relative_motion = 1;
}
// Set New Mode
glfwSetInputMode(glfw_window, GLFW_CURSOR, new_mode);
// Handle Cursor Lock/Unlock
if ((new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) || (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED)) {
// Use Raw Mouse Motion
if (raw_mouse_motion_enabled) {
glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, new_mode == GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE);
}
// Request Focus
if (!glfwGetWindowAttrib(glfw_window, GLFW_FOCUSED)) {
glfwRequestWindowAttention(glfw_window);
}
}
// Reset Mouse Position When Unlocking
if (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED) {
double cursor_x;
double cursor_y;
glfwGetCursorPos(glfw_window, &cursor_x, &cursor_y);
glfw_motion(glfw_window, cursor_x, cursor_y);
}
}
}
#endif
}
// Fix SDL Cursor Visibility/Grabbing
SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode) {
if (mode == SDL_GRAB_QUERY) {
// Query
return cursor_grabbed ? SDL_GRAB_ON : SDL_GRAB_OFF;
} else if (mode == SDL_GRAB_ON) {
// Store State
cursor_grabbed = 1;
} else if (mode == SDL_GRAB_OFF) {
// Store State
cursor_grabbed = 0;
}
// Update Cursor GLFW State (Client Only)
update_cursor();
// Return
return mode;
}
// Stub SDL Cursor Visibility
int SDL_ShowCursor(int toggle) {
if (toggle == SDL_QUERY) {
// Query
return cursor_visible ? SDL_ENABLE : SDL_DISABLE;
} else if (toggle == SDL_ENABLE) {
// Store State
cursor_visible = 1;
} else if (toggle == SDL_DISABLE) {
// Store State
cursor_visible = 0;
}
// Update Cursor GLFW State (Client Only)
update_cursor();
// Return
return toggle;
}
// Get Framebuffer Size
void media_get_framebuffer_size(int *width, int *height) {
#ifndef MCPI_HEADLESS_MODE
if (glfw_window != NULL) {
glfwGetFramebufferSize(glfw_window, width, height);
return;
}
#endif
*width = DEFAULT_WIDTH;
*height = DEFAULT_HEIGHT;
}

View File

@ -0,0 +1,561 @@
#include <ctime>
#include <unistd.h>
#include <SDL/SDL.h>
#include <libreborn/libreborn.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <media-layer/core.h>
#include <media-layer/internal.h>
#include "audio/engine.h"
// Allow Disabling Interaction
static void update_cursor();
static int is_interactable = 1;
void media_set_interactable(int toggle) {
if (bool(toggle) != is_interactable) {
is_interactable = toggle;
update_cursor();
}
}
// Store Cursor State
static bool cursor_grabbed = false;
static bool cursor_visible = true;
// Track If Raw Mouse Motion Is Enabled
static bool raw_mouse_motion_enabled = true;
// Window
static GLFWwindow *glfw_window = nullptr;
// Handle GLFW Error
static void glfw_error(__attribute__((unused)) int error, const char *description) {
WARN("GLFW Error: %s", description);
}
// Convert GLFW Key To SDL Key
static SDLKey glfw_key_to_sdl_key(const int key) {
switch (key) {
// Movement
case GLFW_KEY_W:
return SDLK_w;
case GLFW_KEY_A:
return SDLK_a;
case GLFW_KEY_S:
return SDLK_s;
case GLFW_KEY_D:
return SDLK_d;
case GLFW_KEY_SPACE:
return SDLK_SPACE;
case GLFW_KEY_LEFT_SHIFT:
return SDLK_LSHIFT;
case GLFW_KEY_RIGHT_SHIFT:
return SDLK_RSHIFT;
// Inventory
case GLFW_KEY_E:
return SDLK_e;
// Drop Item
case GLFW_KEY_Q:
return SDLK_q;
// Toolbar
case GLFW_KEY_1:
return SDLK_1;
case GLFW_KEY_2:
return SDLK_2;
case GLFW_KEY_3:
return SDLK_3;
case GLFW_KEY_4:
return SDLK_4;
case GLFW_KEY_5:
return SDLK_5;
case GLFW_KEY_6:
return SDLK_6;
case GLFW_KEY_7:
return SDLK_7;
case GLFW_KEY_8:
return SDLK_8;
case GLFW_KEY_9:
return SDLK_9;
case GLFW_KEY_0:
return SDLK_0;
// UI Control
case GLFW_KEY_ESCAPE:
return SDLK_ESCAPE;
case GLFW_KEY_UP:
return SDLK_UP;
case GLFW_KEY_DOWN:
return SDLK_DOWN;
case GLFW_KEY_LEFT:
return SDLK_LEFT;
case GLFW_KEY_RIGHT:
return SDLK_RIGHT;
case GLFW_KEY_TAB:
return SDLK_TAB;
case GLFW_KEY_ENTER:
return SDLK_RETURN;
case GLFW_KEY_BACKSPACE:
return SDLK_BACKSPACE;
case GLFW_KEY_DELETE:
return SDLK_DELETE;
// Fullscreen
case GLFW_KEY_F11:
return SDLK_F11;
// Screenshot
case GLFW_KEY_F2:
return SDLK_F2;
// Hide GUI
case GLFW_KEY_F1:
return SDLK_F1;
// Third Person
case GLFW_KEY_F5:
return SDLK_F5;
// Chat
case GLFW_KEY_T:
return SDLK_t;
// Unknown
default:
return SDLK_UNKNOWN;
}
}
// Convert GLFW Key Modifier To SDL Key Modifier
static SDLMod glfw_modifier_to_sdl_modifier(const int mods) {
int ret = KMOD_NONE;
// Control
if ((mods & GLFW_MOD_CONTROL) != 0) {
ret |= KMOD_CTRL;
}
// Shift
if ((mods & GLFW_MOD_SHIFT) != 0) {
ret |= KMOD_SHIFT;
}
// Alt
if ((mods & GLFW_MOD_ALT) != 0) {
ret |= KMOD_ALT;
}
// Return
return SDLMod(ret);
}
// Pass Key Presses To SDL
static void glfw_key_raw(int key, int scancode, int action, int mods) {
SDL_Event event1;
bool up = action == GLFW_RELEASE;
event1.type = up ? SDL_KEYUP : SDL_KEYDOWN;
event1.key.state = up ? SDL_RELEASED : SDL_PRESSED;
event1.key.keysym.scancode = scancode;
event1.key.keysym.mod = glfw_modifier_to_sdl_modifier(mods);
event1.key.keysym.sym = glfw_key_to_sdl_key(key);
SDL_PushEvent(&event1);
// Allow MCPI To Access Original GLFW Keycode
SDL_Event event2;
event2.type = SDL_USEREVENT;
event2.user.code = USER_EVENT_REAL_KEY;
event2.user.data1 = event1.key.state;
event2.user.data2 = key;
SDL_PushEvent(&event2);
}
static void glfw_key(__attribute__((unused)) GLFWwindow *window, const int key, const int scancode, const int action, const int mods) {
if (is_interactable) {
glfw_key_raw(key, scancode, action, mods);
}
}
// Pass Text To Minecraft
static void character_event(char c) {
if (!is_interactable) {
return;
}
// SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = USER_EVENT_CHARACTER;
event.user.data1 = (int) c;
SDL_PushEvent(&event);
}
static void codepoint_to_utf8(unsigned char *const buffer, const unsigned int code) {
// https://stackoverflow.com/a/42013433/16198887
if (code <= 0x7f) {
buffer[0] = code;
} else if (code <= 0x7ff) {
buffer[0] = 0xc0 | (code >> 6); // 110xxxxx
buffer[1] = 0x80 | (code & 0x3f); // 10xxxxxx
} else if (code <= 0xffff) {
buffer[0] = 0xe0 | (code >> 12); // 1110xxxx
buffer[1] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx
buffer[2] = 0x80 | (code & 0x3f); // 10xxxxxx
} else if (code <= 0x10ffff) {
buffer[0] = 0xf0 | (code >> 18); // 11110xxx
buffer[1] = 0x80 | ((code >> 12) & 0x3f); // 10xxxxxx
buffer[2] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx
buffer[3] = 0x80 | (code & 0x3f); // 10xxxxxx
}
}
static void glfw_char(__attribute__((unused)) GLFWwindow *window, const unsigned int codepoint) {
// Convert
size_t str_size = 4 /* Maximum UTF-8 character size */ + 1 /* NULL-terminator */;
char str[str_size] = {};
codepoint_to_utf8((unsigned char *) str, codepoint);
char *cp437_str = to_cp437(str);
// Send Event
for (int i = 0; cp437_str[i] != '\0'; i++) {
character_event(cp437_str[i]);
}
// Free
free(cp437_str);
}
// Last Mouse Location
static double last_mouse_x = 0;
static double last_mouse_y = 0;
// Ignore Relative Cursor Motion
static bool ignore_relative_motion = false;
// Convert Screen Coordinates To Pixels
static void convert_to_pixels(GLFWwindow *window, double *xpos, double *ypos) {
// Skip If Cursor Is Grabbed
if (cursor_grabbed && raw_mouse_motion_enabled) {
return;
}
// Get Window Size
int window_width;
int window_height;
glfwGetWindowSize(window, &window_width, &window_height);
// Get Framebuffer Size
int framebuffer_width;
int framebuffer_height;
glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height);
// Calculate Ratios
const double width_ratio = ((double) framebuffer_width) / ((double) window_width);
const double height_ratio = ((double) framebuffer_height) / ((double) window_height);
// Multiply
*xpos *= width_ratio;
*ypos *= height_ratio;
}
// Pass Mouse Movement To SDL
static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) {
convert_to_pixels(window, &xpos, &ypos);
if (is_interactable) {
SDL_Event event;
event.type = SDL_MOUSEMOTION;
event.motion.x = xpos;
event.motion.y = ypos;
event.motion.xrel = !ignore_relative_motion ? (xpos - last_mouse_x) : 0;
event.motion.yrel = !ignore_relative_motion ? (ypos - last_mouse_y) : 0;
SDL_PushEvent(&event);
}
ignore_relative_motion = false;
last_mouse_x = xpos;
last_mouse_y = ypos;
}
// Create And Push SDL Mouse Click Event
static void click_event(int button, bool up) {
SDL_Event event;
event.type = up ? SDL_MOUSEBUTTONUP : SDL_MOUSEBUTTONDOWN;
event.button.x = last_mouse_x;
event.button.y = last_mouse_y;
event.button.state = up ? SDL_RELEASED : SDL_PRESSED;
event.button.button = button;
SDL_PushEvent(&event);
}
// Pass Mouse Click To SDL
static void glfw_click_raw(const int button, const int action) {
const bool up = action == GLFW_RELEASE;
const int sdl_button = button == GLFW_MOUSE_BUTTON_RIGHT ? SDL_BUTTON_RIGHT : (button == GLFW_MOUSE_BUTTON_LEFT ? SDL_BUTTON_LEFT : SDL_BUTTON_MIDDLE);
click_event(sdl_button, up);
}
static void glfw_click(__attribute__((unused)) GLFWwindow *window, const int button, const int action, __attribute__((unused)) int mods) {
if (is_interactable) {
glfw_click_raw(button, action);
}
}
// Pass Mouse Scroll To SDL
static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) {
if (is_interactable && yoffset != 0) {
int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN;
click_event(sdl_button, false);
click_event(sdl_button, true);
}
}
// Enable/Disable Raw Mouse Motion
void media_set_raw_mouse_motion_enabled(const int enabled) {
raw_mouse_motion_enabled = enabled;
if (glfw_window) {
glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
}
if (!raw_mouse_motion_enabled) {
WARN("Raw mouse motion has been DISABLED, this IS NOT recommended, and should only ever be used on systems that don't support or have broken raw mouse motion.");
}
}
// Disable V-Sync
static int disable_vsync = 0;
void media_disable_vsync() {
disable_vsync = 1;
if (glfw_window) {
glfwSwapInterval(0);
}
}
// Force EGL
static int force_egl = 0;
void media_force_egl() {
if (force_egl == -1) {
IMPOSSIBLE();
}
force_egl = 1;
}
// Init Media Layer
#define GL_VERSION 0x1f02
typedef const char *(*glGetString_t)(unsigned int name);
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
extern "C" void init_gles_compatibility_layer(void *);
#endif
void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *icon) {
// Disable In Headless Mode
if (reborn_is_headless()) {
return;
}
// Init GLFW
reborn_check_display();
glfwSetErrorCallback(glfw_error);
if (!glfwInit()) {
ERR("Unable To Initialize GLFW");
}
// Create OpenGL ES Context
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
#endif
// Use EGL
if (force_egl) {
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
}
force_egl = -1;
// Extra Settings
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
glfwWindowHint(GLFW_ALPHA_BITS, 0); // Fix Transparent Window On Wayland
// App ID
glfwWindowHintString(GLFW_X11_CLASS_NAME, MCPI_APP_ID);
glfwWindowHintString(GLFW_WAYLAND_APP_ID, MCPI_APP_ID);
// Create Window
glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL);
if (!glfw_window) {
ERR("Unable To Create GLFW Window");
}
// Event Handlers
glfwSetKeyCallback(glfw_window, glfw_key);
glfwSetCharCallback(glfw_window, glfw_char);
glfwSetCursorPosCallback(glfw_window, glfw_motion);
glfwSetMouseButtonCallback(glfw_window, glfw_click);
glfwSetScrollCallback(glfw_window, glfw_scroll);
// Make Window Context Current
glfwMakeContextCurrent(glfw_window);
// Setup Compatibility Layer
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
init_gles_compatibility_layer((void *) glfwGetProcAddress);
#endif
// Debug
glGetString_t glGetString = (glGetString_t) glfwGetProcAddress("glGetString");
DEBUG("Using %s", (*glGetString)(GL_VERSION));
// Init OpenAL
_media_audio_init();
// Update State
update_cursor();
if (disable_vsync) {
media_disable_vsync();
}
// Always Cleanup Media Layer
atexit(media_cleanup);
}
void media_swap_buffers() {
if (glfw_window) {
glfwSwapBuffers(glfw_window);
}
}
// Track Fullscreen
static bool is_fullscreen = false;
// Old Size And Position To Use When Exiting Fullscreen
static int old_width = -1;
static int old_height = -1;
static int old_x = -1;
static int old_y = -1;
// Toggle Fullscreen
void media_toggle_fullscreen() {
if (glfw_window) {
if (is_fullscreen) {
glfwSetWindowMonitor(glfw_window, nullptr, old_x, old_y, old_width, old_height, GLFW_DONT_CARE);
old_width = -1;
old_height = -1;
old_x = -1;
old_y = -1;
} else {
glfwGetWindowSize(glfw_window, &old_width, &old_height);
glfwGetWindowPos(glfw_window, &old_x, &old_y);
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(glfw_window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE);
}
is_fullscreen = !is_fullscreen;
}
}
// Intercept SDL Events
void _media_handle_SDL_PollEvent() {
if (glfw_window) {
// Process GLFW Events
glfwPollEvents();
// Close Window
if (glfwWindowShouldClose(glfw_window)) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
glfwSetWindowShouldClose(glfw_window, GLFW_FALSE);
}
}
}
// Cleanup Media Layer
void media_cleanup() {
if (glfw_window) {
// Ignore GLFW Errors During Termination
glfwSetErrorCallback(nullptr);
// Terminate GLFW
glfwDestroyWindow(glfw_window);
glfwTerminate();
// Cleanup OpenAL
_media_audio_cleanup();
}
}
// Update GLFW Cursor State (Client Only)
static void update_cursor() {
if (glfw_window) {
// Get New State
const bool new_cursor_visible = is_interactable ? cursor_visible : true;
const bool new_cursor_grabbed = is_interactable ? cursor_grabbed : false;
// Store Old Mode
const int old_mode = glfwGetInputMode(glfw_window, GLFW_CURSOR);
// Handle Cursor Visibility
int new_mode;
if (!new_cursor_visible) {
if (new_cursor_grabbed) {
new_mode = GLFW_CURSOR_DISABLED;
} else {
new_mode = GLFW_CURSOR_HIDDEN;
}
} else {
new_mode = GLFW_CURSOR_NORMAL;
}
if (new_mode != old_mode) {
// Ignore Relative Cursor Motion When Locking
if (new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) {
ignore_relative_motion = true;
}
// Set New Mode
glfwSetInputMode(glfw_window, GLFW_CURSOR, new_mode);
// Handle Cursor Lock/Unlock
if ((new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) || (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED)) {
// Use Raw Mouse Motion
if (raw_mouse_motion_enabled) {
glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, new_mode == GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE);
}
// Request Focus
if (!glfwGetWindowAttrib(glfw_window, GLFW_FOCUSED)) {
glfwRequestWindowAttention(glfw_window);
}
}
// Reset Mouse Position When Unlocking
if (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED) {
double cursor_x;
double cursor_y;
glfwGetCursorPos(glfw_window, &cursor_x, &cursor_y);
glfw_motion(glfw_window, cursor_x, cursor_y);
}
}
}
}
// Fix SDL Cursor Visibility/Grabbing
SDL_GrabMode SDL_WM_GrabInput(const SDL_GrabMode mode) {
if (mode == SDL_GRAB_QUERY) {
// Query
return cursor_grabbed ? SDL_GRAB_ON : SDL_GRAB_OFF;
} else if (mode == SDL_GRAB_ON) {
// Store State
cursor_grabbed = true;
} else if (mode == SDL_GRAB_OFF) {
// Store State
cursor_grabbed = false;
}
// Update Cursor GLFW State (Client Only)
update_cursor();
// Return
return mode;
}
// Stub SDL Cursor Visibility
int SDL_ShowCursor(const int toggle) {
if (toggle == SDL_QUERY) {
// Query
return cursor_visible ? SDL_ENABLE : SDL_DISABLE;
} else if (toggle == SDL_ENABLE) {
// Store State
cursor_visible = true;
} else if (toggle == SDL_DISABLE) {
// Store State
cursor_visible = false;
}
// Update Cursor GLFW State (Client Only)
update_cursor();
// Return
return toggle;
}
// Get Framebuffer Size
void media_get_framebuffer_size(int *width, int *height) {
if (glfw_window) {
glfwGetFramebufferSize(glfw_window, width, height);
} else {
*width = DEFAULT_WIDTH;
*height = DEFAULT_HEIGHT;
}
}

View File

@ -1,19 +1,13 @@
project(media-layer-trampoline)
# Configuration
set(MEDIA_LAYER_TRAMPOLINE_SRC src/media-layer-core.cpp) # Media Layer Trampoline Source
if(NOT MCPI_HEADLESS_MODE)
list(APPEND MEDIA_LAYER_TRAMPOLINE_SRC src/GLESv1_CM.cpp)
endif()
# Common Sources
set(MEDIA_LAYER_TRAMPOLINE_SRC src/media-layer-core.cpp src/GLESv1_CM.cpp)
# Build
if(BUILD_NATIVE_COMPONENTS)
# Host Component
add_library(media-layer-trampoline src/host/host.cpp ${MEDIA_LAYER_TRAMPOLINE_SRC})
target_link_libraries(media-layer-trampoline reborn-util media-layer-core trampoline-headers)
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(media-layer-trampoline GLESv1_CM)
endif()
target_link_libraries(media-layer-trampoline reborn-util media-layer-core GLESv1_CM trampoline-headers)
target_compile_definitions(media-layer-trampoline PRIVATE -DMEDIA_LAYER_TRAMPOLINE_HOST)
# Install
install(TARGETS media-layer-trampoline DESTINATION "${MCPI_LIB_DIR}")

View File

@ -46,72 +46,52 @@ set(SRC
src/text-input-box/TextInputScreen.cpp
# test
src/test/test.cpp
# sound
src/sound/sound.cpp
src/sound/repository.cpp
# camera
src/camera/camera.cpp
# input
src/input/input.cpp
src/input/bow.cpp
src/input/attack.cpp
src/input/toggle.cpp
src/input/misc.cpp
src/input/drop.cpp
# sign
src/sign/sign.cpp
# atlas
src/atlas/atlas.cpp
# title-screen
src/title-screen/title-screen.cpp
src/title-screen/splashes.txt # Show In IDE
src/title-screen/welcome.cpp
# skin
src/skin/skin.cpp
src/skin/loader.cpp
# screenshot
src/screenshot/screenshot.cpp
# textures
src/textures/textures.cpp
src/textures/lava.cpp
src/textures/headless.cpp
# fps
src/fps/fps.cpp
# server
src/server/server.cpp
src/server/server_properties.cpp
# multiplayer
src/multiplayer/multiplayer.cpp
# benchmark
src/benchmark/benchmark.cpp
# init
src/init/init.cpp
)
# Server-Only Sources
if(MCPI_SERVER_MODE)
list(APPEND SRC
# server
src/server/server.cpp
src/server/server_properties.cpp
)
else()
list(APPEND SRC
# multiplayer
src/multiplayer/multiplayer.cpp
# benchmark
src/benchmark/benchmark.cpp
)
endif()
# Headless-Only Sources
if(MCPI_HEADLESS_MODE)
list(APPEND SRC
# textures
src/textures/headless.cpp
)
else()
list(APPEND SRC
# sound
src/sound/sound.cpp
src/sound/repository.cpp
# camera
src/camera/camera.cpp
# input
src/input/input.cpp
src/input/bow.cpp
src/input/attack.cpp
src/input/toggle.cpp
src/input/misc.cpp
src/input/drop.cpp
src/input/crafting.cpp
# sign
src/sign/sign.cpp
# atlas
src/atlas/atlas.cpp
# title-screen
src/title-screen/title-screen.cpp
src/title-screen/splashes.txt # Show In IDE
src/title-screen/welcome.cpp
# skin
src/skin/skin.cpp
src/skin/loader.cpp
# screenshot
src/screenshot/screenshot.cpp
# textures
src/textures/textures.cpp
src/textures/lava.cpp
# fps
src/fps/fps.cpp
)
# Install Splashes
install(
FILES "src/title-screen/splashes.txt"
DESTINATION "${MCPI_INSTALL_DIR}/data"
)
endif()
# Install Splashes
install(
FILES "src/title-screen/splashes.txt"
DESTINATION "${MCPI_INSTALL_DIR}/data"
)
# Build
add_library(mods SHARED ${SRC})
@ -122,10 +102,7 @@ install(TARGETS mods DESTINATION "${MCPI_INSTALL_DIR}/mods")
install(TARGETS mods EXPORT sdk DESTINATION "${MCPI_SDK_LIB_DIR}")
# Dependencies
target_link_libraries(mods symbols reborn-patch media-layer-core dl pthread)
if(NOT MCPI_HEADLESS_MODE)
target_link_libraries(mods stb_image)
endif()
target_link_libraries(mods symbols reborn-patch media-layer-core stb_image dl pthread)
# Headers
target_include_directories(

View File

@ -1,16 +1,10 @@
#pragma once
#include <libreborn/libreborn.h>
extern "C" {
bool _feature_has(const char *name);
bool _feature_has(const char *name, int server_default);
}
#ifdef MCPI_SERVER_MODE
#define _feature_has__server_defaul_is_server_disabled(name) 0
#define _feature_has__server_defaul_is_server_auto(name) _feature_has(name)
#define _feature_has__server_defaul_is_server_enabled(name) 1
#define feature_has(name, server_default) _feature_has__server_defaul_is_##server_default(name)
#else
#define feature_has(name, server_default) _feature_has(name)
#endif
#define _feature_has_server_disabled (0)
#define _feature_has_server_auto (-1)
#define _feature_has_server_enabled (1)
#define feature_has(name, server_default) _feature_has(name, _feature_has_##server_default)

View File

@ -0,0 +1,5 @@
#pragma once
#include <string>
int get_seed_from_string(std::string str);

View File

@ -1,5 +1,5 @@
#pragma once
extern "C" {
char *home_get();
const char *home_get();
}

View File

@ -4,13 +4,9 @@ extern "C" {
void run_tests();
void init_version();
void init_compat();
#ifdef MCPI_SERVER_MODE
void init_server();
#else
void init_multiplayer();
void init_benchmark();
#endif
#ifndef MCPI_HEADLESS_MODE
void init_sound();
void init_input();
void init_sign();
@ -19,7 +15,6 @@ void init_atlas();
void init_title_screen();
void init_skin();
void init_fps();
#endif
void init_touch();
void init_textures();
void init_creative();

View File

@ -9,7 +9,6 @@ void input_run_on_tick(input_tick_function_t function);
void input_set_is_right_click(int val);
int input_back();
void input_drop(int drop_slot);
void input_open_crafting();
void input_set_is_left_click(int val);

View File

@ -1,5 +1,5 @@
#pragma once
extern "C" {
void screenshot_take(char *home);
void screenshot_take(const char *home);
}

View File

@ -91,9 +91,6 @@ static void FurnaceScreen_render_ItemRenderer_renderGuiItem_one_injection(Font *
// Init
void init_atlas() {
// Add Better nullptr-Check (And More UI Fixes When The gui_blocks Atlas Is Disabled)
overwrite_calls(ItemRenderer_renderGuiItem_two, ItemRenderer_renderGuiItem_two_injection);
// Disable The gui_blocks Atlas Which Contains Pre-Rendered Textures For Blocks In The Inventory
if (feature_has("Disable \"gui_blocks\" Atlas", server_disabled)) {
unsigned char disable_gui_blocks_atlas_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
@ -105,5 +102,6 @@ void init_atlas() {
overwrite_calls(Tesselator_color, Tesselator_color_injection);
overwrite_call((void *) 0x32324, (void *) FurnaceScreen_render_ItemRenderer_renderGuiItem_one_injection);
overwrite_call((void *) 0x1e21c, (void *) InventoryPane_renderBatch_Tesselator_color_injection);
overwrite_calls(ItemRenderer_renderGuiItem_two, ItemRenderer_renderGuiItem_two_injection);
}
}

View File

@ -47,14 +47,12 @@ static void start_world(Minecraft *minecraft) {
}
// Track Frames
#ifndef MCPI_HEADLESS_MODE
static unsigned long long int frames = 0;
HOOK(media_swap_buffers, void, ()) {
ensure_media_swap_buffers();
real_media_swap_buffers();
frames++;
}
#endif
// Track Ticks
static unsigned long long int ticks = 0;
@ -74,9 +72,7 @@ static long long int get_time() {
// Store Time When World Loaded
static bool world_loaded = false;
static long long int world_loaded_time;
#ifndef MCPI_HEADLESS_MODE
static unsigned long long int world_loaded_frames;
#endif
static unsigned long long int world_loaded_ticks;
// Last Logged Status Update
@ -98,11 +94,9 @@ static void Minecraft_update_injection(Minecraft *minecraft) {
// Detect World Loaded
if (!world_loaded && minecraft->isLevelGenerated()) {
world_loaded = true;
INFO("Loaded");
INFO("Benchmark Loaded");
world_loaded_time = now;
#ifndef MCPI_HEADLESS_MODE
world_loaded_frames = frames;
#endif
world_loaded_ticks = ticks;
}
@ -110,9 +104,7 @@ static void Minecraft_update_injection(Minecraft *minecraft) {
if (!exit_requested && world_loaded) {
// Get Time
long long int current_time = now - world_loaded_time;
#ifndef MCPI_HEADLESS_MODE
unsigned long long int current_frames = frames - world_loaded_frames;
#endif
unsigned long long int current_ticks = ticks - world_loaded_ticks;
// Log
@ -151,11 +143,11 @@ static void Minecraft_update_injection(Minecraft *minecraft) {
// Calculate FPS & TPS
INFO("Benchmark Completed");
INFO(" Total Time: %lld Nanoseconds", current_time);
#ifndef MCPI_HEADLESS_MODE
static double frames_per_nanosecond = ((double) current_frames) / ((double) current_time);
static double frames_per_second = frames_per_nanosecond * NANOSECONDS_IN_SECOND;
INFO(" FPS: %f (%llu Total Frames)", frames_per_second, current_frames);
#endif
if (!reborn_is_headless()) {
static double frames_per_nanosecond = ((double) current_frames) / ((double) current_time);
static double frames_per_second = frames_per_nanosecond * NANOSECONDS_IN_SECOND;
INFO(" FPS: %f (%llu Total Frames)", frames_per_second, current_frames);
}
static double ticks_per_nanosecond = ((double) current_ticks) / ((double) current_time);
static double ticks_per_second = ticks_per_nanosecond * NANOSECONDS_IN_SECOND;
INFO(" TPS: %f (%llu Total Ticks)", ticks_per_second, current_ticks);

View File

@ -8,9 +8,7 @@
// Take Screenshot Using TripodCamera
static void AppPlatform_saveScreenshot_injection(__attribute__((unused)) AppPlatform_saveScreenshot_t original, __attribute__((unused)) AppPlatform *app_platform, __attribute__((unused)) std::string *path, __attribute__((unused)) int32_t width, __attribute__((unused)) int32_t height) {
#ifndef MCPI_HEADLESS_MODE
screenshot_take(home_get());
#endif
}
// Enable TripodCameraRenderer

View File

@ -10,15 +10,12 @@
#include <libreborn/libreborn.h>
#include <SDL/SDL.h>
#ifndef MCPI_HEADLESS_MODE
#include <media-layer/core.h>
#include <mods/input/input.h>
#include <mods/sign/sign.h>
#include <mods/chat/chat.h>
#include <mods/home/home.h>
#endif
// Custom Title
HOOK(SDL_WM_SetCaption, void, (__attribute__((unused)) const char *title, const char *icon)) {
@ -37,14 +34,12 @@ HOOK(SDL_ShowCursor, int, (int toggle)) {
HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
// In Server Mode, Exit Requests Are Handled In src/server/server.cpp
// Check If Exit Is Requested
#ifndef MCPI_SERVER_MODE
if (compat_check_exit_requested()) {
if (!reborn_is_server() && compat_check_exit_requested()) {
// Send SDL_QUIT
SDL_Event new_event;
new_event.type = SDL_QUIT;
SDL_PushEvent(&new_event);
}
#endif
// Poll Events
ensure_SDL_PollEvent();
@ -53,8 +48,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
// Handle Events
if (ret == 1 && event != nullptr) {
int handled = 0;
#ifndef MCPI_HEADLESS_MODE
switch (event->type) {
case SDL_KEYDOWN: {
// Handle Key Presses
@ -71,10 +64,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
// Drop Item
input_drop((event->key.keysym.mod & KMOD_CTRL) != 0);
handled = 1;
} else if (event->key.keysym.sym == SDLK_WORLD_0) {
// Crafting
input_open_crafting();
handled = 1;
}
break;
}
@ -97,8 +86,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
break;
}
}
#endif
if (handled) {
// Event Was Handled
return SDL_PollEvent(event);

View File

@ -6,7 +6,6 @@
#include <mods/misc/misc.h>
#include <mods/creative/creative.h>
#ifndef MCPI_SERVER_MODE
// Add Item To Inventory
static void inventory_add_item(FillingContainer *inventory, Item *item) {
ItemInstance *item_instance = new ItemInstance;
@ -83,7 +82,6 @@ static void Inventory_setupDefault_FillingContainer_addItem_call_injection(Filli
filling_container->addItem(new_item_instance);
}
}
#endif
// Hook Specific TileItem Constructor
static TileItem *Tile_initTiles_TileItem_injection(TileItem *tile_item, int32_t id) {
@ -111,9 +109,7 @@ int creative_is_restricted() {
void init_creative() {
// Add Extra Items To Creative Inventory (Only Replace Specific Function Call)
if (feature_has("Expand Creative Mode Inventory", server_enabled)) {
#ifndef MCPI_SERVER_MODE
misc_run_on_creative_inventory_setup(Inventory_setupDefault_FillingContainer_addItem_call_injection);
#endif
// Use AuxDataTileItem by default instead of TileItem, so tiles in the Creative
// Inventory can have arbitrary auxiliary values.

View File

@ -6,7 +6,11 @@
#include <mods/feature/feature.h>
// Check For Feature
bool _feature_has(const char *name) {
bool _feature_has(const char *name, int server_default) {
// Server Handling
if (reborn_is_server() && server_default != -1) {
return server_default > 0;
}
// Get Value
char *env = getenv("MCPI_FEATURE_FLAGS");
char *features = strdup(env != nullptr ? env : "");

View File

@ -1,5 +1,6 @@
#include <string>
#include <set>
#include <utility>
#include <symbols/minecraft.h>
#include <libreborn/libreborn.h>
@ -7,6 +8,7 @@
#include <mods/text-input-box/TextInputScreen.h>
#include <mods/touch/touch.h>
#include <mods/misc/misc.h>
#include <mods/game-mode/game-mode.h>
#include "game-mode-internal.h"
// Strings
@ -175,20 +177,24 @@ static std::string getUniqueLevelName(LevelStorageSource *source, const std::str
}
// Create World
static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed_str) {
// Get Seed
int get_seed_from_string(std::string str) {
int seed;
seed_str = Util::stringTrim(&seed_str);
if (!seed_str.empty()) {
str = Util::stringTrim(&str);
if (!str.empty()) {
int num;
if (sscanf(seed_str.c_str(), "%d", &num) > 0) {
if (sscanf(str.c_str(), "%d", &num) > 0) {
seed = num;
} else {
seed = Util::hashCode(&seed_str);
seed = Util::hashCode(&str);
}
} else {
seed = Common::getEpochTimeS();
}
return seed;
}
static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed_str) {
// Get Seed
int seed = get_seed_from_string(std::move(seed_str));
// Get Folder Name
name = Util::stringTrim(&name);

View File

@ -7,24 +7,20 @@
#include <mods/init/init.h>
// Get MCPI Home Directory
char *home_get() {
static char *dir = nullptr;
const char *home_get() {
static std::string dir = "";
// Load
if (dir == nullptr) {
safe_asprintf(&dir, "%s" HOME_SUBDIRECTORY_FOR_GAME_DATA, getenv("HOME"));
if (dir.empty()) {
dir = std::string(getenv("HOME")) + std::string(get_home_subdirectory_for_game_data());
}
// Return
return dir;
}
// Free
__attribute__((destructor)) static void _free_home() {
free(home_get());
return dir.c_str();
}
// Init
void init_home() {
// Store Data In ~/.minecraft-pi Instead Of ~/.minecraft
patch_address((void *) &Strings::default_path, (void *) HOME_SUBDIRECTORY_FOR_GAME_DATA);
patch_address((void *) &Strings::default_path, (void *) get_home_subdirectory_for_game_data());
// The override code resolves assets manually,
// making changing directory redundant.

View File

@ -8,12 +8,11 @@ __attribute__((constructor)) static void init() {
run_tests();
init_version();
init_compat();
#ifdef MCPI_SERVER_MODE
init_server();
#else
init_multiplayer();
#endif
#ifndef MCPI_HEADLESS_MODE
if (reborn_is_server()) {
init_server();
} else {
init_multiplayer();
}
init_sound();
init_input();
init_sign();
@ -22,7 +21,6 @@ __attribute__((constructor)) static void init() {
init_title_screen();
init_skin();
init_fps();
#endif
init_touch();
init_textures();
init_creative();
@ -34,7 +32,7 @@ __attribute__((constructor)) static void init() {
init_bucket();
init_cake();
init_home();
#ifndef MCPI_SERVER_MODE
init_benchmark();
#endif
if (!reborn_is_server()) {
init_benchmark();
}
}

View File

@ -1,30 +0,0 @@
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include "input-internal.h"
#include <mods/input/input.h>
#include <mods/creative/creative.h>
// Store Should Open Crafting Menu
static int should_open_crafting = 0;
void input_open_crafting() {
should_open_crafting = 1;
}
static void _handle_open_crafting(Minecraft *minecraft) {
if (should_open_crafting) {
should_open_crafting = 0;
// Set Screen
if (!creative_is_restricted() || !Minecraft_isCreativeMode(minecraft)) {
WorkbenchScreen *screen = new WorkbenchScreen;
ALLOC_CHECK(screen);
screen = screen->constructor(0);
minecraft->setScreen((Screen *) screen);
}
}
}
// Init
void _init_crafting() {
input_run_on_tick(_handle_open_crafting);
}

View File

@ -5,4 +5,3 @@ __attribute__((visibility("internal"))) void _init_bow();
__attribute__((visibility("internal"))) void _init_misc();
__attribute__((visibility("internal"))) void _init_toggle();
__attribute__((visibility("internal"))) void _init_drop();
__attribute__((visibility("internal"))) void _init_crafting();

View File

@ -49,9 +49,6 @@ void init_input() {
// Allow Attacking Mobs
_init_attack();
// Allow Opening Crafting With Controller
_init_crafting();
// Disable Raw Mouse Motion
if (feature_has("Disable Raw Mouse Motion (Not Recommended)", server_disabled)) {
media_set_raw_mouse_motion_enabled(0);

View File

@ -3,9 +3,7 @@
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#ifndef MCPI_HEADLESS_MODE
#include <GLES/gl.h>
#endif
#include <mods/misc/misc.h>
#include "misc-internal.h"
@ -95,9 +93,7 @@ void misc_run_on_game_key_press(const std::function<bool(Minecraft *, int)> &fun
// Render Fancy Background
void misc_render_background(int color, Minecraft *minecraft, int x, int y, int width, int height) {
// https://github.com/ReMinecraftPE/mcpe/blob/f0d65eaecec1b3fe9c2f2b251e114a890c54ab77/source/client/gui/components/RolledSelectionList.cpp#L169-L179
#ifndef MCPI_HEADLESS_MODE
glColor4f(1, 1, 1, 1);
#endif
std::string texture = "gui/background.png";
minecraft->textures->loadAndBindTexture(&texture);
Tesselator *t = &Tesselator::instance;

View File

@ -9,9 +9,7 @@
#include <streambuf>
#include <algorithm>
#ifndef MCPI_HEADLESS_MODE
#include <GLES/gl.h>
#endif
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
@ -143,9 +141,7 @@ static void Gui_renderChatMessages_injection(Gui_renderChatMessages_t original,
// Render Selected Item Text
if (render_selected_item_text) {
// Fix GL Mode
#ifndef MCPI_HEADLESS_MODE
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
// Calculate Selected Item Text Scale
Minecraft *minecraft = gui->minecraft;
int32_t screen_width = minecraft->screen_width;
@ -184,37 +180,25 @@ static void Inventory_selectSlot_injection(Inventory_selectSlot_t original, Inve
// Translucent Toolbar
static void Gui_renderToolBar_injection(Gui_renderToolBar_t original, Gui *gui, float param_1, int32_t param_2, int32_t param_3) {
// Call Original Method
#ifndef MCPI_HEADLESS_MODE
int was_blend_enabled = glIsEnabled(GL_BLEND);
bool was_blend_enabled = glIsEnabled(GL_BLEND);
if (!was_blend_enabled) {
glEnable(GL_BLEND);
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
original(gui, param_1, param_2, param_3);
#ifndef MCPI_HEADLESS_MODE
if (!was_blend_enabled) {
glDisable(GL_BLEND);
}
#endif
}
static void Gui_renderToolBar_glColor4f_injection(GLfloat red, GLfloat green, GLfloat blue, __attribute__((unused)) GLfloat alpha) {
// Fix Alpha
#ifndef MCPI_HEADLESS_MODE
glColor4f(red, green, blue, 1.0f);
#else
(void) red;
(void) green;
(void) blue;
#endif
}
// Fix Screen Rendering When GUI is Hidden
static void Screen_render_injection(Screen_render_t original, Screen *screen, int32_t param_1, int32_t param_2, float param_3) {
// Fix
#ifndef MCPI_HEADLESS_MODE
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
// Call Original Method
original(screen, param_1, param_2, param_3);
}
@ -265,18 +249,21 @@ static const char *RAKNET_ERROR_NAMES[] = {
"Couldn't Generate GUID",
"Unknown"
};
#ifdef MCPI_SERVER_MODE
#define PRINT_RAKNET_STARTUP_FAILURE ERR
#else
#define PRINT_RAKNET_STARTUP_FAILURE WARN
#endif
#define CONDITIONAL_ERR(is_error, ...) \
{ \
if ((is_error)) { \
ERR(__VA_ARGS__); \
} else { \
WARN(__VA_ARGS__); \
} \
}
static RakNet_StartupResult RakNetInstance_host_RakNet_RakPeer_Startup_injection(RakNet_RakPeer *rak_peer, unsigned short maxConnections, unsigned char *socketDescriptors, uint32_t socketDescriptorCount, int32_t threadPriority) {
// Call Original Method
RakNet_StartupResult result = rak_peer->Startup(maxConnections, socketDescriptors, socketDescriptorCount, threadPriority);
// Print Error
if (result != RAKNET_STARTED) {
PRINT_RAKNET_STARTUP_FAILURE("Failed To Start RakNet: %s", RAKNET_ERROR_NAMES[result]);
CONDITIONAL_ERR(reborn_is_server(), "Failed To Start RakNet: %s", RAKNET_ERROR_NAMES[result]);
}
// Return
@ -339,7 +326,6 @@ static int32_t FurnaceScreen_handleAddItem_injection(FurnaceScreen_handleAddItem
//
// The default behavior for Touch GUI is to only render the cursor when the mouse is clicking, this fixes that.
// This also makes the cursor always render if the mouse is unlocked, instead of just when there is a Screen showing.
#ifndef MCPI_HEADLESS_MODE
static void GameRenderer_render_injection(GameRenderer_render_t original, GameRenderer *game_renderer, float param_1) {
// Call Original Method
original(game_renderer, param_1);
@ -356,7 +342,6 @@ static void GameRenderer_render_injection(GameRenderer_render_t original, GameRe
Common::renderCursor(x, y, minecraft);
}
}
#endif
// Get Real Selected Slot
int32_t misc_get_real_selected_slot(Player *player) {
@ -375,12 +360,12 @@ int32_t misc_get_real_selected_slot(Player *player) {
return selected_slot;
}
#ifndef MCPI_HEADLESS_MODE
// Properly Generate Buffers
static void anGenBuffers_injection(int32_t count, uint32_t *buffers) {
glGenBuffers(count, buffers);
if (!reborn_is_headless()) {
glGenBuffers(count, buffers);
}
}
#endif
// Fix Graphics Bug When Switching To First-Person While Sneaking
static void PlayerRenderer_render_injection(PlayerRenderer *model_renderer, Entity *entity, float param_2, float param_3, float param_4, float param_5, float param_6) {
@ -564,9 +549,8 @@ static ContainerMenu *ContainerMenu_destructor_injection(ContainerMenu_destructo
return original(container_menu);
}
#ifndef MCPI_HEADLESS_MODE
// Custom Outline Color
static void glColor4f_injection(__attribute__((unused)) GLfloat red, __attribute__((unused)) GLfloat green, __attribute__((unused)) GLfloat blue, __attribute__((unused)) GLfloat alpha) {
static void LevelRenderer_render_AABB_glColor4f_injection(__attribute__((unused)) GLfloat red, __attribute__((unused)) GLfloat green, __attribute__((unused)) GLfloat blue, __attribute__((unused)) GLfloat alpha) {
// Set Color
glColor4f(0, 0, 0, 0.4);
@ -591,7 +575,6 @@ static void glColor4f_injection(__attribute__((unused)) GLfloat red, __attribute
// Set Line Width
glLineWidth(line_width);
}
#endif
// Fix Furnace Visual Bug
static int FurnaceTileEntity_getLitProgress_injection(FurnaceTileEntity_getLitProgress_t original, FurnaceTileEntity *furnace, int max) {
@ -756,7 +739,8 @@ static std::string AppPlatform_linux_getDateString_injection(__attribute__((unus
}
// Init
static void nop() {
template <typename... Args>
static void nop(__attribute__((unused)) Args... args) {
}
void init_misc() {
// Remove Invalid Item Background (A Red Background That Appears For Items That Are Not Included In The gui_blocks Atlas)
@ -820,13 +804,6 @@ void init_misc() {
overwrite_calls(FurnaceScreen_handleAddItem, FurnaceScreen_handleAddItem_injection);
}
#ifdef MCPI_HEADLESS_MODE
// Don't Render Game In Headless Mode
overwrite_manual((void *) GameRenderer_render, (void *) nop);
overwrite_manual((void *) NinecraftApp_initGLStates, (void *) nop);
overwrite_manual((void *) Gui_onConfigChanged, (void *) nop);
overwrite_manual((void *) LevelRenderer_generateSky, (void *) nop);
#else
// Improved Cursor Rendering
if (feature_has("Improved Cursor Rendering", server_disabled)) {
// Disable Normal Cursor Rendering
@ -835,7 +812,6 @@ void init_misc() {
// Add Custom Cursor Rendering
overwrite_calls(GameRenderer_render, GameRenderer_render_injection);
}
#endif
// Disable V-Sync
if (feature_has("Disable V-Sync", server_enabled)) {
@ -849,13 +825,11 @@ void init_misc() {
// Remove Forced GUI Lag
if (feature_has("Remove Forced GUI Lag (Can Break Joining Servers)", server_enabled)) {
overwrite_manual((void *) Common_sleepMs, (void *) nop);
overwrite_calls(Common_sleepMs, nop<Common_sleepMs_t, int>);
}
#ifndef MCPI_HEADLESS_MODE
// Properly Generate Buffers
overwrite(Common_anGenBuffers, anGenBuffers_injection);
#endif
// Fix Graphics Bug When Switching To First-Person While Sneaking
patch_vtable(PlayerRenderer_render, PlayerRenderer_render_injection);
@ -928,15 +902,13 @@ void init_misc() {
}
overwrite_calls(ChestTileEntity_shouldSave, ChestTileEntity_shouldSave_injection);
#ifndef MCPI_HEADLESS_MODE
// Replace Block Highlight With Outline
if (feature_has("Replace Block Highlight With Outline", server_disabled)) {
overwrite(LevelRenderer_renderHitSelect, LevelRenderer_renderHitOutline);
unsigned char fix_outline_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0x4d830, fix_outline_patch);
overwrite_call((void *) 0x4d764, (void *) glColor4f_injection);
overwrite_call((void *) 0x4d764, (void *) LevelRenderer_render_AABB_glColor4f_injection);
}
#endif
// Fix Furnace Visual Bug
overwrite_calls(FurnaceTileEntity_getLitProgress, FurnaceTileEntity_getLitProgress_injection);
@ -999,4 +971,12 @@ void init_misc() {
// Init Logging
_init_misc_logging();
_init_misc_api();
// Don't Render Game In Headless Mode
if (reborn_is_headless()) {
overwrite_calls(GameRenderer_render, nop<GameRenderer_render_t, GameRenderer *, float>);
overwrite_calls(NinecraftApp_initGLStates, nop<NinecraftApp_initGLStates_t, NinecraftApp *>);
overwrite_calls(Gui_onConfigChanged, nop<Gui_onConfigChanged_t, Gui *, Config *>);
overwrite_calls(LevelRenderer_generateSky, nop<LevelRenderer_generateSky_t, LevelRenderer *>);
}
}

View File

@ -1,16 +1,10 @@
// Config Needs To Load First
#include <libreborn/libreborn.h>
#ifdef MCPI_SERVER_MODE
#error "External Server Code Requires Client Mode"
#endif
#include <fstream>
#include <functional>
#include <string>
#include <vector>
#include <symbols/minecraft.h>
#include <libreborn/libreborn.h>
#include <mods/home/home.h>
#include <mods/init/init.h>
@ -21,7 +15,7 @@ struct server_list_entry {
std::string address;
int port;
};
static std::vector<struct server_list_entry> server_list_entries;
static std::vector<server_list_entry> server_list_entries;
static bool server_list_loaded = false;
static void load_servers() {
// Prepare
@ -54,40 +48,34 @@ static void load_servers() {
std::string line;
while (std::getline(server_list_file, line)) {
// Check Line
if (line.length() > 0) {
if (!line.empty()) {
if (line[0] == '#') {
continue;
}
// Parse
std::string address;
std::string port_str;
// Add Default Port If Needed
size_t last_colon = line.find_last_of(':');
if (last_colon == std::string::npos) {
line.append(":19132");
last_colon = line.find_last_of(':');
}
// Loop
for (std::string::size_type i = 0; i < line.length(); i++) {
if (i > last_colon) {
port_str.push_back(line[i]);
} else if (i < last_colon) {
address.push_back(line[i]);
}
size_t separator_pos = line.find_last_of('|');
if (separator_pos == std::string::npos) {
port_str = "19132";
address = line;
} else {
address = line.substr(0, separator_pos);
port_str = line.substr(separator_pos + 1);
}
// Check Line
if (address.length() < 1 || port_str.length() < 1 || port_str.find_first_not_of("0123456789") != std::string::npos) {
if (address.empty() || port_str.empty() || port_str.find_first_not_of("0123456789") != std::string::npos) {
// Invalid Line
WARN("Invalid Server: %s", line.c_str());
continue;
}
// Parse Port
int port = std::stoi(port_str);
// Done
struct server_list_entry entry;
entry.address = address;
entry.port = port;
server_list_entry entry = {
.address = address,
.port = port
};
server_list_entries.push_back(entry);
}
}
@ -98,7 +86,7 @@ static void load_servers() {
}
// Iterare Server List
static void iterate_servers(std::function<void(const char *address, int port)> callback) {
static void iterate_servers(const std::function<void(const char *address, int port)> &callback) {
// Load
if (!server_list_loaded) {
load_servers();
@ -106,8 +94,7 @@ static void iterate_servers(std::function<void(const char *address, int port)> c
}
// Loop
for (std::vector<struct server_list_entry>::size_type i = 0; i < server_list_entries.size(); i++) {
struct server_list_entry entry = server_list_entries[i];
for (const server_list_entry &entry : server_list_entries) {
callback(entry.address.c_str(), entry.port);
}
}
@ -122,7 +109,7 @@ static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t
// Add External Servers
iterate_servers([rak_peer](const char *address, int port) {
rak_peer->Ping(address, port, 1, 0);
rak_peer->Ping(address, port, true, 0);
});
}

View File

@ -40,9 +40,9 @@ static std::string extra_version_info_full = !extra_version_info.empty() ? (" ("
// Profile Directory
static std::string profile_directory_suffix =
#ifdef MCPI_IS_FLATPAK_BUILD
"/.var/app/" MCPI_APP_ID
"/.var/app/" MCPI_APP_ID +
#endif
HOME_SUBDIRECTORY_FOR_GAME_DATA
std::string(get_home_subdirectory_for_game_data())
;
static std::string get_profile_directory_url() {
const char *home = getenv("HOME");

View File

@ -57,10 +57,8 @@ static void Options_initDefaultValue_injection(Options_initDefaultValue_t origin
original(options);
// Default Graphics Settings
#ifndef MCPI_SERVER_MODE
options->fancy_graphics = true;
options->ambient_occlusion = true;
#endif
// Store
stored_options = options;
@ -133,26 +131,15 @@ static std::vector<std::string> OptionsFile_getOptionStrings_injection(OptionsFi
}
// Get New options.txt Path
#ifndef MCPI_SERVER_MODE
static char *get_new_options_txt_path() {
static char *path = nullptr;
static const char *get_new_options_txt_path() {
static std::string path = "";
// Path
if (path == nullptr) {
safe_asprintf(&path, "%s/options.txt", home_get());
if (path.empty()) {
path = !reborn_is_server() ? (std::string(home_get()) + "/options.txt") : "/dev/null";
}
// Return
return path;
return path.c_str();
}
// Free
__attribute__((destructor)) static void _free_new_options_txt_path() {
free(get_new_options_txt_path());
}
#else
static char *get_new_options_txt_path() {
// Block options.txt On Servers
return (char *) "/dev/null";
}
#endif
// Init
void init_options() {

View File

@ -34,34 +34,32 @@ char *override_get_path(const char *filename) {
}
// Get MCPI Home Path
char *home_path = home_get();
const std::string home_path = home_get();
// Get Asset Override Path
char *overrides = nullptr;
safe_asprintf(&overrides, "%s/overrides", home_path);
const std::string overrides = home_path + "/overrides";
// Data Prefiix
const char *data_prefix = "data/";
int data_prefix_length = strlen(data_prefix);
const std::string data_prefix = "data/";
int data_prefix_length = data_prefix.length();
// Folders To Check
char *asset_folders[] = {
std::string asset_folders[] = {
overrides,
getenv("MCPI_REBORN_ASSETS_PATH"),
getenv("MCPI_VANILLA_ASSETS_PATH"),
nullptr
""
};
// Check For Override
char *new_path = nullptr;
if (starts_with(filename, data_prefix)) {
std::string new_path;
if (std::string(filename).rfind(data_prefix, 0) == 0) {
// Test Asset Folders
for (int i = 0; asset_folders[i] != nullptr; i++) {
safe_asprintf(&new_path, "%s/%s", asset_folders[i], &filename[data_prefix_length]);
for (int i = 0; !asset_folders[i].empty(); i++) {
new_path = asset_folders[i] + '/' + &filename[data_prefix_length];
ensure_access();
if (real_access(new_path, F_OK) == -1) {
if (real_access(new_path.c_str(), F_OK) == -1) {
// Not Found In Asset Folder
free(new_path);
new_path = nullptr;
new_path = "";
continue;
} else {
// Found
@ -70,11 +68,14 @@ char *override_get_path(const char *filename) {
}
}
// Free
free(overrides);
// Return
return new_path;
if (new_path.empty()) {
return nullptr;
} else {
char *ret = strdup(new_path.c_str());
ALLOC_CHECK(ret);
return ret;
}
}
// Hook fopen

View File

@ -38,7 +38,12 @@ static int save_png(const char *filename, unsigned char *pixels, int line_size,
// Write Image
return !stbi_write_png(filename, width, height, 4, pixels, line_size);
}
void screenshot_take(char *home) {
void screenshot_take(const char *home) {
// Check
if (reborn_is_headless()) {
IMPOSSIBLE();
}
// Get Directory
char *screenshots = nullptr;
safe_asprintf(&screenshots, "%s/screenshots", home);

View File

@ -1,12 +1,5 @@
// Config Needs To Load First
#include <libreborn/libreborn.h>
#ifndef MCPI_SERVER_MODE
#error "Server Code Requires Server Mode"
#endif
#include <string>
#include <stdint.h>
#include <cstdint>
#include <ctime>
#include <cstdio>
#include <fstream>
@ -19,15 +12,16 @@
#include <SDL/SDL.h>
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <mods/server/server.h>
#include <mods/feature/feature.h>
#include <mods/init/init.h>
#include <mods/home/home.h>
#include <mods/compat/compat.h>
#include <mods/misc/misc.h>
#include <mods/game-mode/game-mode.h>
// --only-generate: Ony Generate World And Then Exit
static bool only_generate = false;
@ -79,8 +73,8 @@ static void start_world(Minecraft *minecraft) {
// Specify Level Settings
LevelSettings settings;
settings.game_type = get_server_properties().get_int("game-mode", DEFAULT_GAME_MODE);
std::string seed_str = get_server_properties().get_string("seed", DEFAULT_SEED);
int32_t seed = seed_str.length() > 0 ? std::stoi(seed_str) : time(nullptr);
const std::string seed_str = get_server_properties().get_string("seed", DEFAULT_SEED);
const int32_t seed = get_seed_from_string(seed_str);
settings.seed = seed;
// Select Level
@ -89,7 +83,7 @@ static void start_world(Minecraft *minecraft) {
// Don't Open Port When Using --only-generate
if (!only_generate) {
// Open Port
int port = get_server_properties().get_int("port", DEFAULT_PORT);
const int port = get_server_properties().get_int("port", DEFAULT_PORT);
INFO("Listening On: %i", port);
minecraft->hostMultiplayer(port);
}
@ -130,8 +124,8 @@ static Level *get_level(Minecraft *minecraft) {
}
// Find Players With Username And Run Callback
typedef void (*player_callback_t)(Minecraft *minecraft, std::string username, Player *player);
static void find_players(Minecraft *minecraft, std::string target_username, player_callback_t callback, bool all_players) {
typedef void (*player_callback_t)(Minecraft *minecraft, const std::string &username, Player *player);
static void find_players(Minecraft *minecraft, const std::string &target_username, player_callback_t callback, bool all_players) {
Level *level = get_level(minecraft);
std::vector<Player *> players = get_players_in_level(level);
bool found_player = false;
@ -177,7 +171,7 @@ static char *get_player_ip(Minecraft *minecraft, Player *player) {
// Ban Player
static bool is_ip_in_blacklist(const char *ip);
static void ban_callback(Minecraft *minecraft, std::string username, Player *player) {
static void ban_callback(Minecraft *minecraft, const std::string &username, Player *player) {
// Get IP
char *ip = get_player_ip(minecraft, player);
@ -198,13 +192,13 @@ static void ban_callback(Minecraft *minecraft, std::string username, Player *pla
}
// Kill Player
static void kill_callback(__attribute__((unused)) Minecraft *minecraft, __attribute__((unused)) std::string username, Player *player) {
static void kill_callback(__attribute__((unused)) Minecraft *minecraft, __attribute__((unused)) const std::string &username, Player *player) {
player->hurt(nullptr, INT32_MAX);
INFO("Killed: %s", username.c_str());
}
// List Player
static void list_callback(Minecraft *minecraft, std::string username, Player *player) {
static void list_callback(Minecraft *minecraft, const std::string &username, Player *player) {
INFO(" - %s (%s)", username.c_str(), get_player_ip(minecraft, player));
}
@ -416,7 +410,7 @@ static bool is_ip_in_blacklist(const char *ip) {
} else {
// Check List
for (std::string &x : ips) {
if (x.compare(ip) == 0) {
if (x == ip) {
return true;
}
}

View File

@ -79,6 +79,10 @@ static int32_t Textures_loadAndBindTexture_injection(Textures *textures, __attri
// Init
void init_skin() {
// Not Needed On Headless Mode
if (reborn_is_headless()) {
return;
}
// Check Feature Flag
if (feature_has("Load Custom Skins", server_disabled)) {
// LocalPlayer

View File

@ -117,6 +117,10 @@ static void SoundEngine_init_injection(SoundEngine_init_t original, SoundEngine
// Init
void init_sound() {
// Not Needed On Headless Mode
if (reborn_is_headless()) {
return;
}
// Implement Sound Engine
if (feature_has("Implement Sound Engine", server_disabled)) {
overwrite(SoundEngine_playUI, SoundEngine_playUI_injection);

View File

@ -10,10 +10,9 @@
void run_tests() {
// Test ~/.minecraft-pi Permissions
{
char *path = home_get();
const char *path = home_get();
int exists = access(path, F_OK) == 0;
int can_write = exists ? access(path, R_OK | W_OK) == 0 : 1;
if (!can_write) {
// Failure
ERR("Invalid Data Directory Permissions");

View File

@ -1,6 +1,7 @@
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <mods/init/init.h>
#include "textures-internal.h"
// Disable Texture Loading
static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) AppPlatform_linux_loadTexture_t original, __attribute__((unused)) AppPlatform_linux *app_platform, __attribute__((unused)) std::string *path, __attribute__((unused)) bool b) {
@ -17,7 +18,7 @@ static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) A
}
// Init
void init_textures() {
void _init_textures_headless() {
// Disable Texture Loading
overwrite_calls(AppPlatform_linux_loadTexture, AppPlatform_linux_loadTexture_injection);
}

View File

@ -1,3 +1,4 @@
#pragma once
__attribute__((visibility("internal"))) void _init_textures_headless();
__attribute__((visibility("internal"))) void _init_textures_lava(bool animated_water, bool animated_lava, bool animated_fire);

View File

@ -213,6 +213,12 @@ static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) A
// Init
void init_textures() {
// Handle Headless Mode
if (reborn_is_headless()) {
_init_textures_headless();
return;
}
// Tick Dynamic Textures (Animated Water)
bool animated_water = feature_has("Animated Water", server_disabled);
bool animated_lava = feature_has("Animated Lava", server_disabled);

View File

@ -10,7 +10,7 @@
#include "title-screen-internal.h"
// Constants
static std::string line1 = "Welcome to " MCPI_APP_BASE_TITLE " v" MCPI_VERSION "!";
static std::string line1 = "Welcome to " MCPI_APP_TITLE " v" MCPI_VERSION "!";
static int line_height = 8;
static int button_width = 120;
static int button_height = 24;

View File

@ -47,10 +47,6 @@ const PackageTypes = wrap(new Enum([
'AppImage',
'Flatpak'
]));
const Variants = wrap(new Enum([
'Client',
'Server'
]));
const Architectures = wrap(new Enum([
'AMD64',
'ARM64',
@ -67,7 +63,6 @@ let out = path.join(root, 'out');
// Positional Arguments
let argIndex = 2; // Skip First Two Arguments
const POSITIONAL_ARGUMENT_COUNT = 3;
function readArg(from, type) {
// Check Argument Count
if (argIndex >= process.argv.length) {
@ -84,8 +79,6 @@ function readArg(from, type) {
}
// Type Of Packaging
const packageType = readArg(PackageTypes, 'Package Type');
// Build Variant
const variant = readArg(Variants, 'Variant');
// Build Architecture
const architecture = readArg(Architectures, 'Architecture');
// Flatpak Builds Work Best Without Custom Toolchains
@ -133,7 +126,7 @@ function updateDir(dir) {
if (packageType !== PackageTypes.None) {
dir = path.join(dir, packageType.name);
}
return path.join(dir, variant.name, architecture.name);
return path.join(dir, architecture.name);
}
build = updateDir(build);
let cleanOut = false;
@ -147,7 +140,6 @@ if (packageType !== PackageTypes.AppImage) {
function toCmakeBool(val) {
return val ? 'ON' : 'OFF';
}
options.set('MCPI_SERVER_MODE', toCmakeBool(variant === Variants.Server));
options.set('MCPI_IS_APPIMAGE_BUILD', toCmakeBool(packageType === PackageTypes.AppImage));
options.set('MCPI_IS_FLATPAK_BUILD', toCmakeBool(packageType === PackageTypes.Flatpak));
if (architecture !== Architectures.Host) {

View File

@ -0,0 +1,3 @@
#!/bin/sh
exec sed -i '0,/AI\x02/{s|AI\x02|\x00\x00\x00|}' "$1"

View File

@ -9,75 +9,44 @@ if [ "$(id -u)" -eq 0 ]; then
}
fi
# Main Script
run() {
# Add ARM Repository
for arch in "$@"; do
sudo dpkg --add-architecture "$(echo "${arch}" | tr '[:upper:]' '[:lower:]')"
done
# Run APT
install_pkg() {
sudo apt-get install --no-install-recommends -y "$@"
}
# Update APT
sudo apt-get update
sudo apt-get dist-upgrade -y
# Install Everything In One Go
PKG_QUEUE=''
queue_pkg() {
PKG_QUEUE="${PKG_QUEUE} $@"
}
# Build System
queue_pkg \
# Build Dependencies
run_build() {
install_pkg \
`# Build System` \
git \
cmake \
ninja-build \
python3 \
python3-venv
# Host Dependencies Needed For Compile
queue_pkg \
libwayland-bin
# Architecture-Specific Dependencies
architecture_specific_pkg() {
# Compiler
queue_pkg crossbuild-essential-$1
# Dependencies
queue_pkg \
libopenal-dev:$1
# GLFW Dependencies
queue_pkg \
libwayland-dev:$1 \
libxkbcommon-dev:$1 \
libx11-dev:$1 \
libxcursor-dev:$1 \
libxi-dev:$1 \
libxinerama-dev:$1 \
libxrandr-dev:$1 \
libxext-dev:$1
# Zenity Dependencies
queue_pkg \
libgtk-3-dev:$1 \
libglib2.0-dev:$1
}
for arch in "$@"; do
architecture_specific_pkg "$(echo "${arch}" | tr '[:upper:]' '[:lower:]')"
done
# AppStream Verification
queue_pkg \
python3-venv \
`# Host Dependencies Needed For Compile` \
libwayland-bin \
`# Compiler` \
"crossbuild-essential-$1" \
`# Main Dependencies` \
"libopenal-dev:$1" \
`# GLFW Dependencies` \
"libwayland-dev:$1" \
"libxkbcommon-dev:$1" \
"libx11-dev:$1" \
"libxcursor-dev:$1" \
"libxi-dev:$1" \
"libxinerama-dev:$1" \
"libxrandr-dev:$1" \
"libxext-dev:$1" \
`# Zenity Dependencies` \
"libgtk-3-dev:$1" \
"libglib2.0-dev:$1" \
`# AppStream Verification` \
appstream
# Install Queue
sudo apt-get install --no-install-recommends -y ${PKG_QUEUE}
# Install appimagetool
sudo rm -rf /opt/squashfs-root /opt/appimagetool.AppDir
sudo rm -f /opt/appimagetool /usr/local/bin/appimagetool
case "$(dpkg-architecture -qDEB_BUILD_ARCH)" in
sudo rm -rf /opt/squashfs-root /opt/appimagetool /usr/local/bin/appimagetool
case "$(dpkg --print-architecture)" in
'armhf') APPIMAGE_ARCH='armhf';;
'arm64') APPIMAGE_ARCH='aarch64';;
'i386') APPIMAGE_ARCH='i686';;
@ -87,19 +56,43 @@ run() {
sudo wget -O /opt/appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${APPIMAGE_ARCH}.AppImage"
sudo chmod +x /opt/appimagetool
# Workaround AppImage Issues With Docker
cd /opt
sudo sed -i '0,/AI\x02/{s|AI\x02|\x00\x00\x00|}' ./appimagetool
sudo ./scripts/fix-appimage-for-docker.sh /opt/appimagetool
# Extract
sudo ./appimagetool --appimage-extract
cd /opt
sudo ./appimagetool --appimage-extract > /dev/null
sudo rm -f ./appimagetool
# Link
sudo mv ./squashfs-root ./appimagetool.AppDir
sudo ln -s /opt/appimagetool.AppDir/AppRun /usr/local/bin/appimagetool
sudo mv ./squashfs-root ./appimagetool
sudo ln -s /opt/appimagetool/AppRun /usr/local/bin/appimagetool
}
# Run
if [ "$#" -lt 1 ]; then
run "$(dpkg-architecture -qDEB_BUILD_ARCH)"
else
run "$@"
fi
# Test Dependencies
run_test() {
sudo apt-get install --no-install-recommends -y \
"libc6:$1" \
"libopenal1:$1" \
"libglib2.0-0:$1"
}
# Example Mods Dependencies
run_example_mods() {
install_pkg \
cmake \
ninja-build \
g++-arm-linux-gnueabihf \
gcc-arm-linux-gnueabihf
}
# Variables
MODE="$1"
ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')"
# Add ARM Repository
sudo dpkg --add-architecture "${ARCH}"
# Update APT
sudo apt-get update
sudo apt-get dist-upgrade -y
# Install Packages
"run_${MODE}" "${ARCH}"

View File

@ -1,9 +0,0 @@
#!/bin/sh
set -e
# Change Directory
cd "$(dirname "$0")/../"
# Run
act push -W '.gitea/workflows/build.yml'

View File

@ -7,29 +7,35 @@ cd "$(dirname "$0")/../"
# Variables
MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
ARCH='host'
ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')"
APPIMAGE="$(pwd)/out/minecraft-pi-reborn-$(cat VERSION)-${ARCH}.AppImage"
# Build
./scripts/build.mjs none "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON
# Add To PATH
export PATH="$(pwd)/out/${MODE}/${ARCH}/usr/bin:${PATH}"
# Check If File Exists
if [ ! -f "${APPIMAGE}" ]; then
echo 'Missing AppImage!' > /dev/stderr
exit 1
fi
# Make Test Directory
TEST_WORKING_DIR="$(pwd)/.testing-tmp"
rm -rf "${TEST_WORKING_DIR}"
mkdir -p "${TEST_WORKING_DIR}"
ROOT="$(pwd)"
cd "${TEST_WORKING_DIR}"
# Prepare AppImage For Docker
cp "${APPIMAGE}" tmp.AppImage
"${ROOT}/scripts/fix-appimage-for-docker.sh" tmp.AppImage
chmod +x tmp.AppImage
# Run
if [ "${MODE}" = "server" ]; then
# Server Test
cd "${TEST_WORKING_DIR}"
minecraft-pi-reborn-server --only-generate
./tmp.AppImage --appimage-extract-and-run --server --only-generate
else
# Client Test
export _MCPI_SKIP_ROOT_CHECK=1
export HOME="${TEST_WORKING_DIR}"
minecraft-pi-reborn-client --default --no-cache --benchmark
./tmp.AppImage --appimage-extract-and-run --default --no-cache --benchmark --force-headless
fi
# Clean Up