diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 3b7dc5d..0000000 --- a/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -/.git -/.gitignore -/Dockerfile* -/Jenkinsfile -/out -/LICENSE -*.md -/debian -/scripts diff --git a/.gitignore b/.gitignore index df22795..95c2f47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /out /debian/tmp -/.vscode \ No newline at end of file +/.vscode +/build +/CMakeLists.txt.user +*.autosave diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..064e0a8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 3.13.0) + +# Specify Options +option(MCPI_USE_MEDIA_LAYER_PROXY "Whether To Enable The Media Layer Proxy" FALSE) +option(MCPI_SERVER_MODE "Server Mode" FALSE) +set(MCPI_BUILD_MODE "both" CACHE STRING "\"arm\" = Build Only Code That Must Be ARN; \"native\" = Build Architecture-Independent Code; \"both\" = Build All Code As ARM") +set_property(CACHE MCPI_BUILD_MODE PROPERTY STRINGS "both" "arm" "native") + +# Configure Build Mode +if(MCPI_BUILD_MODE STREQUAL "arm") + set(USE_ARM32_TOOLCHAIN TRUE) + set(BUILD_ARM_COMPONENTS TRUE) + set(BUILD_NATIVE_COMPONENTS FALSE) +elseif(MCPI_BUILD_MODE STREQUAL "native") + set(USE_ARM32_TOOLCHAIN FALSE) + set(BUILD_ARM_COMPONENTS FALSE) + set(BUILD_NATIVE_COMPONENTS TRUE) +elseif(MCPI_BUILD_MODE STREQUAL "both") + set(USE_ARM32_TOOLCHAIN TRUE) + set(BUILD_ARM_COMPONENTS TRUE) + set(BUILD_NATIVE_COMPONENTS TRUE) +else() + message(FATAL_ERROR "Invalid Mode") +endif() + +# Use Clang By Default +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) + +# Setup ARM Cross Compilation +if(USE_ARM32_TOOLCHAIN) + include(cmake/armhf-toolchain.cmake) +endif() + +# Use LLD When Using Clang +if(CMAKE_C_COMPILER STREQUAL "clang") + add_link_options("-fuse-ld=lld") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld") # Fix try_compile() +endif() + +# Utility Functions +include(cmake/util.cmake) + +# Specify Variant Name +set(MCPI_VARIANT_NAME "minecraft-pi-reborn") +if(MCPI_SERVER_MODE) + set(MCPI_VARIANT_NAME "${MCPI_VARIANT_NAME}-server") +else() + set(MCPI_VARIANT_NAME "${MCPI_VARIANT_NAME}-client") +endif() + +# Specify Installation Paths +set(MCPI_INSTALL_DIR "opt/${MCPI_VARIANT_NAME}") +set(MCPI_LIB_DIR "${MCPI_INSTALL_DIR}/lib") +set(MCPI_FALLBACK_LIB_DIR "${MCPI_INSTALL_DIR}/fallback-lib") + +# Optimizations +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() +if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_compile_options(-O3) +else() + add_compile_options(-g) + add_definitions(-DDEBUG) +endif() + +# Start Project +project(minecraft-pi-reborn) + +# Specify Default Installation Prefix +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "/" CACHE PATH "" FORCE) +endif() + +# Buld LibPNG + ZLib + Download Minecraft: Pi Edition +if(BUILD_ARM_COMPONENTS) + add_subdirectory(dependencies) +endif() + +# Warnings +add_compile_options(-Wall -Wextra -Werror) +add_link_options(-Wl,--no-undefined) +add_definitions(-D_GNU_SOURCE) + +# Specify Constants +if(MCPI_SERVER_MODE) + add_definitions(-DMCPI_SERVER_MODE) +endif() + +# Build libreborn +add_subdirectory(libreborn) + +# Build Media Layer +add_subdirectory(media-layer) + +# Build Launcher +if(BUILD_NATIVE_COMPONENTS) + add_subdirectory(launcher) +endif() + +# Build Mods +if(BUILD_ARM_COMPONENTS) + add_subdirectory(mods) +endif() diff --git a/Dockerfile.build b/Dockerfile.build deleted file mode 100644 index d84fb86..0000000 --- a/Dockerfile.build +++ /dev/null @@ -1,3 +0,0 @@ -FROM ubuntu:focal - -RUN apt-get update && apt-get install -y docker.io rsync diff --git a/Dockerfile.client b/Dockerfile.client deleted file mode 100644 index 9b23080..0000000 --- a/Dockerfile.client +++ /dev/null @@ -1,52 +0,0 @@ -# Runtime Base Environment -FROM arm32v7/debian:bullseye-slim AS runtime-base - -ENV DEBIAN_FRONTEND noninteractive -RUN \ - # Install Runtime Dependencies - apt-get update && \ - apt-get install -y --no-install-recommends tini libgles1 libx11-6 zlib1g libfreeimage3 libglfw3 xinput libxfixes3 gosu tk && \ - rm -rf /var/lib/apt/lists/* - -# Compile Environment -FROM runtime-base AS build - -ENV DEBIAN_FRONTEND noninteractive -RUN \ - # Install Dependencies - apt-get update && \ - apt-get install -y --no-install-recommends libgles-dev libx11-dev libxrandr-dev libsdl1.2-dev gcc g++ libc-dev make cmake zlib1g-dev git wget ca-certificates libfreeimage-dev libglfw3-dev xinput libxfixes-dev && \ - rm -rf /var/lib/apt/lists/* - -# Add Build Scripts -ADD ./build /app/build - -WORKDIR /app - -RUN \ - # Download MCPI - ./build/download-minecraft-pi.sh && \ - # Build LibPNG12 - ./build/build-libpng12.sh - -# Add Code -ADD . /app - -# Build Mods -RUN ./build/build-mods.sh - -# Runtime Environment -FROM runtime-base AS runtime - -# Setup /home Permissions -RUN \ - mkdir -p /home && \ - chmod -R a+rw /home - -# Copy Build -COPY --from=build /app/minecraft-pi /app/minecraft-pi - -WORKDIR /app/minecraft-pi - -ENTRYPOINT ["/usr/bin/tini", "--"] -CMD ["./run.sh"] diff --git a/Dockerfile.server b/Dockerfile.server deleted file mode 100644 index d245b19..0000000 --- a/Dockerfile.server +++ /dev/null @@ -1,3 +0,0 @@ -FROM thebrokenrail/minecraft-pi-reborn:client - -ENV MCPI_MODE=server diff --git a/Jenkinsfile b/Jenkinsfile index 7df851a..56d995c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,36 +1,33 @@ pipeline { - agent { - dockerfile { - filename 'Dockerfile.build' - args '-v /var/run/docker.sock:/var/run/docker.sock' - } - } + agent none stages { - stage('Install QEMU') { - steps { - sh 'docker run --rm --privileged multiarch/qemu-user-static --reset -p yes' - } - } - stage('Build') { - steps { - sh 'DOCKER_BUILD_OPTIONS="--no-cache" ./scripts/build.sh' - } - } - stage('Publish') { - steps { - withCredentials([usernamePassword(credentialsId: 'docker_hub_login', usernameVariable: 'DOCKER_HUB_USERNAME', passwordVariable: 'DOCKER_HUB_PASSWORD')]) { - sh 'docker login -u "${DOCKER_HUB_USERNAME}" -p "${DOCKER_HUB_PASSWORD}"' + stage('Build (Debian Bullseye)') { + agent { + docker { + image 'debian:bullseye' } - sh 'docker push thebrokenrail/minecraft-pi-reborn' } - } - stage('Package') { steps { - sh './scripts/package.sh' + sh './scripts/ci/run.sh' } post { success { - archiveArtifacts artifacts: 'out/**', fingerprint: true + archiveArtifacts artifacts: 'out/*.deb', fingerprint: true + } + } + } + stage('Build (Debian Buster)') { + agent { + docker { + image 'debian:buster' + } + } + steps { + sh './scripts/ci/run.sh' + } + post { + success { + archiveArtifacts artifacts: 'out/*.deb', fingerprint: true } } } diff --git a/README.md b/README.md index 78acdfe..c5b0efe 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,5 @@ # Minecraft: Pi Edition: Reborn Minecraft: Pi Edition Modding Project -## Installation - -### Option A: Pi-Apps (Raspberry Pi Only) -[![Pi-Apps](https://github.com/Botspot/pi-apps/blob/master/icons/badge.png?raw=true)](https://github.com/Botspot/pi-apps) - -### Option B: Manual Installation -[View Manual Installation](docs/INSTALL.md) - ## Documentation -- [View Overriding Assets](docs/OVERRIDING_ASSETS.md) -- [View Troubleshooting](docs/TROUBLESHOOTING.md) -- [View Dedicated Server](docs/DEDICATED_SERVER.md) -- [View Modding](docs/MODDING.md) -- [View Credits](docs/CREDITS.md) \ No newline at end of file +[View Documentation](docs/README.md) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..227cea2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.0.0 diff --git a/build/build-libpng12.sh b/build/build-libpng12.sh deleted file mode 100755 index c885a9e..0000000 --- a/build/build-libpng12.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -set -e - -git clone --depth 1 https://git.code.sf.net/p/libpng/code libpng -b libpng12 - -cd libpng - -./configure - -make -j$(nproc) - -mkdir -p ../minecraft-pi/lib -cp -L .libs/libpng12.so.0 ../minecraft-pi/lib - -cd ../ - -rm -rf libpng diff --git a/build/build-mods.sh b/build/build-mods.sh deleted file mode 100755 index 91442a5..0000000 --- a/build/build-mods.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -set -e - -cd launcher - -mkdir build -cd build - -cmake .. -make -j$(nproc) -make install DESTDIR=../../minecraft-pi - -cd ../../ - -cd mods - -mkdir build -cd build - -cmake .. -make -j$(nproc) -make install DESTDIR=../../minecraft-pi - -cd ../../ \ No newline at end of file diff --git a/build/download-minecraft-pi.sh b/build/download-minecraft-pi.sh deleted file mode 100755 index 939c272..0000000 --- a/build/download-minecraft-pi.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -set -e - -URL="https://www.minecraft.net/content/dam/minecraft/edition-pi/minecraft-pi-0.1.1.tar.gz" - -mkdir minecraft-pi -wget -O - "${URL}" | tar -xz --strip-components 1 -C minecraft-pi diff --git a/cmake/armhf-toolchain.cmake b/cmake/armhf-toolchain.cmake new file mode 100644 index 0000000..fd87143 --- /dev/null +++ b/cmake/armhf-toolchain.cmake @@ -0,0 +1,13 @@ +# Compile For ARM +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64_be" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "armv8b" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "armv8l") + # Force 32-Bit Compile + add_compile_options("-m32") +elseif(NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "arm") + # Use ARM Cross-Compiler + set(TARGET "arm-linux-gnueabihf") + set(CMAKE_C_COMPILER "${TARGET}-gcc") + set(CMAKE_CXX_COMPILER "${TARGET}-g++") + set(CMAKE_FIND_ROOT_PATH "/usr/${TARGET}" "/usr/lib/${TARGET}") +endif() +set(CMAKE_SYSTEM_NAME "Linux") +set(CMAKE_SYSTEM_PROCESSOR "arm") diff --git a/cmake/util.cmake b/cmake/util.cmake new file mode 100644 index 0000000..fc58e8f --- /dev/null +++ b/cmake/util.cmake @@ -0,0 +1,20 @@ +# Symlink Function +function(install_symlink target link) + install(CODE "\ + # Prepare\n \ + set(file \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${link}\")\n \ + \ + # Create Directory\n \ + get_filename_component(dir \"\${file}\" DIRECTORY)\n \ + file(MAKE_DIRECTORY \${dir})\n \ + \ + # Create Symlink\n \ + if(NOT EXISTS \"\${file}\")\n \ + execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink ${target} \"\${file}\")\n \ + message(\"-- Installing: \${file}\")\n \ + else()\n \ + message(\"-- Up-to-date: \${file}\")\n \ + endif() \ + ") +endfunction() + diff --git a/debian/client-arm b/debian/client-arm new file mode 100644 index 0000000..9ef8396 --- /dev/null +++ b/debian/client-arm @@ -0,0 +1,7 @@ +Package: minecraft-pi-reborn-client +Version: ${VERSION} +Maintainer: TheBrokenRail +Description: Fun with Blocks +Homepage: https://www.minecraft.net/en-us/edition/pi +Architecture: armhf +Depends: zenity, libgles1, libglfw3 | libglfw3-wayland, libfreeimage3 diff --git a/debian/client-x86_64 b/debian/client-x86_64 new file mode 100644 index 0000000..8892409 --- /dev/null +++ b/debian/client-x86_64 @@ -0,0 +1,7 @@ +Package: minecraft-pi-reborn-client +Version: ${VERSION} +Maintainer: TheBrokenRail +Description: Fun with Blocks +Homepage: https://www.minecraft.net/en-us/edition/pi +Architecture: amd64 +Depends: zenity, libgles1, libglfw3 | libglfw3-wayland, libfreeimage3, libc6-armhf-cross, libstdc++6-armhf-cross, qemu-user-static diff --git a/debian/client/common/DEBIAN/postinst b/debian/client/common/DEBIAN/postinst deleted file mode 100755 index 42d5469..0000000 --- a/debian/client/common/DEBIAN/postinst +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -set -e - -docker load < /usr/share/minecraft-pi/client/image.tar.gz -rm /usr/share/minecraft-pi/client/image.tar.gz \ No newline at end of file diff --git a/debian/client/common/DEBIAN/prerm b/debian/client/common/DEBIAN/prerm deleted file mode 100755 index 526a170..0000000 --- a/debian/client/common/DEBIAN/prerm +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -set -e - -docker image rm -f thebrokenrail/minecraft-pi-reborn:client \ No newline at end of file diff --git a/debian/client/common/usr/bin/minecraft-pi b/debian/client/common/usr/bin/minecraft-pi deleted file mode 100755 index f983c37..0000000 --- a/debian/client/common/usr/bin/minecraft-pi +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -set -e - -# All Feature Flags -export AVAILABLE_FEATURES="$(tr '\n' ' ' < /usr/share/minecraft-pi/client/features)" - -# Print Feature Flags Option -if [ "$1" = "--print-features" ]; then - echo "${AVAILABLE_FEATURES}" - exit 0 -fi - -# Esnure User Is In docker Group -if ! id -nGz | grep -qzxF 'docker'; then - pkexec /usr/sbin/usermod -aG docker "$(id -un)" -fi - -# Export Important Variables -export ZENITY_CLASS='Minecraft - Pi edition' -export DOCKER_COMPOSE_YML="/usr/share/minecraft-pi/client/docker-compose.yml" - -# Ensure Features Are Selected -if [ -z "${MCPI_FEATURES+x}" ]; then - MCPI_FEATURES="$(eval "zenity --class \"${ZENITY_CLASS}\" --list --checklist --width 400 --height 400 --column 'Enabled' --column 'Feature' ${AVAILABLE_FEATURES}")" -fi -if [ -z "${MCPI_RENDER_DISTANCE+x}" ]; then - MCPI_RENDER_DISTANCE="$(zenity --class "${ZENITY_CLASS}" --list --radiolist --width 400 --height 400 --text 'Minecraft Render Distance:' --column 'Selected' --column 'Name' FALSE 'Far' FALSE 'Normal' TRUE 'Short' FALSE 'Tiny')" -fi -if [ -z "${MCPI_USERNAME+x}" ]; then - MCPI_USERNAME="$(zenity --class "${ZENITY_CLASS}" --entry --text 'Minecraft Username:' --entry-text 'StevePi')" -fi -export MCPI_FEATURES -export MCPI_RENDER_DISTANCE -export MCPI_USERNAME - -# Prepare Environment -export USER_HOME="${HOME}" -export USER_UID="$(id -u)" -export USER_GID="$(id -g)" -get_gid() { - echo "$(getent group "$1" | cut -d : -f 3)" -} -export USER_OTHER_GIDS="$(get_gid video) $(get_gid render)" - -# Run -set +e -sg docker /usr/lib/minecraft-pi/pre-launch.sh -RET=$? -set -e - -# Handle Crash -if [ ${RET} -ne 0 ]; then - zenity --class "${ZENITY_CLASS}" --error --no-wrap --text 'Minecraft: Pi Edition has crashed!\n\nExit Code: '${RET}'\n\nOpen Log Folder\nOpen Troubleshooting Guide' - exit ${RET} -fi \ No newline at end of file diff --git a/debian/client/common/usr/lib/minecraft-pi/pre-launch.sh b/debian/client/common/usr/lib/minecraft-pi/pre-launch.sh deleted file mode 100755 index a660ef8..0000000 --- a/debian/client/common/usr/lib/minecraft-pi/pre-launch.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -set -e - -# Prepare Data Folder -mkdir -p "${USER_HOME}/.minecraft-pi" - -# Create Log Folder -rm -rf /tmp/minecraft-pi -mkdir -p /tmp/minecraft-pi - -# Start Logging -touch /tmp/minecraft-pi/main.log -tail -f /tmp/minecraft-pi/main.log & -TAIL_PID=$! - -# Run -set +e -/usr/lib/minecraft-pi/run.sh > /tmp/minecraft-pi/main.log 2>&1 -RET=$? -set -e - -# Kill Logging -kill ${TAIL_PID} > /dev/null 2>&1 || : - -# Exit -exit ${RET} \ No newline at end of file diff --git a/debian/client/common/usr/share/minecraft-pi/client/features b/debian/client/common/usr/share/minecraft-pi/client/features deleted file mode 100644 index 136a6f2..0000000 --- a/debian/client/common/usr/share/minecraft-pi/client/features +++ /dev/null @@ -1,17 +0,0 @@ -TRUE 'Touch GUI' -TRUE 'Fix Bow & Arrow' -TRUE 'Fix Attacking' -TRUE 'Mob Spawning' -TRUE 'Fancy Graphics' -TRUE 'Disable Autojump By Default' -TRUE 'Display Nametags By Default' -TRUE 'Fix Sign Placement' -TRUE 'Show Block Outlines' -FALSE 'Expand Creative Inventory' -FALSE 'Peaceful Mode' -TRUE 'Animated Water' -TRUE 'Remove Invalid Item Background' -TRUE 'Disable gui_blocks Atlas' -TRUE 'Smooth Lighting' -FALSE '3D Anaglyph' -FALSE 'Show FPS Monitor' diff --git a/debian/client/native/DEBIAN/control b/debian/client/native/DEBIAN/control deleted file mode 100644 index 5bceb98..0000000 --- a/debian/client/native/DEBIAN/control +++ /dev/null @@ -1,9 +0,0 @@ -Package: minecraft-pi-reborn-native -Version: ${VERSION} -Maintainer: TheBrokenRail -Description: Fun with Blocks -Homepage: https://www.minecraft.net/en-us/edition/pi -Architecture: all -Depends: ${DEPENDENCIES} -Recommends: ${RECOMMENDED_DEPENDENCIES} -Conflicts: minecraft-pi, minecraft-pi-reborn-virgl diff --git a/debian/client/native/usr/lib/minecraft-pi/run.sh b/debian/client/native/usr/lib/minecraft-pi/run.sh deleted file mode 100755 index b55bfb8..0000000 --- a/debian/client/native/usr/lib/minecraft-pi/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -set -e - -# Launch Minecraft -exec docker-compose -f "${DOCKER_COMPOSE_YML}" run --rm minecraft-pi \ No newline at end of file diff --git a/debian/client/native/usr/share/minecraft-pi/client/docker-compose.yml b/debian/client/native/usr/share/minecraft-pi/client/docker-compose.yml deleted file mode 100644 index 2dd8e0a..0000000 --- a/debian/client/native/usr/share/minecraft-pi/client/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3' -services: - minecraft-pi: - image: 'thebrokenrail/minecraft-pi-reborn:client' - network_mode: 'host' - volumes: - - /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static - - '/tmp/.X11-unix:/tmp/.X11-unix' - - '${USER_HOME}/.minecraft-pi:/home/.minecraft-pi' - devices: - - '/dev/dri:/dev/dri' - environment: - - 'DISPLAY=unix${DISPLAY}' - - 'MCPI_FEATURES=${MCPI_FEATURES}' - - 'MCPI_RENDER_DISTANCE=${MCPI_RENDER_DISTANCE}' - - 'MCPI_USERNAME=${MCPI_USERNAME}' - - 'MCPI_MODE=native' - - 'USER_UID=${USER_UID}' - - 'USER_GID=${USER_GID}' - - 'USER_OTHER_GIDS=${USER_OTHER_GIDS}' diff --git a/debian/client/virgl/DEBIAN/control b/debian/client/virgl/DEBIAN/control deleted file mode 100644 index cfef9cc..0000000 --- a/debian/client/virgl/DEBIAN/control +++ /dev/null @@ -1,9 +0,0 @@ -Package: minecraft-pi-reborn-virgl -Version: ${VERSION} -Maintainer: TheBrokenRail -Description: Fun with Blocks -Homepage: https://www.minecraft.net/en-us/edition/pi -Architecture: all -Depends: ${DEPENDENCIES}, virgl-server -Recommends: ${RECOMMENDED_DEPENDENCIES} -Conflicts: minecraft-pi, minecraft-pi-reborn-native diff --git a/debian/client/virgl/usr/lib/minecraft-pi/run.sh b/debian/client/virgl/usr/lib/minecraft-pi/run.sh deleted file mode 100755 index 30b43b6..0000000 --- a/debian/client/virgl/usr/lib/minecraft-pi/run.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -e - -# Start VirGL -virgl_test_server > /tmp/minecraft-pi/virgl.log 2>&1 & -VIRGL_PID=$! - -# Launch Minecraft -set +e -docker-compose -f "${DOCKER_COMPOSE_YML}" run --rm minecraft-pi -RET=$? -set -e - -# Kill VirGL -kill ${VIRGL_PID} > /dev/null 2>&1 || : - -# Exit -exit ${RET} \ No newline at end of file diff --git a/debian/client/virgl/usr/share/minecraft-pi/client/docker-compose.yml b/debian/client/virgl/usr/share/minecraft-pi/client/docker-compose.yml deleted file mode 100644 index c5207d1..0000000 --- a/debian/client/virgl/usr/share/minecraft-pi/client/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3' -services: - minecraft-pi: - image: 'thebrokenrail/minecraft-pi-reborn:client' - network_mode: 'host' - volumes: - - /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static - - '/tmp/.X11-unix:/tmp/.X11-unix' - - '/tmp/.virgl_test:/tmp/.virgl_test' - - '${USER_HOME}/.minecraft-pi:/home/.minecraft-pi' - environment: - - 'DISPLAY=unix${DISPLAY}' - - 'MCPI_FEATURES=${MCPI_FEATURES}' - - 'MCPI_RENDER_DISTANCE=${MCPI_RENDER_DISTANCE}' - - 'MCPI_USERNAME=${MCPI_USERNAME}' - - 'MCPI_MODE=virgl' - - 'USER_UID=${USER_UID}' - - 'USER_GID=${USER_GID}' - - 'USER_OTHER_GIDS=${USER_OTHER_GIDS}' diff --git a/debian/server/DEBIAN/control b/debian/server-arm similarity index 69% rename from debian/server/DEBIAN/control rename to debian/server-arm index 90c1bc3..05eef32 100644 --- a/debian/server/DEBIAN/control +++ b/debian/server-arm @@ -3,6 +3,4 @@ Version: ${VERSION} Maintainer: TheBrokenRail Description: Fun with Blocks Homepage: https://www.minecraft.net/en-us/edition/pi -Architecture: all -Depends: ${DEPENDENCIES} -Recommends: ${RECOMMENDED_DEPENDENCIES} +Architecture: armhf diff --git a/debian/server-x86_64 b/debian/server-x86_64 new file mode 100644 index 0000000..f6ca68f --- /dev/null +++ b/debian/server-x86_64 @@ -0,0 +1,7 @@ +Package: minecraft-pi-reborn-server +Version: ${VERSION} +Maintainer: TheBrokenRail +Description: Fun with Blocks +Homepage: https://www.minecraft.net/en-us/edition/pi +Architecture: amd64 +Depends: libc6-armhf-cross, libstdc++6-armhf-cross, qemu-user-static diff --git a/debian/server/DEBIAN/postinst b/debian/server/DEBIAN/postinst deleted file mode 100755 index 957cace..0000000 --- a/debian/server/DEBIAN/postinst +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -set -e - -docker load < /usr/share/minecraft-pi/server/image.tar.gz -rm /usr/share/minecraft-pi/server/image.tar.gz \ No newline at end of file diff --git a/debian/server/usr/bin/minecraft-pi-server b/debian/server/usr/bin/minecraft-pi-server deleted file mode 100755 index 3f504f6..0000000 --- a/debian/server/usr/bin/minecraft-pi-server +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -set -e - -# Prepare Environment -export MCPI_ROOT="${PWD}" -export USER_UID="$(id -u)" -export USER_GID="$(id -g)" - -# Launch Minecraft -DOCKER_COMPOSE_YML='/usr/share/minecraft-pi/server/docker-compose.yml' -docker-compose -f "${DOCKER_COMPOSE_YML}" run --rm minecraft-pi-server diff --git a/debian/server/usr/share/minecraft-pi/server/docker-compose.yml b/debian/server/usr/share/minecraft-pi/server/docker-compose.yml deleted file mode 100644 index f180308..0000000 --- a/debian/server/usr/share/minecraft-pi/server/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3' -services: - minecraft-pi-server: - image: 'thebrokenrail/minecraft-pi-reborn:server' - network_mode: 'host' - stdin_open: true - tty: true - volumes: - - /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static - - '${MCPI_ROOT}:/home/.minecraft-pi' - environment: - - 'USER_UID=${USER_UID}' - - 'USER_GID=${USER_GID}' diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt new file mode 100644 index 0000000..797df73 --- /dev/null +++ b/dependencies/CMakeLists.txt @@ -0,0 +1,8 @@ +project(dependencies) + +# ZLib +add_subdirectory(zlib) +# LibPNG +add_subdirectory(libpng) +# Minecraft: Pi Edition +add_subdirectory(minecraft-pi) diff --git a/dependencies/libpng/CMakeLists.txt b/dependencies/libpng/CMakeLists.txt new file mode 100644 index 0000000..39238e3 --- /dev/null +++ b/dependencies/libpng/CMakeLists.txt @@ -0,0 +1,28 @@ +project(libpng) + +include(FetchContent) + +# Silence Warnings +add_compile_options(-w) + +## LibPNG + +# Download +set(SKIP_INSTALL_ALL TRUE) # Skip Default LibPNG Installation +FetchContent_Declare( + libpng + GIT_REPOSITORY "https://git.code.sf.net/p/libpng/code" + GIT_TAG "v1.2.59" +) +FetchContent_Populate(libpng) +set(ZLIB_LIBRARY zlib) +set(ZLIB_INCLUDE_DIR "${zlib_SOURCE_DIR}" "${zlib_BINARY_DIR}") +set(CMAKE_POLICY_DEFAULT_CMP0054 OLD) # Silence Warning +add_subdirectory("${libpng_SOURCE_DIR}" "${libpng_BINARY_DIR}") +set(CMAKE_POLICY_DEFAULT_CMP0054 NEW) # Re-Enable New Behavior +set_target_properties(png12 PROPERTIES LINK_FLAGS "-Wl,--version-script='${CMAKE_CURRENT_SOURCE_DIR}/libpng.vers'") # Use Symbol Versioning +set_target_properties(png12 PROPERTIES DEBUG_POSTFIX "") # Fix LibPNG Suffix In Debug Mode + +# Install +install(TARGETS png12 DESTINATION "${MCPI_LIB_DIR}") + diff --git a/dependencies/libpng/libpng.vers b/dependencies/libpng/libpng.vers new file mode 100644 index 0000000..2511df8 --- /dev/null +++ b/dependencies/libpng/libpng.vers @@ -0,0 +1,208 @@ +PNG12_0 {global: +png_libpng_ver; +png_pass_start; +png_pass_inc; +png_pass_ystart; +png_pass_yinc; +png_pass_mask; +png_pass_dsp_mask; +png_access_version_number; +png_set_sig_bytes; +png_sig_cmp; +png_check_sig; +png_create_read_struct; +png_create_write_struct; +png_get_compression_buffer_size; +png_set_compression_buffer_size; +png_reset_zstream; +png_create_read_struct_2; +png_create_write_struct_2; +png_write_chunk; +png_write_chunk_start; +png_write_chunk_data; +png_write_chunk_end; +png_create_info_struct; +png_info_init; +png_info_init_3; +png_write_info_before_PLTE; +png_write_info; +png_read_info; +png_convert_to_rfc1123; +png_convert_from_struct_tm; +png_convert_from_time_t; +png_set_expand; +png_set_expand_gray_1_2_4_to_8; +png_set_palette_to_rgb; +png_set_tRNS_to_alpha; +png_set_gray_1_2_4_to_8; +png_set_bgr; +png_set_gray_to_rgb; +png_set_rgb_to_gray; +png_set_rgb_to_gray_fixed; +png_get_rgb_to_gray_status; +png_build_grayscale_palette; +png_set_strip_alpha; +png_set_swap_alpha; +png_set_invert_alpha; +png_set_filler; +png_set_add_alpha; +png_set_swap; +png_set_packing; +png_set_packswap; +png_set_shift; +png_set_interlace_handling; +png_set_invert_mono; +png_set_background; +png_set_strip_16; +png_set_dither; +png_set_gamma; +png_permit_empty_plte; +png_set_flush; +png_write_flush; +png_start_read_image; +png_read_update_info; +png_read_rows; +png_read_row; +png_read_image; +png_write_row; +png_write_rows; +png_write_image; +png_write_end; +png_read_end; +png_destroy_info_struct; +png_destroy_read_struct; +png_destroy_write_struct; +png_set_crc_action; +png_set_filter; +png_set_filter_heuristics; +png_set_compression_level; +png_set_compression_mem_level; +png_set_compression_strategy; +png_set_compression_window_bits; +png_set_compression_method; +png_init_io; +png_set_error_fn; +png_get_error_ptr; +png_set_write_fn; +png_set_read_fn; +png_get_io_ptr; +png_set_read_status_fn; +png_set_write_status_fn; +png_set_mem_fn; +png_get_mem_ptr; +png_set_read_user_transform_fn; +png_set_write_user_transform_fn; +png_set_user_transform_info; +png_get_user_transform_ptr; +png_set_read_user_chunk_fn; +png_get_user_chunk_ptr; +png_set_progressive_read_fn; +png_get_progressive_ptr; +png_process_data; +png_progressive_combine_row; +png_malloc; +png_malloc_warn; +png_free; +png_free_data; +png_data_freer; +png_malloc_default; +png_free_default; +png_memcpy_check; +png_memset_check; +png_error; +png_chunk_error; +png_warning; +png_chunk_warning; +png_get_valid; +png_get_rowbytes; +png_get_rows; +png_set_rows; +png_get_channels; +png_get_image_width; +png_get_image_height; +png_get_bit_depth; +png_get_color_type; +png_get_filter_type; +png_get_interlace_type; +png_get_compression_type; +png_get_pixels_per_meter; +png_get_x_pixels_per_meter; +png_get_y_pixels_per_meter; +png_get_pixel_aspect_ratio; +png_get_x_offset_pixels; +png_get_y_offset_pixels; +png_get_x_offset_microns; +png_get_y_offset_microns; +png_get_signature; +png_get_bKGD; +png_set_bKGD; +png_get_cHRM; +png_get_cHRM_fixed; +png_set_cHRM; +png_set_cHRM_fixed; +png_get_gAMA; +png_get_gAMA_fixed; +png_set_gAMA; +png_set_gAMA_fixed; +png_get_hIST; +png_set_hIST; +png_get_IHDR; +png_set_IHDR; +png_get_oFFs; +png_set_oFFs; +png_get_pCAL; +png_set_pCAL; +png_get_pHYs; +png_set_pHYs; +png_get_PLTE; +png_set_PLTE; +png_get_sBIT; +png_set_sBIT; +png_get_sRGB; +png_set_sRGB; +png_set_sRGB_gAMA_and_cHRM; +png_get_iCCP; +png_set_iCCP; +png_get_sPLT; +png_set_sPLT; +png_get_text; +png_set_text; +png_get_tIME; +png_set_tIME; +png_get_tRNS; +png_set_tRNS; +png_get_sCAL; +png_set_sCAL; +png_set_keep_unknown_chunks; +png_handle_as_unknown; +png_set_unknown_chunks; +png_set_unknown_chunk_location; +png_get_unknown_chunks; +png_set_invalid; +png_read_png; +png_write_png; +png_get_copyright; +png_get_header_ver; +png_get_header_version; +png_get_libpng_ver; +png_permit_mng_features; +png_get_mmx_flagmask; +png_get_asm_flagmask; +png_get_asm_flags; +png_get_mmx_bitdepth_threshold; +png_get_mmx_rowbytes_threshold; +png_set_asm_flags; +png_set_mmx_thresholds; +png_mmx_support; +png_set_strip_error_numbers; +png_set_user_limits; +png_get_user_width_max; +png_get_user_height_max; +png_get_uint_32; +png_get_uint_16; +png_get_int_32; +png_get_uint_31; +png_save_uint_32; +png_save_int_32; +png_save_uint_16; +local: *; }; diff --git a/dependencies/minecraft-pi/CMakeLists.txt b/dependencies/minecraft-pi/CMakeLists.txt new file mode 100644 index 0000000..bd673e4 --- /dev/null +++ b/dependencies/minecraft-pi/CMakeLists.txt @@ -0,0 +1,17 @@ +project(minecraft-pi) + +include(FetchContent) + +## Minecraft: Pi Edition + +# Download +FetchContent_Declare( + minecraft-pi + URL "https://www.minecraft.net/content/dam/minecraft/edition-pi/minecraft-pi-0.1.1.tar.gz" + URL_HASH "SHA256=e0d68918874cdd403de1fd399380ae2930913fcefdbf60a3fbfebb62e2cfacab" +) +FetchContent_Populate(minecraft-pi) + +# Install +install(DIRECTORY "${minecraft-pi_SOURCE_DIR}/" DESTINATION "${MCPI_INSTALL_DIR}" USE_SOURCE_PERMISSIONS) + diff --git a/dependencies/zlib/CMakeLists.txt b/dependencies/zlib/CMakeLists.txt new file mode 100644 index 0000000..04cfd39 --- /dev/null +++ b/dependencies/zlib/CMakeLists.txt @@ -0,0 +1,23 @@ +project(zlib) + +include(FetchContent) + +# Silence Warnings +add_compile_options(-w) + +## zlib + +# Download +set(SKIP_INSTALL_ALL TRUE) # Skip Default ZLib Installation +FetchContent_Declare( + zlib + GIT_REPOSITORY "https://github.com/madler/zlib.git" + GIT_TAG "v1.2.11" +) +FetchContent_Populate(zlib) +include_directories("${zlib_SOURCE_DIR}" "${zlib_BINARY_DIR}") # Fix ZLib Build +add_subdirectory("${zlib_SOURCE_DIR}" "${zlib_BINARY_DIR}") + +# Install +install(TARGETS zlib DESTINATION "${MCPI_LIB_DIR}") + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..6524298 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,103 @@ +# Architecture + +## Launch Sequence + +### Client +1. The launcher is started by the user + 1. The launcher starts several Zenity dialogs to configure MCPI-Reborn +2. The launcher replaces itself with MCPI + 1. MCPI-Reborn components are loaded using ``LD_PRELOAD`` and ``LD_LIBRARY_PATH`` + 2. If the Media Layer Proxy is enabled, the Media Layer Proxy Client is started as a sub-process + +### Server +1. The launcher is started by the user +2. The launcher replaces itself with MCPI + +## Components + +### Launcher +This component configures the various environmental variables required for MCPI-Reborn to work. When running in client-mode, this component will also launch several Zenity dialogs for interactive configuration. + +The environmental variables configured by this component includes: +* ``LD_PRELOAD`` +* ``LD_LIBRAR_PATH`` +* ``MCPI_FEATURE_FLAGS`` +* ``MCPI_RENDER_DISTANCE`` +* ``MCPI_USERNAME`` + +This is always compiled for the host system's architecture. + +### Media Layer +The Media Layer handles MCPI's graphics calls and user input. It replaces MCPI's native usage of SDL 1.2 with GLFW. + +#### Core +This sub-component re-implements a subset of SDL 1.2 calls with GLFW. It also provides a few utility functions that are used internally by MCPI-Reborn. + +The utility functions include: +* Taking Screenshots +* Fullscreen +* Etc + +This is always compiled for the host system's architecture. + +This was created because SDL 1.2 has numerous bugs and is in-general unsupported. + +#### Proxy +This sub-component must be used if the host system's architecture isn't ARM. It uses UNIX pipes to cross architectural boundaries and allow MCPI to use the Media Layer Core (which is always compiled for the host system's architecture). + +It is made of two parts: +* Media Layer Proxy Server + * Links To MCPI + * Creates The UNIX Pipes + * Same External API As The Media Layer Core + * Compiled For ARM +* Media Layer Proxy Client + * Links To The Media Layer Core + * Connects To The Media Layer Proxy Server + * Uses The System's Native GLES Driver (ie. Mesa) + * Compiled For The Host System's Architecture + +While proxying all Media Layer Core API calls across UNIX pipes does hurt performance, it is better than emulating the entire graphics stack. + +Using this in server-mode is redundant (and disallowed). + +#### Extras +This sub-component contains code that must always be linked directly to MCPI. + +This is always compiled for ARM. + +#### Stubs +This sub-component implements stubs for various redundant libraries used by MCPI to silence linker errors. + +This is always compiled for ARM. + +##### What To Stub And What To Patch? +Most libraries (like ``bcm_host``) can just be replaced with stubs, because they don't need to do anything and aren't used by anything else. However, some libraries (like EGL) might be used by some of MCPI-Reborn's dependencies (like GLFW) so instead of being replaced by a stub, each call is manually patched out from MCPI. A stub is still generated just in case that library isn't present on the system to silence linker errors, but it is only loaded if no other version is available. + +#### Headers +This sub-component includes headers for SDL, GLES, and EGL allowing easy (cross-)compilation. + +### Mods +This component links directly to MCPI and patches it to modify its behavior. + +This is always compiled for ARM. + +### ``libreborn`` +This component contains various utility functions including: + +* Code Patching (ARM Only) +* Logging +* MCPI Symbols +* Etc + +The code patching is ARM only because it relies on hard-coded ARM instructions. However, this is irrelevant since code patching is only needed in ARM code (to patch MCPI). + +## Dependencies +MCPI-Reborn has several dependencies: +* MCPI (Bundled) +* GLFW (Only In Client Mode) +* ZLib (Required By LibPNG; Bundled) +* LibPNG (Bundled) +* FreeImage (Only In Client Mode) +* QEMU User Mode (Only On Non-ARM Hosts; Runtime Only) +* Zenity (Only In Client Mode; Runtime Only) diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 0000000..92c3bf8 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,63 @@ +# Building + +## Build Options +* ``MCPI_BUILD_MODE`` + * ``arm``: Only Build ARM Components + * ``native``: Only Build Native Components + * ``both`` (Default): Build Both ARM And Native Components For ARM +* ``MCPI_SERVER_MODE`` + * ``ON``: Enable Server Mode + * ``OFF`` (Default): Disable Server Mode +* ``MCPI_USE_MEDIA_LAYER_PROXY`` + * ``ON``: Enable The Media Layer Proxy + * ``OFF`` (Default): Disable The Media Layer Proxy + +## Build Dependencies +* Common + * ARM Compiler + * Host Compiler (Clang) + * CMake +* Host Architecture Dependencies + * Client Mode Only + * GLFW + * FreeImage + +## Runtime Dependencies +* Non-ARM Host Architectures + * QEMU User-Mode Static +* Host Architecture Dependencies + * CLient Mode Only + * OpenGL ES 1.1 + * GLFW + * FreeImage + * Zenity + +## Two-Step Build +Use this when the host architecture is not ARM. + +```sh +# Create Build Directory +mkdir build && cd build + +# Build ARM Components +mkdir arm && cd arm +cmake -DMCPI_BUILD_MODE=arm ../.. +make -j$(nproc) && sudo make install + +# Build Native Components +mkdir native && cd native +cmake -DMCPI_BUILD_MODE=native ../.. +make -j$(nproc) && sudo make install +``` + +## One-Step Build +Use this when the host architecture is ARM. + +```sh +# Create Build Directory +mkdir build && cd build + +# Build +cmake .. +make -j$(nproc) && sudo make install +``` diff --git a/docs/DEDICATED_SERVER.md b/docs/DEDICATED_SERVER.md index 3f42e26..7cc2c0c 100644 --- a/docs/DEDICATED_SERVER.md +++ b/docs/DEDICATED_SERVER.md @@ -6,27 +6,14 @@ This server is also compatible with MCPE Alpha v0.6.1. ## Setup ### Debian Package -To use, install the ``minecraft-pi-reborn-server`` package and run ``minecraft-pi-reborn-server`` or use. It will generate the world and ``server.properties`` in the current directory. - -### Docker Compose -Make sure you have ``qemu-user-static`` installed and ``binfmt`` setup. -```yml -version: "3.7" - -services: - minecraft-pi: - image: thebrokenrail/minecraft-pi-reborn:server - volumes: - - ./minecraft-pi/data:/home/.minecraft-pi - - /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static - restart: always - stdin_open: true - tty: true - ports: - - "19132:19132/udp" -``` +To use, install and run ``minecraft-pi-reborn-server``. 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 only compatible with ``minecraft-pi-reborn`` clients \ No newline at end of file +* 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 + +## Arguments + +### ``--only-generate`` +If you run the server with ``--only-generate`` it will immediately exit once world generation has completed. diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 5e27015..1991932 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,66 +1,26 @@ # Manual Installation +[Download Packages Here](https://jenkins.thebrokenrail.com/job/minecraft-pi-reborn/job/master/lastSuccessfulBuild/artifact/out/) -## System Requirements -- At Least Debian/Raspbian Buster Or Ubuntu 20.04 +## Picking A Package -## Before You Install - -
-Debian/Raspbian Buster - -### ``libseccomp2`` -``minecraft-pi-reborn`` requires a newer version of the package ``libseccomp2`` to be installed when using Debian/Raspbian Buster. - -```sh -# Install Backports Key -sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 04EE7237B7D453EC -sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 648ACFD622F3D138 -# Install Backports Repository -echo 'deb http://deb.debian.org/debian buster-backports main' | sudo tee -a /etc/apt/sources.list -# Update APT Index -sudo apt update -# Install Updated libseccomp2 -sudo apt install -t buster-backports libseccomp2 +### Name Format +``` +minecraft-pi-reborn-_X.Y.Z~_ ``` -### Official Docker Package -``minecraft-pi-reborn`` requires the official Docker package when running Debian/Raspbian Buster instead of the Debian package (``docker.io``). +### Picking A Variant +* ``client``: Client mode, use this if you want to play MCPI +* ``server``: Server mode, use this if you want to host an MCPI server -```sh -# Remove Debian Docker Package -sudo apt-get remove -y docker.io -# Install Official Docker Package -curl -fsSL https://get.docker.com -o get-docker.sh -sudo sh get-docker.sh -``` +### Picking A Distribution +This specifies which version of Debian MCPI-Reborn was built against. Which one you should use depends on your current distribution. If your distribution supports it, you should use ``bullseye`` for better mouse sensitivity. -### Existing Installation -If you have un-modded ``minecraft-pi`` installed, you must remove it and transfer your existing worlds to ``minecraft-pi-reborn``'s folder. +* Ubuntu 20.04+: ``bullseye`` +* Ubuntu 18.04: ``buster`` +* Raspberry Pi OS Buster: ``buster`` +* Debian Bullseye+: ``bullseye`` +* Debian Buster: ``buster`` -```sh -# Transfer Worlds -mv ~/.minecraft ~/.minecraft-pi -# Remove Vanilla Minecraft Pi -sudo apt-get remove -y minecraft-pi -``` - -
- -
-NVIDIA Users - -The proprietary NVIDIA drivers are not supported, use either the open-source ``noveau`` drivers or use a different GPU (ie. Intel Integrated GPU). - -
- -## Installing -1. Download Appropriate Package From [Here](https://jenkins.thebrokenrail.com/job/minecraft-pi-reborn/job/master/lastSuccessfulBuild/artifact/out/deb/) (See Table Below To Pick Correct Package) -2. Install With ``sudo apt install ./`` Or Your Preferred Package Installer -3. Have Fun! - -### Package Table -| Package | Description | -| --- | --- | -| ``minecraft-pi-reborn-virgl`` | Minecraft Pi Edition Using VirGL For Hardware Acceleration (Recommended For Desktop/Laptop) | -| ``minecraft-pi-reborn-native`` | Minecraft: Pi Edition Using Docker Device Mounting For GPU Acceleration (Recommended For ARM Devices (ie. Raspberry Pi, PinePhone, etc)) | -| ``minecraft-pi-reborn-server`` | Minecraft Pi Edition Modded Into A Dedicated Server | \ No newline at end of file +### Picking An Architecture +* ``amd64``: x86_64, use this if you are using a device with an AMD or Intel processor +* ``armhf``: ARM, use this if you are using an ARM device (like a Raspberry Pi) diff --git a/docs/MODDING.md b/docs/MODDING.md deleted file mode 100644 index 18160e6..0000000 --- a/docs/MODDING.md +++ /dev/null @@ -1,116 +0,0 @@ -# Modding -Modding Minecraft: Pi Edition is possible by patching the binary at runtime. To make this easier ``minecraft-pi-reborn`` includes a library called ``libreborn.so`` which provides several functions to help you patch the game. - -## Hex Addresses -Minecraft: Pi Edition has no symbols so you must patch the hex address of an instruction instead of using a function name. Hex addresses can be found using tools like [Ghidra](https://ghidra-sre.org) or [RetDec](https://retdec.com). To find out what a function does, you can find its equivalent in Minecraft: Pocket Edition 0.6.1 and use its name for reference because Minecraft: Pocket Edition 0.6.1 includes symbols. - -## Loading Directories -``minecraft-pi-reborn`` loads mods from two locations, ``/app/minecraft-pi/mods``, and ``~/.minecraft-pi/mods``. The first location only exists in the Docker container and is immutable, so you should place your mods in ``~/.minecraft-pi/mods`` which is mounted on the host. - -## C++ Strings -Minecraft: Pi Edition was compiled with an old version of GCC, so when interacting with C++ strings, make sure you set ``-D_GLIBCXX_USE_CXX11_ABI=0``. - -## ``libreborn.so`` API -Header files and the shared library can be download from [Jenkins](https://jenkins.thebrokenrail.com/job/minecraft-pi-reborn/job/master/lastSuccessfulBuild/artifact/out/lib). - -### ``void overwrite(void *start, void *target)`` -This method replaces a function with another function. - -#### Parameters -- **start:** The function you are replacing. -- **target:** The function you are replacing it with. - -#### Return Value -None - -#### Warning -This should never be used on functions that are only 1 byte long because it overwrites 2 bytes. - -#### Example -```c -static int func_injection(int a, int b) { - return a + 4; -} - -__attribute__((constructor)) static void init() { - overwrite((void *) 0xabcde, func_injection); -} -``` - -### ``void overwrite_call(void *start, void *original)`` -This allows you to overwrite a specific call of a function rather than the function itself. This allows you to call the original function. However, this does not effect VTables. - -#### Parameters -- **start:** The address of the function call to overwrite. -- **target:** The function call you are replacing it with. - -#### Return Value -None - -#### Warning -This method can only be safely used 512 times in total. - -#### Example -```c -typedef int (*func_t)(int a, int b); -static func_t func = (func_t) 0xabcde; -static void *func_original = NULL; - -static int func_injection(int a, int b) { - (*func)(a, b); - - return a + 4; -} - -__attribute__((constructor)) static void init() { - overwrite_call((void *) 0xabcd, func_injection); -} -``` - -### ``void overwrite_calls(void *start, void *original)`` -This allows you to overwrite all calls of a function rather than the function itself. This allows you to call the original function. However, this does not effect VTables. - -#### Parameters -- **start:** The function call to overwrite; -- **target:** The function call you are replacing it with. - -#### Return Value -None - -#### Warning -This method can only be safely used 512 times in total. - -#### Example -```c -typedef int (*func_t)(int a, int b); -static func_t func = (func_t) 0xabcde; -static void *func_original = NULL; - -static int func_injection(int a, int b) { - (*func)(a, b); - - return a + 4; -} - -__attribute__((constructor)) static void init() { - overwrite_calls((void *) func, func_injection); -} -``` - -### ``void patch(void *start, unsigned char patch[])`` -This allows you to replace a specific instruction. - -#### Parameters -- **start:** The target instruction. -- **patch:** The new instruction (array length must be 4). - -#### Return Value -None - -#### Example -```c -__attribute__((constructor)) static void init() { - unsigned char patch_data[4] = {0x00, 0x00, 0x00, 0x00}; - patch((void *) 0xabcde, patch_data); -} -``` diff --git a/docs/OVERRIDING_ASSETS.md b/docs/OVERRIDING_ASSETS.md index 45bacf8..fe158b9 100644 --- a/docs/OVERRIDING_ASSETS.md +++ b/docs/OVERRIDING_ASSETS.md @@ -1,5 +1,5 @@ # Overriding Assets -Normally, Minecraft: Pi Edition assets can be easily overridden by physically replacing the file, however ``minecraft-pi-=reborn`` uses a Docker image making this much harder to do. To make overriding assets easier, ``minecraft-pi-reborn`` provides an overrides folder. Any file located in Minecraft: Pi Edition's ``data`` folder can be overridden by placing a file with the same name and path in the overrides folder. The overrides folder is located at ``~/.minecraft-pi/overrides``. +To make overriding assets easier, MCPI-Reborn provides an overrides folder. Any file located in Minecraft: Pi Edition's ``data`` folder can be overridden by placing a file with the same name and path in the overrides folder. The overrides folder is located at ``~/.minecraft-pi/overrides``. ## Examples - ``data/images/terrain.png`` -> ``~/.minecraft-pi/overrides/images/terrain.png`` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d1cd6f7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,8 @@ +# Documentation +* [View Manual Installation](docs/INSTALL.md) +* [View Overriding Assets](OVERRIDING_ASSETS.md) +* [View Dedicated Server](DEDICATED_SERVER.md) +* [View Credits](CREDITS.md) +* [View Terminology](TERMINOLOGY.md) +* [View Building](BUILDING.md) +* [View Architecture](ARCHITECTURE.md) diff --git a/docs/TERMINOLOGY.md b/docs/TERMINOLOGY.md new file mode 100644 index 0000000..c8274d5 --- /dev/null +++ b/docs/TERMINOLOGY.md @@ -0,0 +1,10 @@ +# Terminology +| Name | Description | +| --- | --- | +| MCPI | Shorthand for Minecraft: Pi Edition | +| Host Architecture | The native architecture of the CPU that MCPi-Reborn will be running on | +| Native Component | A component that *can* be compiled for the host architecture | +| ARM Component | A component that *must* be compiled for ARM | +| Server Mode | A mode where MCPI is patched into behaving like a dedicated server | +| Client Mode | The normal behavior of MCPI | +| Stub | An implementation of a library where all functions either do nothing or error | diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md deleted file mode 100644 index 9627dbe..0000000 --- a/docs/TROUBLESHOOTING.md +++ /dev/null @@ -1,26 +0,0 @@ -# Troubleshooting -Game logs are located in ``/tmp/minecraft-pi``. - -## ``Couldn't connect to Docker daemon at http+docker://localhost - is it running?`` -Start Docker if it isn't running: -```sh -sudo service docker start -``` - -## ``Error response from daemon: error gathering device information while adding custom device "/dev/dri": no such file or directory`` -Make sure you are using the correct GPU drivers for your system. - -If you are using a Raspberry Pi, make sure your GPU driver is set to ``Full KMS`` or ``Fake KMS`` in ``raspi-config``. - -## ``Segmentation Fault`` (Exit Code: ``139``) -Report an issue with reproduction instructions and system details. - -## ``[ERR]: Invalid ~/.minecraft-pi Permissions`` -Update ``~/.minecraft-pi`` permissions: -```sh -sudo chown -R "$(id -u):$(id -g)" ~/.minecraft-pi -chmod -R u+rw ~/.minecraft-pi -``` - -## Other -If you experience a crash/error not listed above, report it on the issue tracker **with your game log attached**. \ No newline at end of file diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 3f0dd29..612c762 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1,34 +1,18 @@ -cmake_minimum_required(VERSION 3.1.0) - project(launcher) -add_compile_options(-Wall -Wextra -Werror) - -## Launcher - -add_executable(launcher src/launcher.c) - -# Install -install(TARGETS launcher DESTINATION /) -install(PROGRAMS src/run.sh DESTINATION /) - -## Stubs - -# Stub RPI-Specific Graphics -add_library(bcm_host SHARED src/stubs/bcm_host.c) - -# Stub EGL -add_library(EGL SHARED src/stubs/EGL.c) - -# Stub SDL -add_library(SDL-1.2 SHARED src/stubs/SDL.cpp) -target_link_libraries(SDL-1.2 pthread) -set_target_properties(SDL-1.2 PROPERTIES SOVERSION "0") - -# MCPI Links Against GLESv2 But Uses GLESv1_CM -add_library(GLESv2 SHARED src/stubs/GLESv2.c) -target_link_libraries(GLESv2 GLESv1_CM) -target_link_options(GLESv2 PRIVATE "-Wl,--no-as-needed") - -# Install Stubs -install(TARGETS bcm_host EGL SDL-1.2 GLESv2 DESTINATION /lib) \ No newline at end of file +# Launcher +if(BUILD_NATIVE_COMPONENTS) + add_executable(launcher src/bootstrap.c src/ldconfig.cpp) + if(MCPI_SERVER_MODE) + target_sources(launcher PRIVATE src/server/launcher.c) + else() + target_sources(launcher PRIVATE src/client/launcher.cpp) + endif() + target_link_libraries(launcher reborn-headers) + # Install + install(TARGETS launcher DESTINATION "${MCPI_INSTALL_DIR}") + install_symlink("../../${MCPI_INSTALL_DIR}/launcher" "usr/bin/${MCPI_VARIANT_NAME}") + if(NOT MCPI_SERVER_MODE) + install(DIRECTORY "client-data/" DESTINATION ".") + endif() +endif() diff --git a/launcher/client-data/opt/minecraft-pi-reborn-client/available-feature-flags b/launcher/client-data/opt/minecraft-pi-reborn-client/available-feature-flags new file mode 100644 index 0000000..f6d73cd --- /dev/null +++ b/launcher/client-data/opt/minecraft-pi-reborn-client/available-feature-flags @@ -0,0 +1,16 @@ +TRUE Touch GUI +TRUE Fix Bow & Arrow +TRUE Fix Attacking +TRUE Mob Spawning +TRUE Fancy Graphics +TRUE Disable Autojump By Default +TRUE Display Nametags By Default +TRUE Fix Sign Placement +TRUE Show Block Outlines +FALSE Expand Creative Inventory +FALSE Peaceful Mode +TRUE Animated Water +TRUE Remove Invalid Item Background +TRUE Disable gui_blocks Atlas +TRUE Smooth Lighting +FALSE 3D Anaglyph diff --git a/debian/client/common/usr/share/applications/minecraft-pi.desktop b/launcher/client-data/usr/share/applications/minecraft-pi-reborn-client.desktop similarity index 54% rename from debian/client/common/usr/share/applications/minecraft-pi.desktop rename to launcher/client-data/usr/share/applications/minecraft-pi-reborn-client.desktop index 2899f93..f96a572 100755 --- a/debian/client/common/usr/share/applications/minecraft-pi.desktop +++ b/launcher/client-data/usr/share/applications/minecraft-pi-reborn-client.desktop @@ -1,10 +1,10 @@ [Desktop Entry] -Name=Minecraft: Pi Edition +Name=Minecraft: Pi Edition: Reborn Comment=Fun with Blocks -Icon=/usr/share/pixmaps/minecraft-pi.png +Icon=/usr/share/pixmaps/minecraft-pi-reborn-client.png StartupNotify=false StartupWMClass=Minecraft - Pi edition -Exec=/usr/bin/minecraft-pi +Exec=/usr/bin/minecraft-pi-reborn-client Terminal=false Type=Application Categories=Application;Game; diff --git a/debian/client/common/usr/share/pixmaps/minecraft-pi.png b/launcher/client-data/usr/share/pixmaps/minecraft-pi-reborn-client.png similarity index 100% rename from debian/client/common/usr/share/pixmaps/minecraft-pi.png rename to launcher/client-data/usr/share/pixmaps/minecraft-pi-reborn-client.png diff --git a/launcher/src/bootstrap.c b/launcher/src/bootstrap.c new file mode 100644 index 0000000..3921fe2 --- /dev/null +++ b/launcher/src/bootstrap.c @@ -0,0 +1,222 @@ +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "bootstrap.h" +#include "ldconfig.h" + +// Set Environmental Variable +#define PRESERVE_ENVIRONMENTAL_VARIABLE(name) \ + { \ + char *original_env_value = getenv(name); \ + if (original_env_value != NULL) { \ + setenv("ORIGINAL_" name, original_env_value, 1); \ + } \ + } +static void trim(char **value) { + // Remove Trailing Colon + int length = strlen(*value); + if ((*value)[length - 1] == ':') { + (*value)[length - 1] = '\0'; + } + if ((*value)[0] == ':') { + *value = &(*value)[1]; + } +} +static void set_and_print_env(const char *name, char *value) { + // Set Variable With No Trailing Colon + trim(&value); + +#ifdef DEBUG + // Print New Value + INFO("Set %s = %s", name, value); +#endif + // Set The Value + setenv(name, value, 1); +} + +// Get Environmental Variable +static char *get_env_safe(const char *name) { + // Get Variable Or Blank String If Not Set + char *ret = getenv(name); + return ret != NULL ? ret : ""; +} + +// Get All Mods In Folder +static void load(char **ld_preload, char *folder) { + int folder_name_length = strlen(folder); + // Retry Until Successful + while (1) { + // Open Folder + DIR *dp = opendir(folder); + if (dp != NULL) { + // Loop Through Folder + struct dirent *entry = NULL; + errno = 0; + while (1) { + errno = 0; + entry = readdir(dp); + if (entry != NULL) { + // Check If File Is Regular + if (entry->d_type == DT_REG) { + // Get Full Name + int name_length = strlen(entry->d_name); + int total_length = folder_name_length + name_length; + char name[total_length + 1]; + + // Concatenate Folder Name And File Name + for (int i = 0; i < folder_name_length; i++) { + name[i] = folder[i]; + } + for (int i = 0; i < name_length; i++) { + name[folder_name_length + i] = entry->d_name[i]; + } + // Add Terminator + name[total_length] = '\0'; + + // Check If File Is Executable + int result = access(name, R_OK); + if (result == 0) { + // Add To LD_PRELOAD + string_append(ld_preload, ":%s", name); + } else if (result == -1 && errno != 0) { + // Fail + INFO("Unable To Acesss: %s: %s", name, strerror(errno)); + errno = 0; + } + } + } else if (errno != 0) { + // Error Reading Contents Of Folder + ERR("Error Reading Directory: %s: %s", folder, strerror(errno)); + } else { + // Done! + break; + } + } + // Close Folder + closedir(dp); + + // Exit Function + return; + } else if (errno == ENOENT) { + // Folder Doesn't Exists, Attempt Creation + int ret = mkdir(folder, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (ret != 0) { + // Unable To Create Folder + ERR("Error Creating Directory: %s: %s", folder, strerror(errno)); + } + // Continue Retrying + } else { + // Unable To Open Folder + ERR("Error Opening Directory: %s: %s", folder, strerror(errno)); + } + } +} + +#define MCPI_NAME "minecraft-pi" + +// Bootstrap +void bootstrap(int argc, char *argv[]) { + INFO("%s", "Configuring Game..."); + + // Get Binary Directory + char *binary_directory = get_binary_directory(); + + // Configure LD_LIBRARY_PATH + { + PRESERVE_ENVIRONMENTAL_VARIABLE("LD_LIBRARY_PATH"); + // Add Library Directory + char *new_ld_path; + safe_asprintf(&new_ld_path, "%s/lib", binary_directory); + // Add Existing LD_LIBRAR_PATH + { + char *value = get_env_safe("LD_LIBRARY_PATH"); + if (strlen(value) > 0) { + string_append(&new_ld_path, ":%s", value); + } + } + // Load ARM Libraries +#ifdef __ARM_ARCH + string_append(&new_ld_path, "%s", ":/usr/lib/arm-linux-gnueabihf:/usr/arm-linux-gnueabihf/lib"); +#endif + // Add Full Library Search Path + { + char *value = get_full_library_search_path(); + if (strlen(value) > 0) { + string_append(&new_ld_path, ":%s", value); + } + free(value); + } + // Add Fallback Library Directory + string_append(&new_ld_path, ":%s/fallback-lib", binary_directory); + // Set And Free + set_and_print_env("LD_LIBRARY_PATH", new_ld_path); + free(new_ld_path); + } + + // Configure LD_PRELOAD + { + PRESERVE_ENVIRONMENTAL_VARIABLE("LD_PRELOAD"); + char *new_ld_preload = NULL; + safe_asprintf(&new_ld_preload, "%s", get_env_safe("LD_PRELOAD")); + + // Get Mods Folder + char *mods_folder = NULL; + safe_asprintf(&mods_folder, "%s/mods/", binary_directory); + // Load Mods From ./mods + load(&new_ld_preload, mods_folder); + // Free Mods Folder + free(mods_folder); + + // Set LD_PRELOAD + set_and_print_env("LD_PRELOAD", new_ld_preload); + free(new_ld_preload); + } + + // Free Binary Directory + free(binary_directory); + + // Start Game + INFO("%s", "Starting Game..."); +#ifdef __ARM_ARCH + // Create Arguments List + char *new_argv[argc + 1]; + for (int i = 1; i <= argc; i++) { + new_argv[i] = argv[i]; + } + new_argv[0] = NULL; // Updated By safe_execvpe() + // Run + safe_execvpe_relative_to_binary(MCPI_NAME, new_argv, environ); +#else + // Use Static QEMU So It Isn't Affected By LD_* Variables +#define QEMU_NAME "qemu-arm-static" + // Use Correct LibC + setenv("QEMU_LD_PREFIX", "/usr/arm-linux-gnueabihf", 1); + + // Get Binary Directory + binary_directory = get_binary_directory(); + // Create Full Path + char *full_path = NULL; + safe_asprintf(&full_path, "%s/" MCPI_NAME, binary_directory); + // Free Binary Directory + free(binary_directory); + + // Create Arguments List + char *new_argv[argc + 2]; + for (int i = 1; i <= argc; i++) { + new_argv[i + 1] = argv[i]; + } + new_argv[0] = NULL; // Updated By safe_execvpe() + new_argv[1] = full_path; // Path To MCPI + // Run + safe_execvpe(QEMU_NAME, new_argv, environ); +#endif +} diff --git a/mods/src/screenshot/screenshot.h b/launcher/src/bootstrap.h similarity index 61% rename from mods/src/screenshot/screenshot.h rename to launcher/src/bootstrap.h index 27a76c9..9559fd4 100644 --- a/mods/src/screenshot/screenshot.h +++ b/launcher/src/bootstrap.h @@ -4,8 +4,8 @@ extern "C" { #endif -void take_screenshot(); +void bootstrap(int argc, char *argv[]); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/launcher/src/client/launcher.cpp b/launcher/src/client/launcher.cpp new file mode 100644 index 0000000..bb7a58b --- /dev/null +++ b/launcher/src/client/launcher.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "../bootstrap.h" + +// Load Available Feature Flags +static void load_available_feature_flags(std::function callback) { + // Get Path + char *binary_directory = get_binary_directory(); + std::string path = std::string(binary_directory) + "/available-feature-flags"; + free(binary_directory); + // Load File + std::ifstream stream(path); + if (stream && stream.good()) { + std::string line; + while (std::getline(stream, line)) { + if (line.length() > 0) { + // Verify Line + if (line.find('|') == std::string::npos) { + callback(line); + } else { + // Invalid Line + ERR("%s", "Feature Flag Contains Invalid '|'"); + } + } + } + stream.close(); + } else { + ERR("%s", "Unable To Load Available Feature Flags"); + } +} + +// Run Command And Get Output +static char *run_command(char *command[], int *return_code) { + // Store Output + int output_pipe[2]; + safe_pipe2(output_pipe, 0); + // Run + pid_t ret = fork(); + if (ret == -1) { + ERR("Unable To Run Command: %s", strerror(errno)); + } else if (ret == 0) { + // Child Process + + // Pipe stdout + dup2(output_pipe[1], STDOUT_FILENO); + close(output_pipe[0]); + close(output_pipe[1]); + + // Run + safe_execvpe(command[0], command, environ); + } else { + // Parent Process + + // Read stdout + close(output_pipe[1]); + char *output = NULL; + char c; + int bytes_read = 0; + while ((bytes_read = read(output_pipe[0], (void *) &c, 1)) > 0) { + string_append(&output, "%c", c); + } + close(output_pipe[0]); + + // Get Return Code + int status; + waitpid(ret, &status, 0); + *return_code = WIFEXITED(status) ? WEXITSTATUS(status) : 1; + + // Return + return output; + } +} +// Run Command And Set Environmental Variable +static void run_command_and_set_env(const char *env_name, char *command[]) { + // Only Run If Environmental Variable Is NULL + if (getenv(env_name) == NULL) { + // Run + int return_code; + char *output = run_command(command, &return_code); + if (output != NULL) { + // Trim + int length = strlen(output); + if (output[length - 1] == '\n') { + output[length - 1] = '\0'; + } + // Set + setenv(env_name, output, 1); + } + // Check Return Code + if (return_code != 0) { + ERR("%s", "Launch Interrupted"); + } + } +} + +// Use Zenity To Set Environmental Variable +static void run_zenity_and_set_env(const char *env_name, std::vector command) { + // Create Full Command + std::vector full_command; + full_command.push_back("zenity"); + full_command.push_back("--class"); + full_command.push_back("Minecraft - Pi edition"); + full_command.insert(full_command.end(), command.begin(), command.end()); + // Convert To C Array + const char *full_command_array[full_command.size() + 1]; + for (std::vector::size_type i = 0; i < full_command.size(); i++) { + full_command_array[i] = full_command[i].c_str(); + } + full_command_array[full_command.size()] = NULL; + // Run + run_command_and_set_env(env_name, (char **) full_command_array); +} + +// Launch +int main(int argc, char *argv[]) { + // Print Features + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--print-available-feature-flags") == 0) { + // Print Available Feature Flags + load_available_feature_flags([](std::string line) { + printf("%s\n", line.c_str()); + fflush(stdout); + }); + return 0; + } + } + + // Create ~/.minecraft-pi If Needed + // Minecraft Folder + { + char *minecraft_folder = NULL; + asprintf(&minecraft_folder, "%s/.minecraft-pi", getenv("HOME")); + ALLOC_CHECK(minecraft_folder); + { + // Check Minecraft Folder + struct stat obj; + if (stat(minecraft_folder, &obj) != 0 || !S_ISDIR(obj.st_mode)) { + // Create Minecraft Folder + int ret = mkdir(minecraft_folder, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (ret != 0) { + // Unable To Create Folder + ERR("Error Creating Directory: %s: %s", minecraft_folder, strerror(errno)); + } + } + } + free(minecraft_folder); + } + + // Setup MCPI_FEATURE_FLAGS + { + std::vector command; + command.push_back("--list"); + command.push_back("--checklist"); + command.push_back("--width"); + command.push_back("400"); + command.push_back("--height"); + command.push_back("400"); + command.push_back("--column"); + command.push_back("Enabled"); + command.push_back("--column"); + command.push_back("Feature"); + load_available_feature_flags([&command](std::string line) { + if (line.rfind("TRUE ", 0) == 0) { + // Enabled By Default + command.push_back("TRUE"); + command.push_back(line.substr(5, std::string::npos)); + } else if (line.rfind("FALSE ", 0) == 0) { + // Disabled By Default + command.push_back("FALSE"); + command.push_back(line.substr(6, std::string::npos)); + } else { + ERR("%s", "Invalid Feature Flag Default"); + } + }); + // Run + run_zenity_and_set_env("MCPI_FEATURE_FLAGS", command); + } + // Setup MCPI_RENDER_DISTANCE + { + std::vector command; + command.push_back("--list"); + command.push_back("--radiolist"); + command.push_back("--width"); + command.push_back("400"); + command.push_back("--height"); + command.push_back("400"); + command.push_back("--text"); + command.push_back("Minecraft Render Distance:"); + command.push_back("--column"); + command.push_back("Selected"); + command.push_back("--column"); + command.push_back("Name"); + command.push_back("FALSE"); + command.push_back("Far"); + command.push_back("FALSE"); + command.push_back("Normal"); + command.push_back("TRUE"); + command.push_back("Short"); + command.push_back("FALSE"); + command.push_back("Tiny"); + // Run + run_zenity_and_set_env("MCPI_RENDER_DISTANCE", command); + } + // Setup MCPI_USERNAME + { + std::vector command; + command.push_back("--entry"); + command.push_back("--text"); + command.push_back("Minecraft Username:"); + command.push_back("--entry-text"); + command.push_back("StevePi"); + // Run + run_zenity_and_set_env("MCPI_USERNAME", command); + } + + // Bootstrap + bootstrap(argc, argv); +} diff --git a/launcher/src/launcher.c b/launcher/src/launcher.c deleted file mode 100644 index 297eeac..0000000 --- a/launcher/src/launcher.c +++ /dev/null @@ -1,156 +0,0 @@ -#define _FILE_OFFSET_BITS 64 -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include - -// Check String Prefix/Suffix -static int starts_with(const char *s, const char *t) { - return strncmp(s, t, strlen(t)) == 0; -} -static int ends_with(const char *s, const char *t) { - size_t slen = strlen(s); - size_t tlen = strlen(t); - if (tlen > slen) return 1; - return strcmp(s + slen - tlen, t) == 0; -} - -// Set Environmental Variable -static void trim(char *value) { - // Remove Trailing Colon - int length = strlen(value); - if (value[length - 1] == ':') { - value[length - 1] = '\0'; - } -} -static void set_and_print_env(char *name, char *value) { - // Set Variable With No Trailing Colon - trim(value); - - fprintf(stderr, "Set %s = %s\n", name, value); - setenv(name, value, 1); -} - -// Get Environmental Variable -static char *get_env_safe(const char *name) { - // Get Variable Or Blank String If Not Set - char *ret = getenv(name); - return ret != NULL ? ret : ""; -} - -// Get All SO Files In Folder -static void load(char **ld_preload, char *folder) { - int folder_name_length = strlen(folder); - // Retry Until Successful - while (1) { - // Open Folder - DIR *dp = opendir(folder); - if (dp != NULL) { - // Loop Through Folder - struct dirent *entry = NULL; - errno = 0; - while (1) { - entry = readdir(dp); - if (entry != NULL) { - // Check If File Is A Shared Library - if (entry->d_type == DT_REG && starts_with(entry->d_name, "lib") && ends_with(entry->d_name, ".so")) { - int name_length = strlen(entry->d_name); - int total_length = folder_name_length + name_length; - char name[total_length + 1]; - - for (int i = 0; i < folder_name_length; i++) { - name[i] = folder[i]; - } - for (int i = 0; i < name_length; i++) { - name[folder_name_length + i] = entry->d_name[i]; - } - - name[total_length] = '\0'; - - // Add To LD_PRELOAD - asprintf(ld_preload, "%s:%s", name, *ld_preload); - } - } else if (errno != 0) { - // Error Reading Contents Of Folder - fprintf(stderr, "Error Reading Directory: %s: %s\n", folder, strerror(errno)); - exit(EXIT_FAILURE); - } else { - // Done! - break; - } - } - // Close Folder - closedir(dp); - - // Exit Function - return; - } else if (errno == ENOENT) { - // Folder Doesn't Exists, Attempt Creation - int ret = mkdir(folder, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - if (ret != 0) { - // Unable To Create Folder - fprintf(stderr, "Error Creating Directory: %s: %s\n", folder, strerror(errno)); - exit(EXIT_FAILURE); - } - // Continue Retrying - } else { - // Unable To Open Folder - fprintf(stderr, "Error Opening Directory: %s: %s\n", folder, strerror(errno)); - exit(EXIT_FAILURE); - } - } -} - -int main(__attribute__((unused)) int argc, char *argv[]) { - fprintf(stderr, "Configuring Game...\n"); - - // Minecraft Folder - char *minecraft_folder = NULL; - asprintf(&minecraft_folder, "%s/.minecraft-pi", getenv("HOME")); - { - // Check Minecraft Folder - struct stat obj; - if (stat(minecraft_folder, &obj) != 0 || !S_ISDIR(obj.st_mode)) { - // Create Minecraft Folder - int ret = mkdir(minecraft_folder, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - if (ret != 0) { - // Unable To Create Folder - fprintf(stderr, "Error Creating Directory: %s: %s\n", minecraft_folder, strerror(errno)); - exit(EXIT_FAILURE); - } - } - } - free(minecraft_folder); - - // Configure LD_LIBRARY_PATH - char *ld_path; - asprintf(&ld_path, "./lib:/usr/arm-linux-gnueabihf/lib:%s", get_env_safe("LD_LIBRARY_PATH")); - set_and_print_env("LD_LIBRARY_PATH", ld_path); - free(ld_path); - - // Start Configuring LD_PRELOAD - char *ld_preload = NULL; - asprintf(&ld_preload, "%s", get_env_safe("LD_PRELOAD")); - - // Load Mods From ./mods - load(&ld_preload, "./mods/"); - - // Loads Mods From ~/.minecraft-pi/mods - char *home_mods = NULL; - asprintf(&home_mods, "%s/.minecraft-pi/mods/", getenv("HOME")); - load(&ld_preload, home_mods); - free(home_mods); - - // Set LD_PRELOAD - set_and_print_env("LD_PRELOAD", ld_preload); - free(ld_preload); - - // Start Game - fprintf(stderr, "Starting Game...\n"); - return execve("./minecraft-pi", argv, environ); -} diff --git a/launcher/src/ldconfig.cpp b/launcher/src/ldconfig.cpp new file mode 100644 index 0000000..44d7347 --- /dev/null +++ b/launcher/src/ldconfig.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +#include + +#include "ldconfig.h" + +char *get_full_library_search_path() { + std::string output; + // Run + FILE *file = popen("ldconfig -NXv 2> /dev/null", "r"); + // Read + int running = 1; + while (running) { + char *line = NULL; + size_t length = 0; + if (getline(&line, &length, file) != -1) { + // Convert to C++ String + std::string str(line); + // Remove Newline + if (str.size() > 0 && str[str.size() - 1] == '\n') { + str.pop_back(); + } + // Interpret + if (str.size() >= 2 && str[0] != '\t' && str[str.size() - 1] == ':') { + output.append(str); + } + } else { + running = 0; + } + free(line); + } + // Remove Colon + if (output.size() > 0 && output[output.size() - 1] == ':') { + output.pop_back(); + } + // Close Process + pclose(file); + // Return + char *output_str = strdup(output.c_str()); + ALLOC_CHECK(output_str); + return output_str; +} diff --git a/launcher/src/ldconfig.h b/launcher/src/ldconfig.h new file mode 100644 index 0000000..1d08daa --- /dev/null +++ b/launcher/src/ldconfig.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +char *get_full_library_search_path(); // Remember To free() + +#ifdef __cplusplus +} +#endif diff --git a/launcher/src/run.sh b/launcher/src/run.sh deleted file mode 100755 index 9bc3d43..0000000 --- a/launcher/src/run.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh - -set -e - -# Check Root -if [ ! "$(id -u)" = '0' ]; then - echo 'Must Run As Root' 1>&2 - exit 1 -fi - -# Check -if ! id user > /dev/null 2>&1; then - # Create User Groups - if [ -z "${USER_GID+x}" ]; then - USER_GID='1000' - fi - groupadd --force --gid "${USER_GID}" user - - # Create User - if [ -z "${USER_UID+x}" ]; then - USER_UID='1000' - fi - useradd --shell /bin/sh --home-dir /home --no-create-home --uid "${USER_UID}" --gid "${USER_GID}" user - - # Add Other Groups - if [ ! -z "${USER_OTHER_GIDS+x}" ]; then - for gid in ${USER_OTHER_GIDS}; do - groupadd --force --gid "${gid}" "group-${gid}" - usermod -aG "${gid}" user - done - fi -fi - -# Start -exec gosu user ./launcher \ No newline at end of file diff --git a/launcher/src/server/launcher.c b/launcher/src/server/launcher.c new file mode 100644 index 0000000..c3f0bf5 --- /dev/null +++ b/launcher/src/server/launcher.c @@ -0,0 +1,6 @@ +#include "../bootstrap.h" + +int main(int argc, char *argv[]) { + // Bootstrap + bootstrap(argc, argv); +} diff --git a/libreborn/CMakeLists.txt b/libreborn/CMakeLists.txt index f39f04a..da08f68 100644 --- a/libreborn/CMakeLists.txt +++ b/libreborn/CMakeLists.txt @@ -1,13 +1,11 @@ -cmake_minimum_required(VERSION 3.13.0) - project(libreborn) -add_compile_options(-Wall -Wextra -Werror) -add_link_options(-Wl,--no-undefined) +add_library(reborn-headers INTERFACE) +target_include_directories(reborn-headers INTERFACE include) -add_library(reborn SHARED src/libreborn.c) -target_link_libraries(reborn dl) -target_include_directories(reborn PUBLIC include) - -# Install -install(TARGETS reborn DESTINATION /mods) \ No newline at end of file +if(BUILD_ARM_COMPONENTS) + add_library(reborn SHARED src/libreborn.c) + target_link_libraries(reborn dl reborn-headers) + # Install + install(TARGETS reborn DESTINATION "${MCPI_LIB_DIR}") +endif() diff --git a/libreborn/include/libreborn/elf.h b/libreborn/include/libreborn/elf.h new file mode 100644 index 0000000..2d4278f --- /dev/null +++ b/libreborn/include/libreborn/elf.h @@ -0,0 +1,67 @@ +#pragma once + +#ifdef __arm__ +#include +#include +#include +#include + +#include "log.h" + +// Find And Iterate Over All .text Sections In Current Binary +typedef void (*text_section_callback_t)(void *section, Elf32_Word size, void *data); +static inline void iterate_text_sections(text_section_callback_t callback, void *data) { + // Load Main Binary + char *real_path = realpath("/proc/self/exe", NULL); + FILE *file_obj = fopen(real_path, "rb"); + + // Verify Binary + if (!file_obj) { + ERR("Unable To Open Binary: %s", real_path); + } + + // Get File Size + fseek(file_obj, 0L, SEEK_END); + long int size = ftell(file_obj); + fseek(file_obj, 0L, SEEK_SET); + + // Map File To Pointer + unsigned char *file_map = (unsigned char *) mmap(0, size, PROT_READ, MAP_PRIVATE, fileno(file_obj), 0); + + // Parse ELF + Elf32_Ehdr *elf_header = (Elf32_Ehdr *) file_map; + Elf32_Shdr *elf_section_headers = (Elf32_Shdr *) (file_map + elf_header->e_shoff); + int elf_section_header_count = elf_header->e_shnum; + + // Locate Section Names + Elf32_Shdr elf_strtab = elf_section_headers[elf_header->e_shstrndx]; + unsigned char *elf_strtab_p = file_map + elf_strtab.sh_offset; + + // Track .text Sections + int text_sections = 0; + + // Iterate Sections + for (int i = 0; i < elf_section_header_count; ++i) { + Elf32_Shdr header = elf_section_headers[i]; + char *name = (char *) (elf_strtab_p + header.sh_name); + // Check Section Type + if (strcmp(name, ".text") == 0) { + // .text Section + (*callback)((void *) header.sh_addr, header.sh_size, data); + text_sections++; + } + } + + // Ensure At Least .text Section Was Scanned + if (text_sections < 1) { + ERR("Unable To Find .text Sectons On: %s", real_path); + } + + // Free Binary Path + free(real_path); + + // Unmap And Close File + munmap(file_map, size); + fclose(file_obj); +} +#endif // #ifdef __arm__ diff --git a/libreborn/include/libreborn/exec.h b/libreborn/include/libreborn/exec.h new file mode 100644 index 0000000..12630b4 --- /dev/null +++ b/libreborn/include/libreborn/exec.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "string.h" +#include "util.h" + +// Safe execvpe() +__attribute__((noreturn)) static inline void safe_execvpe(const char *pathname, char *argv[], char *const envp[]) { + argv[0] = (char *) pathname; + int ret = execvpe(pathname, argv, envp); + if (ret == -1) { + ERR("Unable To Execute Program: %s: %s", pathname, strerror(errno)); + } else { + ERR("%s", "Unknown execvpe() Error"); + } +} +// Get Binary Directory (Remember To Free) +#define EXE_PATH "/proc/self/exe" +static inline char *get_binary_directory() { + // Get Path To Current Executable + // Get Symlink Path Size + struct stat sb; + if (lstat(EXE_PATH, &sb) == -1) { + ERR("Unable To Get " EXE_PATH " Symlink Size: %s", strerror(errno)); + } + ssize_t path_size = sb.st_size + 1; + if (sb.st_size == 0) { + path_size = PATH_MAX; + } + char *exe = (char *) malloc(path_size); + ALLOC_CHECK(exe); + // Read Link + ssize_t r = readlink(EXE_PATH, exe, path_size); + if (r < 0) { + ERR("Unable To Read " EXE_PATH " Symlink: %s", strerror(errno)); + } + if (r > path_size) { + ERR("%s", "Size Of Symlink " EXE_PATH " Changed"); + } + exe[path_size] = '\0'; + + // Chop Off Last Component + for (int i = path_size - 1; i >= 0; i--) { + if (exe[i] == '/') { + exe[i] = '\0'; + break; + } + } + + // Return + return exe; +} +// Safe execvpe() Relative To Binary +__attribute__((noreturn)) static inline void safe_execvpe_relative_to_binary(const char *pathname, char *argv[], char *const envp[]) { + // Get Binary Directory + char *binary_directory = get_binary_directory(); + // Create Full Path + char *full_path = NULL; + safe_asprintf(&full_path, "%s/%s", binary_directory, pathname); + // Free Binary Directory + free(binary_directory); + // Run + safe_execvpe(full_path, argv, envp); +} diff --git a/libreborn/include/libreborn/libreborn.h b/libreborn/include/libreborn/libreborn.h index 4c76b84..35ae28f 100644 --- a/libreborn/include/libreborn/libreborn.h +++ b/libreborn/include/libreborn/libreborn.h @@ -4,39 +4,13 @@ extern "C" { #endif -#include -#include -#include +#include "log.h" +#include "util.h" +#include "string.h" +#include "exec.h" +#include "elf.h" -// Logging -#define INFO(msg, ...) fprintf(stderr, "[INFO]: " msg "\n", __VA_ARGS__); -#define ERR(msg, ...) fprintf(stderr, "[ERR]: " msg "\n", __VA_ARGS__); exit(EXIT_FAILURE); - -// Check Memory Allocation -#define ALLOC_CHECK(obj) if (obj == NULL) { ERR("(%s:%i) Memory Allocation Failed", __FILE__, __LINE__); } - -// Set obj To NULL On asprintf() Failure -#define asprintf(obj, ...) if (asprintf(obj, __VA_ARGS__) == -1) { *obj = NULL; } - -// Hook Library Function -#define HOOK(name, return_type, args) \ - typedef return_type (*name##_t)args; \ - static name##_t real_##name = NULL; \ - \ - __attribute__((__unused__)) static void ensure_##name() { \ - if (!real_##name) { \ - dlerror(); \ - real_##name = (name##_t) dlsym(RTLD_NEXT, #name); \ - if (!real_##name) { \ - ERR("Error Resolving Symbol: "#name": %s", dlerror()); \ - } \ - } \ - }; \ - \ - __attribute__((__used__)) return_type name args - -// Sanitize String -void sanitize_string(char **str, int max_length, unsigned int allow_newlines); +#ifdef __arm__ // Patching Functions @@ -55,6 +29,8 @@ void _patch(const char *file, int line, void *start, unsigned char patch[]); void _patch_address(const char *file, int line, void *start, void *target); #define patch_address(start, target) _patch_address(__FILE__, __LINE__, start, target); +#endif // #ifdef __arm__ + #ifdef __cplusplus } #endif diff --git a/libreborn/include/libreborn/log.h b/libreborn/include/libreborn/log.h new file mode 100644 index 0000000..fad7ca0 --- /dev/null +++ b/libreborn/include/libreborn/log.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +// Logging +#define INFO(format, ...) { fprintf(stderr, "[INFO]: " format "\n", __VA_ARGS__); } +#define WARN(format, ...) { fprintf(stderr, "[WARN]: " format "\n", __VA_ARGS__); } +#define ERR(format, ...) { fprintf(stderr, "[ERR]: " format "\n", __VA_ARGS__); exit(EXIT_FAILURE); } diff --git a/libreborn/include/libreborn/minecraft.h b/libreborn/include/libreborn/minecraft.h index 41c2314..cc96e01 100644 --- a/libreborn/include/libreborn/minecraft.h +++ b/libreborn/include/libreborn/minecraft.h @@ -404,7 +404,7 @@ struct ConnectedClient { // AppPlatform -typedef void (*AppPlatform_saveScreenshot_t)(unsigned char *app_platform, std::string const& param1, std::string const& param_2); +typedef void (*AppPlatform_saveScreenshot_t)(unsigned char *app_platform, std::string const& path, int32_t width, int32_t height); static void *AppPlatform_linux_saveScreenshot_vtable_addr = (void *) 0x102160; typedef AppPlatform_readAssetFile_return_value (*AppPlatform_readAssetFile_t)(unsigned char *app_platform, std::string const& path); @@ -453,4 +453,4 @@ static SelectWorldScreen_getUniqueLevelName_t Touch_SelectWorldScreen_getUniqueL #endif -#pragma GCC diagnostic pop \ No newline at end of file +#pragma GCC diagnostic pop diff --git a/libreborn/include/libreborn/string.h b/libreborn/include/libreborn/string.h new file mode 100644 index 0000000..755b302 --- /dev/null +++ b/libreborn/include/libreborn/string.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include "util.h" + +// Set obj To NULL On asprintf() Failure +#define safe_asprintf(obj, ...) \ + { \ + if (asprintf(obj, __VA_ARGS__) == -1) { \ + *obj = NULL; \ + } \ + ALLOC_CHECK(*obj); \ + } + +// Dynamic String Append Macro +#define string_append(str, format, ...) \ + { \ + char *old = *str; \ + safe_asprintf(str, "%s" format, *str == NULL ? "" : *str, __VA_ARGS__); \ + ALLOC_CHECK(*str); \ + if (old != NULL && old != *str) { \ + free(old); \ + } \ + } + +// Sanitize String +#define MINIMUM_SAFE_CHARACTER 32 +#define MAXIMUM_SAFE_CHARACTER 126 +#define MINIMUM_EXTENDED_SAFE_CHARACTER 128 +static inline void sanitize_string(char **str, int max_length, unsigned int allow_newlines) { + // Store Message Length + int length = strlen(*str); + // Truncate Message + if (max_length != -1 && length > max_length) { + (*str)[max_length] = '\0'; + length = max_length; + } + // Loop Through Message + for (int i = 0; i < length; i++) { + if (allow_newlines && ((*str)[i] == '\n' || (*str)[i] == '\r')) { + continue; + } + unsigned char c = (unsigned char) (*str)[i]; + if ((c < MINIMUM_SAFE_CHARACTER || c > MAXIMUM_SAFE_CHARACTER) && c < MINIMUM_EXTENDED_SAFE_CHARACTER) { + // Replace Illegal Character + (*str)[i] = '?'; + } + } +} diff --git a/libreborn/include/libreborn/util.h b/libreborn/include/libreborn/util.h new file mode 100644 index 0000000..563d8a3 --- /dev/null +++ b/libreborn/include/libreborn/util.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include "log.h" + +// Check Memory Allocation +#define ALLOC_CHECK(obj) \ + { \ + if (obj == NULL) { \ + ERR("(%s:%i) Memory Allocation Failed", __FILE__, __LINE__); \ + } \ + } + +// Hook Library Function +#define HOOK(name, return_type, args) \ + typedef return_type (*name##_t)args; \ + static name##_t real_##name = NULL; \ + \ + __attribute__((__unused__)) static void ensure_##name() { \ + if (!real_##name) { \ + dlerror(); \ + real_##name = (name##_t) dlsym(RTLD_NEXT, #name); \ + if (!real_##name) { \ + ERR("Error Resolving Symbol: "#name": %s", dlerror()); \ + } \ + } \ + }; \ + \ + __attribute__((__used__)) return_type name args + +// Macro To Reset Environmental Variables To Pre-MCPI State +#define RESET_ENVIRONMENTAL_VARIABLE(name) \ + { \ + char *original_env_value = getenv("ORIGINAL_" name); \ + if (original_env_value != NULL) { \ + setenv(name, original_env_value, 1); \ + } else { \ + unsetenv(name); \ + } \ + } + +// Safe Version Of pipe() +static inline void safe_pipe2(int pipefd[2], int flags) { + if (pipe2(pipefd, flags) != 0) { + ERR("Unable To Create Pipe: %s", strerror(errno)); + } +} diff --git a/libreborn/src/libreborn.c b/libreborn/src/libreborn.c index 8e8f65a..5be5da2 100644 --- a/libreborn/src/libreborn.c +++ b/libreborn/src/libreborn.c @@ -4,65 +4,14 @@ #include #include #include -#include #include +#include #include -// Find And Iterate Over All .text Sections In Current Binary -typedef void (*text_section_callback)(void *section, Elf32_Word size, void *data); -static void iterate_text_section(text_section_callback callback, void *data) { - // Load Main Binary - char *real_path = realpath("/proc/self/exe", NULL); - FILE *file_obj = fopen(real_path, "rb"); - - // Verify Binary - if (!file_obj) { - ERR("Unable To Open Binary: %s", real_path); - } - - // Get File Size - fseek(file_obj, 0L, SEEK_END); - long int size = ftell(file_obj); - fseek(file_obj, 0L, SEEK_SET); - - // Map File To Pointer - unsigned char *file_map = mmap(0, size, PROT_READ, MAP_PRIVATE, fileno(file_obj), 0); - - // Parse ELF - Elf32_Ehdr *elf_header = (Elf32_Ehdr *) file_map; - Elf32_Shdr *elf_section_headers = (Elf32_Shdr *) (file_map + elf_header->e_shoff); - int elf_section_header_count = elf_header->e_shnum; - - // Locate Section Names - Elf32_Shdr elf_strtab = elf_section_headers[elf_header->e_shstrndx]; - unsigned char *elf_strtab_p = file_map + elf_strtab.sh_offset; - - // Track .text Sections - int text_sections = 0; - - // Iterate Sections - for (int i = 0; i < elf_section_header_count; ++i) { - Elf32_Shdr header = elf_section_headers[i]; - char *name = (char *) (elf_strtab_p + header.sh_name); - if (strcmp(name, ".text") == 0) { - (*callback)((void *) header.sh_addr, header.sh_size, data); - text_sections++; - } - } - - // Ensure At Least .text Section Was Scanned - if (text_sections < 1) { - ERR("Unable To Find .text Sectons On: %s", real_path); - } - - // Free Binary Path - free(real_path); - - // Unmap And Close File - munmap(file_map, size); - fclose(file_obj); -} +#ifndef __arm__ +#error "Patching Code Is ARM Only" +#endif // #ifndef __arm__ // BL Instruction Magic Number #define BL_INSTRUCTION 0xeb @@ -124,7 +73,9 @@ static void update_code_block(void *target) { if (code_block == MAP_FAILED) { ERR("Unable To Allocate Code Block: %s", strerror(errno)); } +#ifdef DEBUG INFO("Code Block Allocated At: 0x%08x", (uint32_t) code_block); +#endif } if (code_block_remaining < CODE_SIZE) { ERR("%s", "Maximum Amount Of overwrite_calls() Uses Reached"); @@ -160,7 +111,7 @@ void _overwrite_calls(const char *file, int line, void *start, void *target) { data.replacement = code_block; data.found = 0; - iterate_text_section(overwrite_calls_callback, &data); + iterate_text_sections(overwrite_calls_callback, &data); // Increment Code Block Position increment_code_block(); @@ -179,10 +130,18 @@ void _overwrite(const char *file, int line, void *start, void *target) { } // Print Patch Debug Data +#ifdef DEBUG #define PATCH_PRINTF(file, line, start, str) if (file != NULL) fprintf(stderr, "[PATCH]: (%s:%i) Patching (0x%08x) - "str": 0x%02x 0x%02x 0x%02x 0x%02x\n", file, line, (uint32_t) start, data[0], data[1], data[2], data[3]); +#else +#define PATCH_PRINTF(file, line, start, str) { (void) file; (void) line; (void) start; (void) str; } // Mark As Used +#endif // Patch Instruction void _patch(const char *file, int line, void *start, unsigned char patch[]) { + if (((uint32_t) start) % 4 != 0) { + ERR("%s", "Invalid Address"); + } + size_t page_size = sysconf(_SC_PAGESIZE); uintptr_t end = ((uintptr_t) start) + 4; uintptr_t page_start = ((uintptr_t) start) & -page_size; @@ -210,27 +169,3 @@ void _patch_address(const char *file, int line, void *start, void *target) { unsigned char patch_data[4] = {addr & 0xff, (addr >> 8) & 0xff, (addr >> 16) & 0xff, (addr >> 24) & 0xff}; _patch(file, line, start, patch_data); } - -// Sanitize String -#define MINIMUM_SAFE_CHARACTER 32 -#define MAXIMUM_SAFE_CHARACTER 126 -#define MINIMUM_EXTENDED_SAFE_CHARACTER 128 -void sanitize_string(char **str, int max_length, unsigned int allow_newlines) { - // Store Message Length - int length = strlen(*str); - // Truncate Message - if (max_length != -1 && length > max_length) { - (*str)[max_length] = '\0'; - length = max_length; - } - // Loop Through Message - for (int i = 0; i < length; i++) { - if (allow_newlines && ((*str)[i] == '\n' || (*str)[i] == '\r')) { - continue; - } - if (((*str)[i] < MINIMUM_SAFE_CHARACTER || (*str)[i] > MAXIMUM_SAFE_CHARACTER) && (*str)[i] < MINIMUM_EXTENDED_SAFE_CHARACTER) { - // Replace Illegal Character - (*str)[i] = '?'; - } - } -} \ No newline at end of file diff --git a/media-layer/CMakeLists.txt b/media-layer/CMakeLists.txt new file mode 100644 index 0000000..3403405 --- /dev/null +++ b/media-layer/CMakeLists.txt @@ -0,0 +1,40 @@ +project(media-layer) + +# Check Options +if(MCPI_USE_MEDIA_LAYER_PROXY) + if(MCPI_SERVER_MODE) + message(FATAL_ERROR "Server Mode With Media Layer Proxy Configuration Is Redundant") + endif() + if(MCPI_BUILD_MODE STREQUAL "both") + message(FATAL_ERROR "Media Layer Proxy Is Redundant When Building ARM And Native Components In The Same Build") + endif() +endif() + +# Add Headers +add_library(media-layer-headers INTERFACE) +target_include_directories(media-layer-headers INTERFACE include) + +# Add Core +add_subdirectory(core) + +# Add Proxy +if(MCPI_USE_MEDIA_LAYER_PROXY) + add_subdirectory(proxy) +endif() + +# Add Stubs +add_subdirectory(stubs) + +# Add Extras +add_subdirectory(extras) + +# Add Symlinks So MCPI Can Locate Libraries +if(BUILD_ARM_COMPONENTS) + if(MCPI_SERVER_MODE OR MCPI_USE_MEDIA_LAYER_PROXY) + install_symlink("libmedia-layer-core.so" "${MCPI_LIB_DIR}/libX11.so.6") + else() + # When Loading In Client Mode On An ARM Host, Use Native X11 By Default + install_symlink("../lib/libmedia-layer-core.so" "${MCPI_FALLBACK_LIB_DIR}/libX11.so.6") + endif() + install_symlink("libmedia-layer-core.so" "${MCPI_LIB_DIR}/libSDL-1.2.so.0") +endif() diff --git a/media-layer/core/CMakeLists.txt b/media-layer/core/CMakeLists.txt new file mode 100644 index 0000000..30b5b69 --- /dev/null +++ b/media-layer/core/CMakeLists.txt @@ -0,0 +1,29 @@ +project(media-layer-core) + +# Configuration +set(CORE_SRC src/base.cpp src/media.c src/screenshot.c) # SDL Re-Implementation Using GLFW + +# Build +if(MCPI_USE_MEDIA_LAYER_PROXY AND BUILD_NATIVE_COMPONENTS) + # Building Native Components + add_library(media-layer-core OBJECT ${CORE_SRC}) # Dependencies Are Setup Later +elseif(NOT MCPI_USE_MEDIA_LAYER_PROXY AND BUILD_ARM_COMPONENTS) + # Building ARM Components + add_library(media-layer-core SHARED ${CORE_SRC}) # Dependencies Are Setup Later + # Install + install(TARGETS media-layer-core DESTINATION "${MCPI_LIB_DIR}") +endif() + +# Configure Media Layer Core If Built +if(TARGET media-layer-core) + # Link + target_link_libraries(media-layer-core media-layer-headers reborn-headers pthread dl) + if(NOT MCPI_SERVER_MODE) + # Find GLFW + find_package(glfw3 3.2 REQUIRED) + # Find FreeImage + find_library(FREEIMAGE_LIBRARY NAMES freeimage libfreeimage.so.3 REQUIRED) + # Not Needed In Server Mode + target_link_libraries(media-layer-core "${FREEIMAGE_LIBRARY}" GLESv1_CM glfw) + endif() +endif() diff --git a/launcher/src/stubs/SDL.cpp b/media-layer/core/src/base.cpp similarity index 83% rename from launcher/src/stubs/SDL.cpp rename to media-layer/core/src/base.cpp index 29ee20c..9b17b1d 100644 --- a/launcher/src/stubs/SDL.cpp +++ b/media-layer/core/src/base.cpp @@ -1,25 +1,27 @@ -#include +#include #include #include #include +#include + // SDL Is Replaced With GLFW int SDL_Init(__attribute__((unused)) uint32_t flags) { return 0; } -void SDL_Quit() { - exit(EXIT_SUCCESS); -} - // Event Queue static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; static std::vector queue; int SDL_PollEvent(SDL_Event *event) { + // Handle External Events + _media_handle_SDL_PollEvent(); + + // Poll Event pthread_mutex_lock(&queue_mutex); int ret; if (queue.size() > 0) { diff --git a/media-layer/core/src/media.c b/media-layer/core/src/media.c new file mode 100644 index 0000000..46336d3 --- /dev/null +++ b/media-layer/core/src/media.c @@ -0,0 +1,326 @@ +#include + +#include +#include + +#ifndef MCPI_SERVER_MODE +#define GLFW_INCLUDE_NONE +#include +#endif // #ifndef MCPI_SERVER_MODE + +#include +#include +#include + +// GLFW Code Not Needed In Server Mode +#ifndef MCPI_SERVER_MODE + +static GLFWwindow *glfw_window; + +// 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(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; + // Hotbar + 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; + // 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; + // 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; + } +} + +// 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 = (int) c; + SDL_PushEvent(&event); +} + +// Pass Key Presses To SDL +static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, __attribute__((unused)) int mods) { + SDL_Event event; + int up = action == GLFW_RELEASE; + event.type = up ? SDL_KEYUP : SDL_KEYDOWN; + event.key.state = up ? SDL_RELEASED : SDL_PRESSED; + event.key.keysym.scancode = scancode; + event.key.keysym.mod = KMOD_NONE; + event.key.keysym.sym = glfw_key_to_sdl_key(key); + SDL_PushEvent(&event); + if (key == GLFW_KEY_BACKSPACE && !up) { + character_event((char) '\b'); + } +} + +// Pass Text To Minecraft +static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int codepoint) { + character_event((char) codepoint); +} + +static double last_mouse_x = 0; +static double last_mouse_y = 0; +static int ignore_relative_mouse = 1; + +// Pass Mouse Movement To SDL +static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) { + SDL_Event event; + event.type = SDL_MOUSEMOTION; + event.motion.x = xpos; + event.motion.y = ypos; + event.motion.xrel = !ignore_relative_mouse ? (xpos - last_mouse_x) : 0; + event.motion.yrel = !ignore_relative_mouse ? (ypos - last_mouse_y) : 0; + ignore_relative_mouse = 0; + last_mouse_x = xpos; + last_mouse_y = ypos; + SDL_PushEvent(&event); +} + +// 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(__attribute__((unused)) GLFWwindow *window, int button, int action, __attribute__((unused)) int mods) { + 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); +} + +// Pass Mouse Scroll To SDL +static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) { + if (yoffset != 0) { + int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; + click_event(sdl_button, 0); + click_event(sdl_button, 1); + } +} + +#endif // #ifndef MCPI_SERVER_MODE + +// Init GLFW +void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *icon) { + // Don't Enable GLFW In Server Mode +#ifndef MCPI_SERVER_MODE + glfwSetErrorCallback(glfw_error); + + if (!glfwInit()) { + ERR("%s", "Unable To Initialize GLFW"); + } + + // Create OpenGL ES 1.1 Context + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + // Extra Settings + glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE); + glfwWindowHint(GLFW_ALPHA_BITS, 0); // Fix Transparent Window On Wayland + + glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL); + if (!glfw_window) { + ERR("%s", "Unable To Create GLFW Window"); + } + + // Don't Process Events In Server Mode + 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); + + glfwMakeContextCurrent(glfw_window); +#else // #ifndef MCPI_SERVER_MODE + (void) title; // Mark As Used +#endif // #ifndef MCPI_SERVER_MODE +} + +void media_swap_buffers() { +#ifndef MCPI_SERVER_MODE + // Don't Swap Buffers In A Context-Less Window + glfwSwapBuffers(glfw_window); +#endif // #ifndef MCPI_SERVER_MODE +} + +// Fullscreen Not Needed In Server Mode +#ifndef MCPI_SERVER_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 // #ifndef MCPI_SERVER_MODE +void media_toggle_fullscreen() { +} +#endif // #ifndef MCPI_SERVER_MODE + +// Intercept SDL Events +void _media_handle_SDL_PollEvent() { + // GLFW Is Disabled In Server Mode +#ifndef MCPI_SERVER_MODE + // 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); + } +#endif // #ifndef MCPI_SERVER_MODE +} + +// Terminate GLFW +void media_cleanup() { + // GLFW Is Disabled In Server Mode +#ifndef MCPI_SERVER_MODE + glfwDestroyWindow(glfw_window); + glfwTerminate(); +#endif // #ifndef MCPI_SERVER_MODE +} + +#ifdef MCPI_SERVER_MODE +static SDL_GrabMode fake_grab_mode = SDL_GRAB_OFF; +#endif // #ifdef MCPI_SERVER_MODE + +// Fix SDL Cursor Visibility/Grabbing +SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode) { +#ifdef MCPI_SERVER_MODE + // Don't Grab Input In Server Mode + if (mode != SDL_GRAB_QUERY) { + fake_grab_mode = mode; + } + return fake_grab_mode; +#else // #ifdef MCPI_SERVER_MODE + if (mode != SDL_GRAB_QUERY && mode != SDL_WM_GrabInput(SDL_GRAB_QUERY)) { + glfwSetInputMode(glfw_window, GLFW_CURSOR, mode == SDL_GRAB_OFF ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); +#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3) + glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, mode == SDL_GRAB_OFF ? GLFW_FALSE : GLFW_TRUE); +#endif // #if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3) + + // Reset Last Mouse Position + ignore_relative_mouse = 1; + } + return mode == SDL_GRAB_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_GRAB_OFF : SDL_GRAB_ON) : mode; +#endif // #ifdef MCPI_SERVER_MODE +} + +// Stub SDL Cursor Visibility +int SDL_ShowCursor(int toggle) { +#ifdef MCPI_SERVER_MODE + return toggle == SDL_QUERY ? (fake_grab_mode == SDL_GRAB_OFF ? SDL_ENABLE : SDL_DISABLE) : toggle; +#else // #ifdef MCPI_SERVER_MODE + return toggle == SDL_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_ENABLE : SDL_DISABLE) : toggle; +#endif // #ifdef MCPI_SERVER_MODE +} + +// Get Framebuffer Size +void media_get_framebuffer_size(int *width, int *height) { +#ifndef MCPI_SERVER_MODE + if (glfw_window != NULL) { + glfwGetFramebufferSize(glfw_window, width, height); + return; + } +#endif // #ifndef MCPI_SERVER_MODE + *width = DEFAULT_WIDTH; + *height = DEFAULT_HEIGHT; +} diff --git a/mods/src/screenshot/screenshot.c b/media-layer/core/src/screenshot.c similarity index 79% rename from mods/src/screenshot/screenshot.c rename to media-layer/core/src/screenshot.c index 36ed467..992b199 100644 --- a/mods/src/screenshot/screenshot.c +++ b/media-layer/core/src/screenshot.c @@ -1,4 +1,5 @@ -#define _GNU_SOURCE +// Screenshot Code Is Useless In Server Mode +#ifndef MCPI_SERVER_MODE #include #include @@ -13,14 +14,13 @@ #include #include - -#include "screenshot.h" +#include // 4 (Year) + 1 (Hyphen) + 2 (Month) + 1 (Hyphen) + 2 (Day) + 1 (Underscore) + 2 (Hour) + 1 (Period) + 2 (Minute) + 1 (Period) + 2 (Second) + 1 (Null Terminator) #define TIME_SIZE 20 // Take Screenshot -void take_screenshot() { +void media_take_screenshot() { time_t rawtime; struct tm *timeinfo; @@ -30,16 +30,15 @@ void take_screenshot() { strftime(time, TIME_SIZE, "%Y-%m-%d_%H.%M.%S", timeinfo); char *screenshots = NULL; - asprintf(&screenshots, "%s/.minecraft-pi/screenshots", getenv("HOME")); - ALLOC_CHECK(screenshots); + safe_asprintf(&screenshots, "%s/.minecraft-pi/screenshots", getenv("HOME")); int num = 1; char *file = NULL; - asprintf(&file, "%s/%s.png", screenshots, time); - ALLOC_CHECK(file); + safe_asprintf(&file, "%s/%s.png", screenshots, time); while (access(file, F_OK) != -1) { - asprintf(&file, "%s/%s-%i.png", screenshots, time, num); - ALLOC_CHECK(file); + free(file); + file = NULL; + safe_asprintf(&file, "%s/%s-%i.png", screenshots, time, num); num++; } @@ -56,7 +55,7 @@ void take_screenshot() { unsigned char pixels[size]; glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels); -#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#if __BYTE_ORDER == __LITTLE_ENDIAN // Swap Red And Blue for (int i = 0; i < (size / 3); i++) { int pixel = i * 3; @@ -86,8 +85,7 @@ __attribute__((constructor)) static void init() { // Screenshots Folder char *screenshots_folder = NULL; - asprintf(&screenshots_folder, "%s/.minecraft-pi/screenshots", getenv("HOME")); - ALLOC_CHECK(screenshots_folder); + safe_asprintf(&screenshots_folder, "%s/.minecraft-pi/screenshots", getenv("HOME")); { // Check Screenshots Folder struct stat obj; @@ -101,4 +99,10 @@ __attribute__((constructor)) static void init() { } } free(screenshots_folder); -} \ No newline at end of file +} + +#else // #ifndef MCPI_SERVER_MODE +void media_take_screenshot() { + // NOP +} +#endif // #ifndef MCPI_SERVER_MODE diff --git a/media-layer/extras/CMakeLists.txt b/media-layer/extras/CMakeLists.txt new file mode 100644 index 0000000..3629892 --- /dev/null +++ b/media-layer/extras/CMakeLists.txt @@ -0,0 +1,7 @@ +project(media-layer-extras) + +if(BUILD_ARM_COMPONENTS) + # Add Source To Media Core + target_sources(media-layer-core PRIVATE src/SDL.c src/X11.cpp) + target_link_libraries(media-layer-core dl) +endif() diff --git a/media-layer/extras/src/SDL.c b/media-layer/extras/src/SDL.c new file mode 100644 index 0000000..8b30236 --- /dev/null +++ b/media-layer/extras/src/SDL.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +#include +#include + +// SDL Stub +void *SDL_SetVideoMode(__attribute__((unused)) int width, __attribute__((unused)) int height, __attribute__((unused)) int bpp, __attribute__((unused)) uint32_t flags) { + // Return Value Is Only Used For A NULL-Check + return (void *) 1; +} + +static void x11_nop() { + // NOP +} +int SDL_GetWMInfo(SDL_SysWMinfo *info) { + // Return Fake Lock Functions Since XLib Isn't Directly Used + SDL_SysWMinfo ret; + ret.info.x11.lock_func = x11_nop; + ret.info.x11.unlock_func = x11_nop; + ret.info.x11.display = NULL; + ret.info.x11.window = 0; + ret.info.x11.wmwindow = ret.info.x11.window; + *info = ret; + return 1; +} + +// Quit +__attribute__ ((noreturn)) void SDL_Quit() { + // Cleanup Media Layer + media_cleanup(); + + // Wait For Children To Stop + while (wait(NULL) > 0) {} + + // Exit + INFO("%s", "Stopped"); + exit(EXIT_SUCCESS); +} diff --git a/media-layer/extras/src/X11.cpp b/media-layer/extras/src/X11.cpp new file mode 100644 index 0000000..5723c06 --- /dev/null +++ b/media-layer/extras/src/X11.cpp @@ -0,0 +1,76 @@ +#include +#include + +#include + +#include +#include + +#ifndef MEDIA_LAYER_PROXY_SERVER +// Store Adresses That Are Part Of MCPI +struct text_section_data { + void *section; + Elf32_Word size; +}; +static std::vector &get_text_sections() { + static std::vector sections; + return sections; +} +static void text_section_callback(void *section, Elf32_Word size, __attribute__((unused)) void *data) { + text_section_data section_data; + section_data.section = section; + section_data.size = size; + get_text_sections().push_back(section_data); +} +// Check If The Current Return Address Is Part Of MCPI Or An External Library +static inline int _is_returning_to_external_library(void *return_addr) { + // Load Text Sections If Not Loaded + if (get_text_sections().size() < 1) { + iterate_text_sections(text_section_callback, NULL); + } + // Iterate Text Sections + for (std::vector::size_type i = 0; i < get_text_sections().size(); i++) { + text_section_data section_data = get_text_sections()[i]; + // Check Text Section + if (return_addr >= section_data.section && return_addr < (((unsigned char *) section_data.section) + section_data.size)) { + // Part Of MCPI + return 0; + } + } + // Part Of An External Library + return 1; +} +#define is_returning_to_external_library() _is_returning_to_external_library(__builtin_extract_return_addr(__builtin_return_address(0))) +#else +// When Using The Media Layer Proxy, None Of These Functions Are Ever Called By An External Library +#define is_returning_to_external_library() 0 +#endif + +// Don't Directly Use XLib Unless Returning To An External library +HOOK(XTranslateCoordinates, int, (void *display, XID src_w, XID dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, XID *child_return)) { + if (is_returning_to_external_library()) { + // Use Real Function + ensure_XTranslateCoordinates(); + return (*real_XTranslateCoordinates)(display, src_w, dest_w, src_x, src_y, dest_x_return, dest_y_return, child_return); + } else { + // Use MCPI Replacemnt Function + *dest_x_return = src_x; + *dest_y_return = src_y; + return 1; + } +} +HOOK(XGetWindowAttributes, int, (void *display, XID w, XWindowAttributes *window_attributes_return)) { + if (is_returning_to_external_library()) { + // Use Real Function + ensure_XGetWindowAttributes(); + return (*real_XGetWindowAttributes)(display, w, window_attributes_return); + } else { + // Use MCPI Replacemnt Function + XWindowAttributes attributes; + attributes.x = 0; + attributes.y = 0; + media_get_framebuffer_size(&attributes.width, &attributes.height); + *window_attributes_return = attributes; + return 1; + } +} diff --git a/media-layer/include/EGL/egl.h b/media-layer/include/EGL/egl.h new file mode 100644 index 0000000..4df000f --- /dev/null +++ b/media-layer/include/EGL/egl.h @@ -0,0 +1,40 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +#define EGL_TRUE 1 + +typedef unsigned int EGLenum; +typedef unsigned int EGLBoolean; +typedef void *EGLDisplay; +typedef void *EGLConfig; +typedef void *EGLSurface; +typedef void *EGLContext; +typedef int32_t EGLint; + +typedef void *EGLNativeDisplayType; +typedef XID EGLNativeWindowType; +typedef EGLNativeWindowType NativeWindowType; +typedef EGLNativeDisplayType NativeDisplayType; + +EGLDisplay eglGetDisplay(NativeDisplayType native_display); +EGLBoolean eglInitialize(EGLDisplay display, EGLint *major, EGLint *minor); +EGLBoolean eglChooseConfig(EGLDisplay display, EGLint const *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); +EGLBoolean eglBindAPI(EGLenum api); +EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, EGLint const *attrib_list); +EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, NativeWindowType native_window, EGLint const *attrib_list); +EGLBoolean eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context); +EGLBoolean eglDestroySurface(EGLDisplay display, EGLSurface surface); +EGLBoolean eglDestroyContext(EGLDisplay display, EGLContext context); +EGLBoolean eglTerminate(EGLDisplay display); +EGLBoolean eglSwapBuffers(EGLDisplay display, EGLSurface surface); + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/GLES/gl.h b/media-layer/include/GLES/gl.h new file mode 100644 index 0000000..7671121 --- /dev/null +++ b/media-layer/include/GLES/gl.h @@ -0,0 +1,86 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define GL_FALSE 0 +#define GL_FOG_COLOR 0x0B66 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_VIEWPORT 0x0BA2 +#define GL_DEPTH_TEST 0x0B71 + +#include +#include + +typedef float GLfloat; +typedef float GLclampf; +typedef int GLint; +typedef unsigned char GLboolean; +typedef int GLsizei; +typedef unsigned int GLuint; +typedef ssize_t GLsizeiptr; +typedef intptr_t GLintptr; +typedef int32_t GLfixed; +typedef unsigned int GLbitfield; +typedef unsigned int GLenum; + +void glFogfv(GLenum pname, const GLfloat *params); +void glVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer); +void glLineWidth(GLfloat width); +void glBlendFunc(GLenum sfactor, GLenum dfactor); +void glDrawArrays(GLenum mode, GLint first, GLsizei count); +void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void glClear(GLbitfield mask); +void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage); +void glFogx(GLenum pname, GLfixed param); +void glFogf(GLenum pname, GLfloat param); +void glMatrixMode(GLenum mode); +void glColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer); +void glScissor(GLint x, GLint y, GLsizei width, GLsizei height); +void glTexParameteri(GLenum target, GLenum pname, GLint param); +void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +void glEnable(GLenum cap); +void glEnableClientState(GLenum array); +void glPolygonOffset(GLfloat factor, GLfloat units); +void glDisableClientState(GLenum array); +void glDepthRangef(GLclampf near, GLclampf far); +void glDepthFunc(GLenum func); +void glBindBuffer(GLenum target, GLuint buffer); +void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +void glPopMatrix(); +void glLoadIdentity(); +void glScalef(GLfloat x, GLfloat y, GLfloat z); +void glPushMatrix(); +void glDepthMask(GLboolean flag); +void glHint(GLenum target, GLenum mode); +void glMultMatrixf(const GLfloat *m); +void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer); +void glDeleteBuffers(GLsizei n, const GLuint *buffers); +void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +void glGenTextures(GLsizei n, GLuint *textures); +void glDeleteTextures(GLsizei n, const GLuint *textures); +void glAlphaFunc(GLenum func, GLclampf ref); +void glGetFloatv(GLenum pname, GLfloat *params); +void glBindTexture(GLenum target, GLuint texture); +void glTranslatef(GLfloat x, GLfloat y, GLfloat z); +void glShadeModel(GLenum mode); +void glOrthof(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far); +void glDisable(GLenum cap); +void glCullFace(GLenum mode); +void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); +void glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz); +GLboolean glIsEnabled(GLenum cap); +void glGetIntegerv(GLenum pname, GLint *data); +void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *data); + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/SDL/SDL.h b/media-layer/include/SDL/SDL.h new file mode 100644 index 0000000..a0746cb --- /dev/null +++ b/media-layer/include/SDL/SDL.h @@ -0,0 +1,39 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "SDL_keysym.h" +#include "SDL_events.h" +#include "SDL_syswm.h" +#include "SDL_version.h" + +int SDL_Init(uint32_t flags); +int SDL_PollEvent(SDL_Event *event); +int SDL_PushEvent(SDL_Event *event); +void SDL_WM_SetCaption(const char *title, const char *icon); + +typedef enum { + SDL_GRAB_QUERY = -1, + SDL_GRAB_OFF = 0, + SDL_GRAB_ON = 1, + SDL_GRAB_FULLSCREEN +} SDL_GrabMode; +SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode); + +#define SDL_QUERY -1 +#define SDL_IGNORE 0 +#define SDL_DISABLE 0 +#define SDL_ENABLE 1 +int SDL_ShowCursor(int toggle); + +void *SDL_SetVideoMode(int width, int height, int bpp, uint32_t flags); +int SDL_GetWMInfo(SDL_SysWMinfo *info); +__attribute__ ((noreturn)) void SDL_Quit(); + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/SDL/SDL_events.h b/media-layer/include/SDL/SDL_events.h new file mode 100644 index 0000000..758de85 --- /dev/null +++ b/media-layer/include/SDL/SDL_events.h @@ -0,0 +1,160 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "SDL_keysym.h" + +#define SDL_RELEASED 0 +#define SDL_PRESSED 1 + +typedef enum { + SDL_NOEVENT = 0, + SDL_ACTIVEEVENT, + SDL_KEYDOWN, + SDL_KEYUP, + SDL_MOUSEMOTION, + SDL_MOUSEBUTTONDOWN, + SDL_MOUSEBUTTONUP, + SDL_JOYAXISMOTION, + SDL_JOYBALLMOTION, + SDL_JOYHATMOTION, + SDL_JOYBUTTONDOWN, + SDL_JOYBUTTONUP, + SDL_QUIT, + SDL_SYSWMEVENT, + SDL_EVENT_RESERVEDA, + SDL_EVENT_RESERVEDB, + SDL_VIDEORESIZE, + SDL_VIDEOEXPOSE, + SDL_EVENT_RESERVED2, + SDL_EVENT_RESERVED3, + SDL_EVENT_RESERVED4, + SDL_EVENT_RESERVED5, + SDL_EVENT_RESERVED6, + SDL_EVENT_RESERVED7, + SDL_USEREVENT = 24, + SDL_NUMEVENTS = 32 +} SDL_EventType; + +typedef struct SDL_ActiveEvent { + uint8_t type; + uint8_t gain; + uint8_t state; +} SDL_ActiveEvent; + +typedef struct SDL_keysym { + uint8_t scancode; + SDLKey sym; + SDLMod mod; + uint16_t unicode; +} SDL_keysym; + +typedef struct SDL_KeyboardEvent { + uint8_t type; + uint8_t which; + uint8_t state; + SDL_keysym keysym; +} SDL_KeyboardEvent; + +typedef struct SDL_MouseMotionEvent { + uint8_t type; + uint8_t which; + uint8_t state; + uint16_t x, y; + int16_t xrel; + int16_t yrel; +} SDL_MouseMotionEvent; + +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_WHEELUP 4 +#define SDL_BUTTON_WHEELDOWN 5 + +typedef struct SDL_MouseButtonEvent { + uint8_t type; + uint8_t which; + uint8_t button; + uint8_t state; + uint16_t x, y; +} SDL_MouseButtonEvent; + +typedef struct SDL_JoyAxisEvent { + uint8_t type; + uint8_t which; + uint8_t axis; + int16_t value; +} SDL_JoyAxisEvent; + +typedef struct SDL_JoyBallEvent { + uint8_t type; + uint8_t which; + uint8_t ball; + int16_t xrel; + int16_t yrel; +} SDL_JoyBallEvent; + +typedef struct SDL_JoyHatEvent { + uint8_t type; + uint8_t which; + uint8_t hat; + uint8_t value; +} SDL_JoyHatEvent; + +typedef struct SDL_JoyButtonEvent { + uint8_t type; + uint8_t which; + uint8_t button; + uint8_t state; +} SDL_JoyButtonEvent; + +typedef struct SDL_ResizeEvent { + uint8_t type; + int w; + int h; +} SDL_ResizeEvent; + +typedef struct SDL_ExposeEvent { + uint8_t type; +} SDL_ExposeEvent; + +typedef struct SDL_QuitEvent { + uint8_t type; +} SDL_QuitEvent; + +typedef struct SDL_UserEvent { + uint8_t type; + int code; + void *data1; + void *data2; +} SDL_UserEvent; + +typedef struct SDL_SysWMEvent { + uint8_t type; + void *msg; +} SDL_SysWMEvent; + +typedef union SDL_Event { + uint8_t type; + SDL_ActiveEvent active; + SDL_KeyboardEvent key; + SDL_MouseMotionEvent motion; + SDL_MouseButtonEvent button; + SDL_JoyAxisEvent jaxis; + SDL_JoyBallEvent jball; + SDL_JoyHatEvent jhat; + SDL_JoyButtonEvent jbutton; + SDL_ResizeEvent resize; + SDL_ExposeEvent expose; + SDL_QuitEvent quit; + SDL_UserEvent user; + SDL_SysWMEvent syswm; +} SDL_Event; + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/SDL/SDL_keysym.h b/media-layer/include/SDL/SDL_keysym.h new file mode 100644 index 0000000..3c918d2 --- /dev/null +++ b/media-layer/include/SDL/SDL_keysym.h @@ -0,0 +1,262 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SDLK_UNKNOWN = 0, + SDLK_FIRST = 0, + SDLK_BACKSPACE = 8, + SDLK_TAB = 9, + SDLK_CLEAR = 12, + SDLK_RETURN = 13, + SDLK_PAUSE = 19, + SDLK_ESCAPE = 27, + SDLK_SPACE = 32, + SDLK_EXCLAIM = 33, + SDLK_QUOTEDBL = 34, + SDLK_HASH = 35, + SDLK_DOLLAR = 36, + SDLK_AMPERSAND = 38, + SDLK_QUOTE = 39, + SDLK_LEFTPAREN = 40, + SDLK_RIGHTPAREN = 41, + SDLK_ASTERISK = 42, + SDLK_PLUS = 43, + SDLK_COMMA = 44, + SDLK_MINUS = 45, + SDLK_PERIOD = 46, + SDLK_SLASH = 47, + SDLK_0 = 48, + SDLK_1 = 49, + SDLK_2 = 50, + SDLK_3 = 51, + SDLK_4 = 52, + SDLK_5 = 53, + SDLK_6 = 54, + SDLK_7 = 55, + SDLK_8 = 56, + SDLK_9 = 57, + SDLK_COLON = 58, + SDLK_SEMICOLON = 59, + SDLK_LESS = 60, + SDLK_EQUALS = 61, + SDLK_GREATER = 62, + SDLK_QUESTION = 63, + SDLK_AT = 64, + SDLK_LEFTBRACKET = 91, + SDLK_BACKSLASH = 92, + SDLK_RIGHTBRACKET = 93, + SDLK_CARET = 94, + SDLK_UNDERSCORE = 95, + SDLK_BACKQUOTE = 96, + SDLK_a = 97, + SDLK_b = 98, + SDLK_c = 99, + SDLK_d = 100, + SDLK_e = 101, + SDLK_f = 102, + SDLK_g = 103, + SDLK_h = 104, + SDLK_i = 105, + SDLK_j = 106, + SDLK_k = 107, + SDLK_l = 108, + SDLK_m = 109, + SDLK_n = 110, + SDLK_o = 111, + SDLK_p = 112, + SDLK_q = 113, + SDLK_r = 114, + SDLK_s = 115, + SDLK_t = 116, + SDLK_u = 117, + SDLK_v = 118, + SDLK_w = 119, + SDLK_x = 120, + SDLK_y = 121, + SDLK_z = 122, + SDLK_DELETE = 127, + SDLK_WORLD_0 = 160, + SDLK_WORLD_1 = 161, + SDLK_WORLD_2 = 162, + SDLK_WORLD_3 = 163, + SDLK_WORLD_4 = 164, + SDLK_WORLD_5 = 165, + SDLK_WORLD_6 = 166, + SDLK_WORLD_7 = 167, + SDLK_WORLD_8 = 168, + SDLK_WORLD_9 = 169, + SDLK_WORLD_10 = 170, + SDLK_WORLD_11 = 171, + SDLK_WORLD_12 = 172, + SDLK_WORLD_13 = 173, + SDLK_WORLD_14 = 174, + SDLK_WORLD_15 = 175, + SDLK_WORLD_16 = 176, + SDLK_WORLD_17 = 177, + SDLK_WORLD_18 = 178, + SDLK_WORLD_19 = 179, + SDLK_WORLD_20 = 180, + SDLK_WORLD_21 = 181, + SDLK_WORLD_22 = 182, + SDLK_WORLD_23 = 183, + SDLK_WORLD_24 = 184, + SDLK_WORLD_25 = 185, + SDLK_WORLD_26 = 186, + SDLK_WORLD_27 = 187, + SDLK_WORLD_28 = 188, + SDLK_WORLD_29 = 189, + SDLK_WORLD_30 = 190, + SDLK_WORLD_31 = 191, + SDLK_WORLD_32 = 192, + SDLK_WORLD_33 = 193, + SDLK_WORLD_34 = 194, + SDLK_WORLD_35 = 195, + SDLK_WORLD_36 = 196, + SDLK_WORLD_37 = 197, + SDLK_WORLD_38 = 198, + SDLK_WORLD_39 = 199, + SDLK_WORLD_40 = 200, + SDLK_WORLD_41 = 201, + SDLK_WORLD_42 = 202, + SDLK_WORLD_43 = 203, + SDLK_WORLD_44 = 204, + SDLK_WORLD_45 = 205, + SDLK_WORLD_46 = 206, + SDLK_WORLD_47 = 207, + SDLK_WORLD_48 = 208, + SDLK_WORLD_49 = 209, + SDLK_WORLD_50 = 210, + SDLK_WORLD_51 = 211, + SDLK_WORLD_52 = 212, + SDLK_WORLD_53 = 213, + SDLK_WORLD_54 = 214, + SDLK_WORLD_55 = 215, + SDLK_WORLD_56 = 216, + SDLK_WORLD_57 = 217, + SDLK_WORLD_58 = 218, + SDLK_WORLD_59 = 219, + SDLK_WORLD_60 = 220, + SDLK_WORLD_61 = 221, + SDLK_WORLD_62 = 222, + SDLK_WORLD_63 = 223, + SDLK_WORLD_64 = 224, + SDLK_WORLD_65 = 225, + SDLK_WORLD_66 = 226, + SDLK_WORLD_67 = 227, + SDLK_WORLD_68 = 228, + SDLK_WORLD_69 = 229, + SDLK_WORLD_70 = 230, + SDLK_WORLD_71 = 231, + SDLK_WORLD_72 = 232, + SDLK_WORLD_73 = 233, + SDLK_WORLD_74 = 234, + SDLK_WORLD_75 = 235, + SDLK_WORLD_76 = 236, + SDLK_WORLD_77 = 237, + SDLK_WORLD_78 = 238, + SDLK_WORLD_79 = 239, + SDLK_WORLD_80 = 240, + SDLK_WORLD_81 = 241, + SDLK_WORLD_82 = 242, + SDLK_WORLD_83 = 243, + SDLK_WORLD_84 = 244, + SDLK_WORLD_85 = 245, + SDLK_WORLD_86 = 246, + SDLK_WORLD_87 = 247, + SDLK_WORLD_88 = 248, + SDLK_WORLD_89 = 249, + SDLK_WORLD_90 = 250, + SDLK_WORLD_91 = 251, + SDLK_WORLD_92 = 252, + SDLK_WORLD_93 = 253, + SDLK_WORLD_94 = 254, + SDLK_WORLD_95 = 255, + SDLK_KP0 = 256, + SDLK_KP1 = 257, + SDLK_KP2 = 258, + SDLK_KP3 = 259, + SDLK_KP4 = 260, + SDLK_KP5 = 261, + SDLK_KP6 = 262, + SDLK_KP7 = 263, + SDLK_KP8 = 264, + SDLK_KP9 = 265, + SDLK_KP_PERIOD = 266, + SDLK_KP_DIVIDE = 267, + SDLK_KP_MULTIPLY = 268, + SDLK_KP_MINUS = 269, + SDLK_KP_PLUS = 270, + SDLK_KP_ENTER = 271, + SDLK_KP_EQUALS = 272, + SDLK_UP = 273, + SDLK_DOWN = 274, + SDLK_RIGHT = 275, + SDLK_LEFT = 276, + SDLK_INSERT = 277, + SDLK_HOME = 278, + SDLK_END = 279, + SDLK_PAGEUP = 280, + SDLK_PAGEDOWN = 281, + SDLK_F1 = 282, + SDLK_F2 = 283, + SDLK_F3 = 284, + SDLK_F4 = 285, + SDLK_F5 = 286, + SDLK_F6 = 287, + SDLK_F7 = 288, + SDLK_F8 = 289, + SDLK_F9 = 290, + SDLK_F10 = 291, + SDLK_F11 = 292, + SDLK_F12 = 293, + SDLK_F13 = 294, + SDLK_F14 = 295, + SDLK_F15 = 296, + SDLK_NUMLOCK = 300, + SDLK_CAPSLOCK = 301, + SDLK_SCROLLOCK = 302, + SDLK_RSHIFT = 303, + SDLK_LSHIFT = 304, + SDLK_RCTRL = 305, + SDLK_LCTRL = 306, + SDLK_RALT = 307, + SDLK_LALT = 308, + SDLK_RMETA = 309, + SDLK_LMETA = 310, + SDLK_LSUPER = 311, + SDLK_RSUPER = 312, + SDLK_MODE = 313, + SDLK_COMPOSE = 314, + SDLK_HELP = 315, + SDLK_PRINT = 316, + SDLK_SYSREQ = 317, + SDLK_BREAK = 318, + SDLK_MENU = 319, + SDLK_POWER = 320, + SDLK_EURO = 321, + SDLK_UNDO = 322, + SDLK_LAST +} SDLKey; + +typedef enum { + KMOD_NONE = 0x0000, + KMOD_LSHIFT = 0x0001, + KMOD_RSHIFT = 0x0002, + KMOD_LCTRL = 0x0040, + KMOD_RCTRL = 0x0080, + KMOD_LALT = 0x0100, + KMOD_RALT = 0x0200, + KMOD_LMETA = 0x0400, + KMOD_RMETA = 0x0800, + KMOD_NUM = 0x1000, + KMOD_CAPS = 0x2000, + KMOD_MODE = 0x4000, + KMOD_RESERVED = 0x8000 +} SDLMod; + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/SDL/SDL_syswm.h b/media-layer/include/SDL/SDL_syswm.h new file mode 100644 index 0000000..feb82f2 --- /dev/null +++ b/media-layer/include/SDL/SDL_syswm.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "SDL_events.h" +#include "SDL_version.h" + +typedef enum { + SDL_SYSWM_X11 +} SDL_SYSWM_TYPE; + +typedef struct SDL_SysWMinfo { + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union { + struct { + void *display; + XID window; + void (*lock_func)(void); + void (*unlock_func)(void); + XID fswindow; + XID wmwindow; + void *gfxdisplay; + } x11; + } info; +} SDL_SysWMinfo; + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/SDL/SDL_version.h b/media-layer/include/SDL/SDL_version.h new file mode 100644 index 0000000..7b49ea6 --- /dev/null +++ b/media-layer/include/SDL/SDL_version.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SDL_version { + uint8_t major; + uint8_t minor; + uint8_t patch; +} SDL_version; + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/X11/Xlib.h b/media-layer/include/X11/Xlib.h new file mode 100644 index 0000000..64a651c --- /dev/null +++ b/media-layer/include/X11/Xlib.h @@ -0,0 +1,40 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef unsigned long XID; + +typedef struct { + int x, y; + int width, height; + int border_width; + int depth; + void *visual; + XID root; + int clazz; + int bit_gravity; + int win_gravity; + int backing_store; + unsigned long backing_planes; + unsigned long backing_pixel; + int save_under; + XID colormap; + int map_installed; + int map_state; + long all_event_masks; + long your_event_mask; + long do_not_propagate_mask; + int override_redirect; + void *screen; +} XWindowAttributes; + +int XTranslateCoordinates(void *display, XID src_w, XID dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, XID *child_return); +int XGetWindowAttributes(void *display, XID w, XWindowAttributes *window_attributes_return); + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/libreborn/media-layer/core.h b/media-layer/include/libreborn/media-layer/core.h new file mode 100644 index 0000000..73efa9e --- /dev/null +++ b/media-layer/include/libreborn/media-layer/core.h @@ -0,0 +1,19 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Default Window Size +#define DEFAULT_WIDTH 840 +#define DEFAULT_HEIGHT 480 + +void media_take_screenshot(); +void media_toggle_fullscreen(); +void media_swap_buffers(); +void media_cleanup(); +void media_get_framebuffer_size(int *width, int *height); + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/include/libreborn/media-layer/internal.h b/media-layer/include/libreborn/media-layer/internal.h new file mode 100644 index 0000000..9c1e157 --- /dev/null +++ b/media-layer/include/libreborn/media-layer/internal.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Internal Methods (Not Handled By Media Layer Proxy) + +__attribute__((visibility("internal"))) void _media_handle_SDL_PollEvent(); +__attribute__((visibility("internal"))) void _media_handle_SDL_Quit(); + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/proxy/CMakeLists.txt b/media-layer/proxy/CMakeLists.txt new file mode 100644 index 0000000..4049af8 --- /dev/null +++ b/media-layer/proxy/CMakeLists.txt @@ -0,0 +1,35 @@ +# Component Details: +# Media Layer Proxy +# +# This components proxies multi-media calls from the ARM +# MCPI to the native architecture by using a UNIX socket. + +project(media-layer-proxy) + +# Configuration +set(MEDIA_LAYER_PROXY_SRC src/common/common.c src/media-layer-core.c src/GLESv1_CM.c) # Media Layer Proxy Source +set(MEDIA_LAYER_PROXY_LIBS media-layer-core GLESv1_CM) # Media Layer Proxy Client Dependencies + +if(BUILD_NATIVE_COMPONENTS) + # Building Native Components + + # Build Media Layer Proxy Client + add_executable(media-layer-proxy-client src/client/client.cpp ${MEDIA_LAYER_PROXY_SRC}) + target_link_libraries(media-layer-proxy-client media-layer-headers reborn-headers ${MEDIA_LAYER_PROXY_LIBS}) + target_compile_definitions(media-layer-proxy-client PRIVATE -DMEDIA_LAYER_PROXY_CLIENT) + # Install + install(TARGETS media-layer-proxy-client DESTINATION "${MCPI_LIB_DIR}") +endif() + +if(BUILD_ARM_COMPONENTS) + # Building ARM Components + + # Build Media Layer Proxy Server + add_library(media-layer-core SHARED src/server/server.cpp ${MEDIA_LAYER_PROXY_SRC}) + target_link_libraries(media-layer-core media-layer-headers reborn-headers) + target_compile_definitions(media-layer-core PRIVATE -DMEDIA_LAYER_PROXY_SERVER) + # Symlink GLESv1_CM To Media Layer Proxy Server + install_symlink("libmedia-layer-core.so" "${MCPI_LIB_DIR}/libGLESv1_CM.so.1") + # Install + install(TARGETS media-layer-core DESTINATION "${MCPI_LIB_DIR}") +endif() diff --git a/media-layer/proxy/src/GLESv1_CM.c b/media-layer/proxy/src/GLESv1_CM.c new file mode 100644 index 0000000..8c4245d --- /dev/null +++ b/media-layer/proxy/src/GLESv1_CM.c @@ -0,0 +1,1077 @@ +#include + +#include + +#include + +#include "common/common.h" + +static int get_glFogfv_params_length(GLenum pname) { + return pname == GL_FOG_COLOR ? 4 : 1; +} +CALL(11, glFogfv, void, (GLenum pname, const GLfloat *params)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) pname); + int length = get_glFogfv_params_length(pname); + for (int i = 0; i < length; i++) { + write_float(params[i]); + } + + // Release Proxy + end_proxy_call(); +#else + GLenum pname = (GLenum) read_int(); + int length = get_glFogfv_params_length(pname); + GLfloat params[length]; + for (int i = 0; i < length; i++) { + params[i] = read_float(); + } + // Run + glFogfv(pname, params); +#endif +} + +// 'pointer' Is Only Supported As An Integer, Not As An Actual Pointer +#if defined(MEDIA_LAYER_PROXY_SERVER) +#define CALL_GL_POINTER(unique_id, name) \ + CALL(unique_id, name, void, (GLint size, GLenum type, GLsizei stride, const void *pointer)) { \ + /* Lock Proxy */ \ + start_proxy_call(); \ + \ + /* Arguments */ \ + write_int((uint32_t) size); \ + write_int((uint32_t) type); \ + write_int((uint32_t) stride); \ + write_int((uint32_t) pointer); \ + \ + /* Release Proxy */ \ + end_proxy_call(); \ + } +#else +#define CALL_GL_POINTER(unique_id, name) \ + CALL(unique_id, name, unused, unused) { \ + /* Check State */ \ + GLint current_buffer = 0; \ + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, ¤t_buffer); \ + if (current_buffer == 0) { \ + PROXY_ERR("%s", "gl*Pointer() Functions Are Only Supported When A Buffer Is Bound To GL_ARRAY_BUFFER"); \ + } \ + GLint size = (GLint) read_int(); \ + GLenum type = (GLenum) read_int(); \ + GLsizei stride = (GLsizei) read_int(); \ + const void *pointer = (const void *) (uintptr_t) read_int(); \ + /* Run */ \ + name(size, type, stride, pointer); \ + } +#endif + +CALL_GL_POINTER(12, glVertexPointer) + +CALL(13, glLineWidth, void, (GLfloat width)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(width); + + // Release Proxy + end_proxy_call(); +#else + GLfloat width = read_float(); + // Run + glLineWidth(width); +#endif +} + +CALL(14, glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) sfactor); + write_int((uint32_t) dfactor); + + // Release Proxy + end_proxy_call(); +#else + GLenum sfactor = (GLenum) read_int(); + GLenum dfactor = (GLenum) read_int(); + // Run + glBlendFunc(sfactor, dfactor); +#endif +} + +CALL(15, glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) mode); + write_int((uint32_t) first); + write_int((uint32_t) count); + + // Release Proxy + end_proxy_call(); +#else + GLenum mode = (GLenum) read_int(); + GLint first = (GLint) read_int(); + GLsizei count = (GLsizei) read_int(); + // Run + glDrawArrays(mode, first, count); +#endif +} + +CALL(16, glColor4f, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(red); + write_float(green); + write_float(blue); + write_float(alpha); + + // Release Proxy + end_proxy_call(); +#else + GLfloat red = read_float(); + GLfloat green = read_float(); + GLfloat blue = read_float(); + GLfloat alpha = read_float(); + // Run + glColor4f(red, green, blue, alpha); +#endif +} + +CALL(17, glClear, void, (GLbitfield mask)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) mask); + + // Release Proxy + end_proxy_call(); +#else + GLbitfield mask = (GLbitfield) read_int(); + // Run + glClear(mask); +#endif +} + +#if defined(MEDIA_LAYER_PROXY_CLIENT) +// Preserve Buffer For Performance +static size_t _glBufferData_data_size = 0; +static void *_glBufferData_data = NULL; +__attribute__((destructor)) static void _free_glBufferData_data() { + if (_glBufferData_data != NULL) { + free(_glBufferData_data); + } +} +#endif +CALL(18, glBufferData, void, (GLenum target, GLsizeiptr size, const void *data, GLenum usage)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) target); + write_int((uint32_t) size); + write_int((uint32_t) usage); + // Write Data + unsigned char is_null = data == NULL; + write_byte(is_null); + if (!is_null) { + safe_write((void *) data, (size_t) size); + } + + // Release Proxy + end_proxy_call(); +#else + GLenum target = (GLenum) read_int(); + GLsizeiptr size = (GLsizeiptr) read_int(); + GLenum usage = (GLenum) read_int(); + // Load Data + void *data = NULL; + unsigned char is_null = read_byte(); + if (!is_null) { + // Allocate + int size_changed = 0; + if (_glBufferData_data == NULL) { + _glBufferData_data = malloc((size_t) size); + size_changed = 1; + } else if (((size_t) size) > _glBufferData_data_size) { + _glBufferData_data = realloc(_glBufferData_data, (size_t) size); + size_changed = 1; + } + // Verify + ALLOC_CHECK(_glBufferData_data); + if (size_changed) { + _glBufferData_data_size = size; + } + data = _glBufferData_data; + // Read + safe_read(data, (size_t) size); + } + // Run + glBufferData(target, size, data, usage); +#endif +} + +CALL(19, glFogx, void, (GLenum pname, GLfixed param)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) pname); + write_int((uint32_t) param); + + // Release Proxy + end_proxy_call(); +#else + GLenum pname = (GLenum) read_int(); + GLfixed param = (GLfixed) read_int(); + // Run + glFogx(pname, param); +#endif +} + +CALL(20, glFogf, void, (GLenum pname, GLfloat param)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) pname); + write_float(param); + + // Release Proxy + end_proxy_call(); +#else + GLenum pname = (GLenum) read_int(); + GLfloat param = read_float(); + // Run + glFogf(pname, param); +#endif +} + +CALL(21, glMatrixMode, void, (GLenum mode)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) mode); + + // Release Proxy + end_proxy_call(); +#else + GLenum mode = (GLenum) read_int(); + // Run + glMatrixMode(mode); +#endif +} + +CALL_GL_POINTER(22, glColorPointer) + +CALL(23, glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) x); + write_int((uint32_t) y); + write_int((uint32_t) width); + write_int((uint32_t) height); + + // Release Proxy + end_proxy_call(); +#else + GLint x = (GLint) read_int(); + GLint y = (GLint) read_int(); + GLsizei width = (GLsizei) read_int(); + GLsizei height = (GLsizei) read_int(); + // Run + glScissor(x, y, width, height); +#endif +} + +CALL(24, glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) target); + write_int((uint32_t) pname); + write_int((uint32_t) param); + + // Release Proxy + end_proxy_call(); +#else + GLenum target = (GLenum) read_int(); + GLenum pname = (GLenum) read_int(); + GLint param = (GLint) read_int(); + // Run + glTexParameteri(target, pname, param); +#endif +} + +static int get_texture_size(GLsizei width, GLsizei height, GLenum format, GLenum type) { + int multiplier; + if (type == GL_UNSIGNED_BYTE) { + switch (format) { + case GL_RGB: { + PROXY_ERR("%s", "WIP"); + multiplier = 3; + break; + } + case GL_RGBA: { + multiplier = 4; + break; + } + default: { + PROXY_ERR("Invalid Texture Format: %u", (unsigned int) format); + } + } + } else { + multiplier = sizeof (unsigned short); + } + return width * height * multiplier; // This Should Have The Same Behavior On 32-Bit And 64-Bit Systems +} + +CALL(25, glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) target); + write_int((uint32_t) level); + write_int((uint32_t) internalformat); + write_int((uint32_t) width); + write_int((uint32_t) height); + write_int((uint32_t) border); + write_int((uint32_t) format); + write_int((uint32_t) type); + unsigned char is_null = pixels == NULL; + write_byte(is_null); + if (!is_null) { + int size = get_texture_size(width, height, format, type); + safe_write((void *) pixels, (size_t) size); + } + + // Release Proxy + end_proxy_call(); +#else + GLenum target = (GLenum) read_int(); + GLint level = (GLint) read_int(); + GLint internalformat = (GLint) read_int(); + GLsizei width = (GLsizei) read_int(); + GLsizei height = (GLsizei) read_int(); + GLint border = (GLint) read_int(); + GLenum format = (GLenum) read_int(); + GLenum type = (GLenum) read_int(); + unsigned char is_null = read_byte(); + void *pixels = NULL; + if (!is_null) { + int size = get_texture_size(width, height, format, type); + pixels = malloc(size); + ALLOC_CHECK(pixels); + safe_read(pixels, (size_t) size); + } + // Run + glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels); + // Free + if (!is_null) { + free(pixels); + } +#endif +} + +CALL(26, glEnable, void, (GLenum cap)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) cap); + + // Release Proxy + end_proxy_call(); +#else + GLenum cap = (GLenum) read_int(); + // Run + glEnable(cap); +#endif +} + +CALL(27, glEnableClientState, void, (GLenum array)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) array); + + // Release Proxy + end_proxy_call(); +#else + GLenum array = (GLenum) read_int(); + // Run + glEnableClientState(array); +#endif +} + +CALL(28, glPolygonOffset, void, (GLfloat factor, GLfloat units)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(factor); + write_float(units); + + // Release Proxy + end_proxy_call(); +#else + GLfloat factor = read_float(); + GLfloat units = read_float(); + // Run + glPolygonOffset(factor, units); +#endif +} + +CALL(29, glDisableClientState, void, (GLenum array)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) array); + + // Release Proxy + end_proxy_call(); +#else + GLenum array = (GLenum) read_int(); + // Run + glDisableClientState(array); +#endif +} + +CALL(30, glDepthRangef, void, (GLclampf near, GLclampf far)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(near); + write_float(far); + + // Release Proxy + end_proxy_call(); +#else + GLclampf near = read_float(); + GLclampf far = read_float(); + // Run + glDepthRangef(near, far); +#endif +} + +CALL(31, glDepthFunc, void, (GLenum func)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) func); + + // Release Proxy + end_proxy_call(); +#else + GLenum func = (GLenum) read_int(); + // Run + glDepthFunc(func); +#endif +} + +CALL(32, glBindBuffer, void, (GLenum target, GLuint buffer)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) target); + write_int((uint32_t) buffer); + + // Release Proxy + end_proxy_call(); +#else + GLenum target = (GLenum) read_int(); + GLuint buffer = (GLuint) read_int(); + // Run + glBindBuffer(target, buffer); +#endif +} + +CALL(33, glClearColor, void, (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(red); + write_float(green); + write_float(blue); + write_float(alpha); + + // Release Proxy + end_proxy_call(); +#else + GLclampf red = read_float(); + GLclampf green = read_float(); + GLclampf blue = read_float(); + GLclampf alpha = read_float(); + // Run + glClearColor(red, green, blue, alpha); +#endif +} + +CALL(34, glPopMatrix, void, ()) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + // Release Proxy + end_proxy_call(); +#else + // Run + glPopMatrix(); +#endif +} + +CALL(35, glLoadIdentity, void, ()) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + // Release Proxy + end_proxy_call(); +#else + // Run + glLoadIdentity(); +#endif +} + +CALL(36, glScalef, void, (GLfloat x, GLfloat y, GLfloat z)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(x); + write_float(y); + write_float(z); + + // Release Proxy + end_proxy_call(); +#else + GLfloat x = read_float(); + GLfloat y = read_float(); + GLfloat z = read_float(); + // Run + glScalef(x, y, z); +#endif +} + +CALL(37, glPushMatrix, void, ()) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + // Release Proxy + end_proxy_call(); +#else + // Run + glPushMatrix(); +#endif +} + +CALL(38, glDepthMask, void, (GLboolean flag)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) flag); + + // Release Proxy + end_proxy_call(); +#else + GLboolean flag = (GLboolean) read_int(); + // Run + glDepthMask(flag); +#endif +} + +CALL(39, glHint, void, (GLenum target, GLenum mode)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) target); + write_int((uint32_t) mode); + + // Release Proxy + end_proxy_call(); +#else + GLenum target = (GLenum) read_int(); + GLenum mode = (GLenum) read_int(); + // Run + glHint(target, mode); +#endif +} + +static int get_glMultMatrixf_size() { + return 16; +} +CALL(40, glMultMatrixf, void, (const GLfloat *m)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + safe_write((void *) m, sizeof (float) * get_glMultMatrixf_size()); + + // Release Proxy + end_proxy_call(); +#else + GLfloat m[get_glMultMatrixf_size()]; + safe_read((void *) m, sizeof (float) * get_glMultMatrixf_size()); + // Run + glMultMatrixf(m); +#endif +} + +CALL_GL_POINTER(41, glTexCoordPointer) + +CALL(42, glDeleteBuffers, void, (GLsizei n, const GLuint *buffers)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) n); + for (int i = 0; i < n; i++) { + write_int((uint32_t) buffers[i]); + } + + // Release Proxy + end_proxy_call(); +#else + GLsizei n = (GLsizei) read_int(); + GLuint buffers[n]; + for (int i = 0; i < n; i++) { + buffers[i] = (GLuint) read_int(); + } + // Run + glDeleteBuffers(n, buffers); +#endif +} + +CALL(43, glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) red); + write_int((uint32_t) green); + write_int((uint32_t) blue); + write_int((uint32_t) alpha); + + // Release Proxy + end_proxy_call(); +#else + GLboolean red = (GLboolean) read_int(); + GLboolean green = (GLboolean) read_int(); + GLboolean blue = (GLboolean) read_int(); + GLboolean alpha = (GLboolean) read_int(); + // Run + glColorMask(red, green, blue, alpha); +#endif +} + +CALL(44, glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) target); + write_int((uint32_t) level); + write_int((uint32_t) xoffset); + write_int((uint32_t) yoffset); + write_int((uint32_t) width); + write_int((uint32_t) height); + write_int((uint32_t) format); + write_int((uint32_t) type); + unsigned char is_null = pixels == NULL; + write_byte(is_null); + if (!is_null) { + int size = get_texture_size(width, height, format, type); + safe_write((void *) pixels, (size_t) size); + } + + // Release Proxy + end_proxy_call(); +#else + GLenum target = (GLenum) read_int(); + GLint level = (GLint) read_int(); + GLint xoffset = (GLint) read_int(); + GLint yoffset = (GLint) read_int(); + GLsizei width = (GLsizei) read_int(); + GLsizei height = (GLsizei) read_int(); + GLenum format = (GLenum) read_int(); + GLenum type = (GLenum) read_int(); + unsigned char is_null = read_byte(); + void *pixels = NULL; + if (!is_null) { + int size = get_texture_size(width, height, format, type); + pixels = malloc(size); + ALLOC_CHECK(pixels); + safe_read(pixels, (size_t) size); + } + // Run + glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); + // Free + if (!is_null) { + free(pixels); + } +#endif +} + +CALL(45, glGenTextures, void, (GLsizei n, GLuint *textures)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) n); + + // Get Return Value + for (GLsizei i = 0; i < n; i++) { + textures[i] = (GLuint) read_int(); + } + + // Release Proxy + end_proxy_call(); +#else + GLsizei n = (GLsizei) read_int(); + GLuint textures[n]; + // Run + glGenTextures(n, textures); + // Return Value + for (GLsizei i = 0; i < n; i++) { + write_int((uint32_t) textures[i]); + } +#endif +} + +CALL(46, glDeleteTextures, void, (GLsizei n, const GLuint *textures)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) n); + for (GLsizei i = 0; i < n; i++) { + write_int((uint32_t) textures[i]); + } + + // Release Proxy + end_proxy_call(); +#else + GLsizei n = (GLsizei) read_int(); + GLuint textures[n]; + for (GLsizei i = 0; i < n; i++) { + textures[i] = (GLuint) read_int(); + } + // Run + glDeleteTextures(n, textures); +#endif +} + +CALL(47, glAlphaFunc, void, (GLenum func, GLclampf ref)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) func); + write_float(ref); + + // Release Proxy + end_proxy_call(); +#else + GLenum func = (GLenum) read_int(); + GLclampf ref = read_float(); + // Run + glAlphaFunc(func, ref); +#endif +} + +static int get_glGetFloatv_params_size(GLenum pname) { + switch (pname) { + case GL_MODELVIEW_MATRIX: + case GL_PROJECTION_MATRIX: { + return 16; + } + default: { + PROXY_ERR("Inavlid glGetFloatv Property: %u", pname); + } + } +} +CALL(48, glGetFloatv, void, (GLenum pname, GLfloat *params)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) pname); + + // Get Return Value + int size = get_glGetFloatv_params_size(pname); + safe_read((void *) params, sizeof (float) * size); + + // Release Proxy + end_proxy_call(); +#else + GLenum pname = (GLenum) read_int(); + int size = get_glGetFloatv_params_size(pname); + GLfloat params[size]; + // Run + glGetFloatv(pname, params); + // Return Value + safe_write((void *) params, sizeof (float) * size); +#endif +} + +CALL(49, glBindTexture, void, (GLenum target, GLuint texture)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) target); + write_int((uint32_t) texture); + + // Release Proxy + end_proxy_call(); +#else + GLenum target = (GLenum) read_int(); + GLuint texture = (GLuint) read_int(); + // Run + glBindTexture(target, texture); +#endif +} + +CALL(50, glTranslatef, void, (GLfloat x, GLfloat y, GLfloat z)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(x); + write_float(y); + write_float(z); + + // Release Proxy + end_proxy_call(); +#else + GLfloat x = read_float(); + GLfloat y = read_float(); + GLfloat z = read_float(); + // Run + glTranslatef(x, y, z); +#endif +} + +CALL(51, glShadeModel, void, (GLenum mode)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) mode); + + // Release Proxy + end_proxy_call(); +#else + GLenum mode = (GLenum) read_int(); + // Run + glShadeModel(mode); +#endif +} + +CALL(52, glOrthof, void, (GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(left); + write_float(right); + write_float(bottom); + write_float(top); + write_float(near); + write_float(far); + + // Release Proxy + end_proxy_call(); +#else + GLfloat left = read_float(); + GLfloat right = read_float(); + GLfloat bottom = read_float(); + GLfloat top = read_float(); + GLfloat near = read_float(); + GLfloat far = read_float(); + // Run + glOrthof(left, right, bottom, top, near, far); +#endif +} + +CALL(53, glDisable, void, (GLenum cap)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) cap); + + // Release Proxy + end_proxy_call(); +#else + GLenum cap = (GLenum) read_int(); + // Run + glDisable(cap); +#endif +} + +CALL(54, glCullFace, void, (GLenum mode)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) mode); + + // Release Proxy + end_proxy_call(); +#else + GLenum mode = (GLenum) read_int(); + // Run + glCullFace(mode); +#endif +} + +CALL(55, glRotatef, void, (GLfloat angle, GLfloat x, GLfloat y, GLfloat z)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(angle); + write_float(x); + write_float(y); + write_float(z); + + // Release Proxy + end_proxy_call(); +#else + GLfloat angle = read_float(); + GLfloat x = read_float(); + GLfloat y = read_float(); + GLfloat z = read_float(); + // Run + glRotatef(angle, x, y, z); +#endif +} + +CALL(56, glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) x); + write_int((uint32_t) y); + write_int((uint32_t) width); + write_int((uint32_t) height); + + // Release Proxy + end_proxy_call(); +#else + GLint x = (GLint) read_int(); + GLint y = (GLint) read_int(); + GLsizei width = (GLsizei) read_int(); + GLsizei height = (GLsizei) read_int(); + // Run + glViewport(x, y, width, height); +#endif +} + +CALL(57, glNormal3f, void, (GLfloat nx, GLfloat ny, GLfloat nz)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_float(nx); + write_float(ny); + write_float(nz); + + // Release Proxy + end_proxy_call(); +#else + GLfloat nx = read_float(); + GLfloat ny = read_float(); + GLfloat nz = read_float(); + // Run + glNormal3f(nx, ny, nz); +#endif +} + +CALL(58, glIsEnabled, GLboolean, (GLenum cap)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) cap); + + // Get Return Value + GLboolean ret = (GLboolean) read_int(); + + // Release Proxy + end_proxy_call(); + + // Return + return ret; +#else + GLenum cap = (GLenum) read_int(); + // Run + GLboolean ret = glIsEnabled(cap); + // Return Value + write_int((uint32_t) ret); +#endif +} diff --git a/media-layer/proxy/src/client/client.cpp b/media-layer/proxy/src/client/client.cpp new file mode 100644 index 0000000..bae94a8 --- /dev/null +++ b/media-layer/proxy/src/client/client.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + +#include "../common/common.h" + +// Store Handlers +__attribute__((const)) static std::vector &get_handlers() { + static std::vector handlers; + return handlers; +} +void _add_handler(unsigned char unique_id, proxy_handler_t handler) { + if (get_handlers().size() > unique_id && get_handlers()[unique_id] != NULL) { + PROXY_ERR("Duplicate ID: %i", (int) unique_id); + } + if (get_handlers().size() <= unique_id) { + get_handlers().resize(unique_id + 1); + } + get_handlers()[unique_id] = handler; +} + +// Store Parent PID +static int parent_is_alive = 1; +static void sigusr1_handler(__attribute__((unused)) int sig) { + // Mark Parent As Dead + parent_is_alive = 0; +} +// Check State Of Proxy And Exit If Invalid +void _check_proxy_state() { + // Check Server State + if (!parent_is_alive) { + PROXY_ERR("%s", "Server Terminated"); + } +} + +// Main +int main(int argc, char *argv[]) { + // Ignore SIGINT, Send Signal To Parent + signal(SIGINT, SIG_IGN); + + // Send Signal On Parent Death To Interrupt Connection Read/Write And Exit + prctl(PR_SET_PDEATHSIG, SIGUSR1); + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = &sigusr1_handler; + if (sigaction(SIGUSR1, &sa, NULL) == -1) { + PROXY_ERR("Unable To Install Signal Handler: %s", strerror(errno)); + } + + // Get Connection + if (argc != 3) { + PROXY_ERR("%s", "Invalid Arguments"); + } + char *read_str = argv[1]; + char *write_str = argv[2]; + set_connection(atoi(read_str), atoi(write_str)); + PROXY_INFO("%s", "Connected"); + + // Send Connection Message + write_string((char *) CONNECTED_MSG); + + // Loop + int running = is_connection_open(); + while (running) { + unsigned char unique_id = read_byte(); + if (get_handlers().size() > unique_id && get_handlers()[unique_id] != NULL) { + // Run Method + get_handlers()[unique_id](); + // Check If Connection Is Still Open + if (!is_connection_open()) { + // Exit + running = 0; + } + } else { + PROXY_ERR("Invalid Method ID: %i", (int) unique_id); + } + } + + // Exit + PROXY_INFO("%s", "Stopped"); + return 0; +} diff --git a/media-layer/proxy/src/client/client.h b/media-layer/proxy/src/client/client.h new file mode 100644 index 0000000..181518c --- /dev/null +++ b/media-layer/proxy/src/client/client.h @@ -0,0 +1,13 @@ +#pragma once + +#define PROXY_LOG_TAG "(Media Layer Proxy Client) " + +typedef void (*proxy_handler_t)(); +__attribute__((visibility("internal"))) void _add_handler(unsigned char id, proxy_handler_t handler); + +#define CALL(unique_id, name, return_type, args) \ + static void _run_##name (); \ + __attribute__((constructor)) static void _init_##name() { \ + _add_handler(unique_id, _run_##name); \ + } \ + static void _run_##name () diff --git a/media-layer/proxy/src/common/common.c b/media-layer/proxy/src/common/common.c new file mode 100644 index 0000000..fb30ef1 --- /dev/null +++ b/media-layer/proxy/src/common/common.c @@ -0,0 +1,142 @@ +#include +#include +#include + +#include "common.h" + +// Safely Send/Receive Data From The Connection +#define CHECK_CONNECTION() \ + { \ + _check_proxy_state(); \ + if (!is_connection_open()) { \ + PROXY_ERR("%s", "Attempting To Access Closed Connection"); \ + } else { \ + _check_proxy_state(); \ + } \ + } +void safe_read(void *buf, size_t len) { + if (buf == NULL) { + PROXY_ERR("%s", "Attempting To Read Into NULL Buffer"); + } + size_t to_read = len; + while (to_read > 0) { + CHECK_CONNECTION(); + ssize_t x = read(get_connection_read(), buf + (len - to_read), to_read); + if (x == -1 && errno != EINTR) { + PROXY_ERR("Failed Reading Data To Connection: %s", strerror(errno)); + } + to_read -= x; + } +} +// Buffer Writes +void safe_write(void *buf, size_t len) { + if (buf == NULL) { + PROXY_ERR("%s", "Attempting To Send NULL Data"); + } + size_t to_write = len; + while (to_write > 0) { + CHECK_CONNECTION(); + ssize_t x = write(get_connection_write(), buf + (len - to_write), to_write); + if (x == -1 && errno != EINTR) { + PROXY_ERR("Failed Writing Data To Connection: %s", strerror(errno)); + } + to_write -= x; + } +} + +// Read/Write 32-Bit Integers +uint32_t read_int() { + uint32_t ret = 0; + safe_read((void *) &ret, sizeof (ret)); + return ret; +} +void write_int(uint32_t x) { + safe_write((void *) &x, sizeof (x)); +} + +// Read/Write Floats +float read_float() { + float ret = 0; + safe_read((void *) &ret, sizeof (ret)); + return ret; +} +void write_float(float x) { + safe_write((void *) &x, sizeof (x)); +} + +// Read/Write Bytes +unsigned char read_byte() { + unsigned char ret = 0; + safe_read((void *) &ret, sizeof (ret)); + return ret; +} +void write_byte(unsigned char x) { + safe_write((void *) &x, sizeof (x)); +} + +// Read/Write Strings +char *read_string() { + // Check NULL + unsigned char is_null = read_byte(); + if (is_null) { + return NULL; + } + // Allocate String + unsigned char length = read_byte(); + char *str = malloc((size_t) length + 1); + // Read String + safe_read((void *) str, length); + // Add Terminator + str[length] = '\0'; + // Return String + return strdup(str); +} +#define MAX_STRING_SIZE 256 +void write_string(char *str) { + unsigned char is_null = str == NULL; + write_byte(is_null); + if (!is_null) { + int length = strlen(str); + if (length > MAX_STRING_SIZE) { + PROXY_ERR("Unable To Write String To Connection: Larger Than %i Bytes", MAX_STRING_SIZE); + } + write_byte((unsigned char) length); + safe_write((void *) str, length); + } +} + +// Close Connection +void close_connection() { + int state_changed = 0; + if (get_connection_read() != -1) { + close(get_connection_read()); + state_changed = 1; + } + if (get_connection_write() != -1) { + close(get_connection_write()); + state_changed = 1; + } + set_connection(-1, -1); + if (state_changed) { + PROXY_INFO("%s", "Connection Closed"); + } +} +// Check If Connection Is Open +int is_connection_open() { + return get_connection_read() != -1 && get_connection_write() != -1; +} +// Pipe +static int _read = -1; +static int _write = -1; +// Set Pipe +void set_connection(int read, int write) { + _read = read; + _write = write; +} +// Get Pipe +int get_connection_read() { + return _read; +} +int get_connection_write() { + return _write; +} diff --git a/media-layer/proxy/src/common/common.h b/media-layer/proxy/src/common/common.h new file mode 100644 index 0000000..64bfd6c --- /dev/null +++ b/media-layer/proxy/src/common/common.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if __BYTE_ORDER != __LITTLE_ENDIAN +#error "Only Little Endian Is Supported" +#endif + +#if defined(MEDIA_LAYER_PROXY_SERVER) +#include "../server/server.h" +#elif defined(MEDIA_LAYER_PROXY_CLIENT) +#include "../client/client.h" +#else +#error "Invalid Configuration" +#endif + +#define CONNECTED_MSG "Connected" + +#define PROXY_INFO(format, ...) INFO(PROXY_LOG_TAG format, __VA_ARGS__); +#define PROXY_ERR(format, ...) { close_connection(); ERR(PROXY_LOG_TAG format, __VA_ARGS__); } + +// Safely Send/Receive Data From The Connection +__attribute__((visibility("internal"))) void safe_read(void *buf, size_t len); +__attribute__((visibility("internal"))) void safe_write(void *buf, size_t len); + +// Read/Write 32-Bit Integers +__attribute__((visibility("internal"))) uint32_t read_int(); +__attribute__((visibility("internal"))) void write_int(uint32_t x); + +// Read/Write Bytes +__attribute__((visibility("internal"))) unsigned char read_byte(); +__attribute__((visibility("internal"))) void write_byte(unsigned char x); + +// Read/Write Floats +__attribute__((visibility("internal"))) float read_float(); +__attribute__((visibility("internal"))) void write_float(float x); + +// Read/Write Strings +__attribute__((visibility("internal"))) char *read_string(); // Remember To free() +__attribute__((visibility("internal"))) void write_string(char *str); + +// Manipulate Connection +__attribute__((visibility("internal"))) void set_connection(int read, int write); +__attribute__((visibility("internal"))) int get_connection_read(); +__attribute__((visibility("internal"))) int get_connection_write(); +__attribute__((visibility("internal"))) void close_connection(); +__attribute__((visibility("internal"))) int is_connection_open(); + +// Check State Of Proxy And Exit If Invalid +__attribute__((visibility("internal"))) void _check_proxy_state(); + +#ifdef __cplusplus +} +#endif diff --git a/media-layer/proxy/src/media-layer-core.c b/media-layer/proxy/src/media-layer-core.c new file mode 100644 index 0000000..5aa5080 --- /dev/null +++ b/media-layer/proxy/src/media-layer-core.c @@ -0,0 +1,351 @@ +#include + +#include + +#include +#include +#include + +#include "common/common.h" + +// Read/Write SDL Events +static void write_SDL_Event(SDL_Event event) { + // Write EVent Type + write_int(event.type); + // Write Event Details + switch (event.type) { + // Focus Event + case SDL_ACTIVEEVENT: { + write_int(event.active.gain); + write_int(event.active.state); + break; + } + // Key Press Events + case SDL_KEYDOWN: + case SDL_KEYUP: { + write_int(event.key.state); + write_int(event.key.keysym.scancode); + write_int(event.key.keysym.sym); + write_int(event.key.keysym.mod); + write_int(event.key.keysym.unicode); + break; + } + // Mouse Motion Event + case SDL_MOUSEMOTION: { + write_int(event.motion.state); + write_int(event.motion.x); + write_int(event.motion.y); + write_int(event.motion.xrel); + write_int(event.motion.yrel); + break; + } + // Mouse Press Events + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + write_int(event.button.button); + write_int(event.button.state); + write_int(event.button.x); + write_int(event.button.y); + break; + } + // User-Specified Event (Repurposed As Unicode Character Event) + case SDL_USEREVENT: { + write_int(event.user.code); + break; + } + } +} +static SDL_Event read_SDL_Event() { + // Create Event + SDL_Event event; + event.type = read_int(); + // Read Event Details + switch (event.type) { + // Focus Event + case SDL_ACTIVEEVENT: { + event.active.gain = read_int(); + event.active.state = read_int(); + break; + } + // Key Press Events + case SDL_KEYDOWN: + case SDL_KEYUP: { + event.key.state = read_int(); + event.key.keysym.scancode = read_int(); + event.key.keysym.sym = read_int(); + event.key.keysym.mod = read_int(); + event.key.keysym.unicode = read_int(); + break; + } + // Mouse Motion Event + case SDL_MOUSEMOTION: { + event.motion.state = read_int(); + event.motion.x = read_int(); + event.motion.y = read_int(); + event.motion.xrel = read_int(); + event.motion.yrel = read_int(); + break; + } + // Mouse Press Events + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + event.button.button = read_int(); + event.button.state = read_int(); + event.button.x = read_int(); + event.button.y = read_int(); + break; + } + // Quit Event + case SDL_QUIT: { + break; + } + // User-Specified Event (Repurposed As Unicode Character Event) + case SDL_USEREVENT: { + event.user.code = read_int(); + break; + } + // Unsupported Event + default: { + INFO("Unsupported SDL Event: %u", event.type); + } + } + // Return +#pragma GCC diagnostic push +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + return event; +#pragma GCC diagnostic pop +} + +// SDL Functions + +CALL(0, SDL_Init, int, (uint32_t flags)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int(flags); + + // Get Return Value + int32_t ret = (int32_t) read_int(); + + // Release Proxy + end_proxy_call(); + + // Return + return ret; +#else + uint32_t flags = read_int(); + // Run + int ret = SDL_Init(flags); + // Return Values + write_int((uint32_t) ret); +#endif +} + +CALL(1, SDL_PollEvent, int, (SDL_Event *event)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // No Arguments + + // Get Return Value + int32_t ret = (int32_t) read_int(); + if (ret) { + *event = read_SDL_Event(); + } + + // Release Proxy + end_proxy_call(); + + // Return Value + return ret; +#else + SDL_Event event; + // Run + int ret = (int32_t) SDL_PollEvent(&event); + // Return Values + write_int(ret); + if (ret) { + write_SDL_Event(event); + } +#endif +} + +CALL(2, SDL_PushEvent, int, (SDL_Event *event)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_SDL_Event(*event); + + // Get Return Value + int32_t ret = (int32_t) read_int(); + + // Release Proxy + end_proxy_call(); + + // Return Value + return ret; +#else + SDL_Event event = read_SDL_Event(); + // Run + int ret = SDL_PushEvent(&event); + // Return Value + write_int((uint32_t) ret); +#endif +} + +CALL(3, SDL_WM_SetCaption, void, (const char *title, const char *icon)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_string((char *) title); + write_string((char *) icon); + + // Release Proxy + end_proxy_call(); +#else + char *title = read_string(); + char *icon = read_string(); + // Run + SDL_WM_SetCaption(title, icon); + // Free + free(title); + free(icon); +#endif +} + +CALL(4, media_toggle_fullscreen, void, ()) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + // Release Proxy + end_proxy_call(); +#else + // Run + media_toggle_fullscreen(); +#endif +} + +CALL(5, SDL_WM_GrabInput, SDL_GrabMode, (SDL_GrabMode mode)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) mode); + + // Get Return Value + SDL_GrabMode ret = (SDL_GrabMode) read_int(); + + // Release Proxy + end_proxy_call(); + + // Return Value + return ret; +#else + SDL_GrabMode mode = (SDL_GrabMode) read_int(); + // Run + SDL_GrabMode ret = SDL_WM_GrabInput(mode); + // Return Value + write_int((uint32_t) ret); +#endif +} + +CALL(6,SDL_ShowCursor, int, (int32_t toggle)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Arguments + write_int((uint32_t) toggle); + + // Get Return Value + int32_t ret = (int32_t) read_int(); + + // Release Proxy + end_proxy_call(); + + // Return Value + return ret; +#else + int mode = (int) read_int(); + // Run + int ret = SDL_ShowCursor(mode); + // Return Value + write_int((uint32_t) ret); +#endif +} + +CALL(7, media_take_screenshot, void, ()) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + // Release Proxy + end_proxy_call(); +#else + // Run + media_take_screenshot(); +#endif +} + +CALL(8, media_swap_buffers, void, ()) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + // Release Proxy + end_proxy_call(); +#else + // Run + media_swap_buffers(); +#endif +} + +// This Method May Be Called In A Situation Where The Proxy Is Disconnected +CALL(9, media_cleanup, void, ()) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Check Connection + if (is_connection_open()) { + // Lock Proxy + start_proxy_call(); + // Close The Connection + close_connection(); + // Release Proxy + end_proxy_call(); + } +#else + // Close The Connection + close_connection(); + // Run + media_cleanup(); +#endif +} + +CALL(10, media_get_framebuffer_size, void, (int *width, int *height)) { +#if defined(MEDIA_LAYER_PROXY_SERVER) + // Lock Proxy + start_proxy_call(); + + // Get Return Values + *width = (int) read_int(); + *height = (int) read_int(); + + // Release Proxy + end_proxy_call(); +#else + int width; + int height; + // Run + media_get_framebuffer_size(&width, &height); + // Return Values + write_int((uint32_t) width); + write_int((uint32_t) height); +#endif +} diff --git a/media-layer/proxy/src/server/server.cpp b/media-layer/proxy/src/server/server.cpp new file mode 100644 index 0000000..625093f --- /dev/null +++ b/media-layer/proxy/src/server/server.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../common/common.h" + +// Track Client State +static int _client_is_alive = 0; +static int _client_status = 0; +static void update_client_state(int is_alive, int status) { + _client_is_alive = is_alive; + _client_status = status; +} +// Check State Of Proxy And Exit If Invalid +void _check_proxy_state() { + // Check Client State + if (!_client_is_alive) { + if (WIFEXITED(_client_status)) { + PROXY_ERR("Client Terminated: Exit Code: %i", WEXITSTATUS(_client_status)); + } else if (WIFSIGNALED(_client_status)) { + PROXY_ERR("Client Terminated: Signal: %i%s", WTERMSIG(_client_status), WCOREDUMP(_client_status) ? " (Core Dumped)" : ""); + } else { + PROXY_ERR("%s", "Client Terminated"); + } + } +} + +// Start Proxy Client +static pid_t _client_pid; +static void sigchld_handler(__attribute__((unused)) int sig) { + // Track + int status; + + // Reap + int saved_errno = errno; + // Only waitpid() Proxy Client, Other Sub-Processes Are Handled By pclose() + if (waitpid(_client_pid, &status, WNOHANG) == _client_pid) { + // Handle Client Death + update_client_state(0, status); + } + errno = saved_errno; +} +static void start_media_layer_proxy_client(int read, int write) { + // Reap Children + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = &sigchld_handler; + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + PROXY_ERR("Unable To Install Signal Handler: %s", strerror(errno)); + } + + // Fork And Start + pid_t ret = fork(); + if (ret == -1) { + PROXY_ERR("Unable To Launch Client: %s", strerror(errno)); + } else if (ret == 0) { + // Child Process + + // Prepare Environment + RESET_ENVIRONMENTAL_VARIABLE("LD_LIBRARY_PATH"); + RESET_ENVIRONMENTAL_VARIABLE("LD_PRELOAD"); + + // Prepare Arguments + char *read_str = NULL; + safe_asprintf(&read_str, "%i", read); + char *write_str = NULL; + safe_asprintf(&write_str, "%i", write); + char *argv[] = {NULL /* Updated By safe_execvpe() */, read_str, write_str, NULL}; + + // Run + safe_execvpe_relative_to_binary("lib/media-layer-proxy-client", argv, environ); + } else { + // Parent Process + _client_pid = ret; + } + update_client_state(1, 0); +} + +// Maximize Pipe Buffer Size +static void maximize_pipe_fd_size(int fd) { + // Read Maximum Pipe Size + std::ifstream max_size_file("/proc/sys/fs/pipe-max-size"); + if (!max_size_file.good()) { + PROXY_ERR("%s", "Unable To Open Maximum Pipe Size File"); + } + // Read One Line + int max_size; + std::string line; + if (std::getline(max_size_file, line) && line.size() > 0) { + max_size = std::stoi(line); + } else { + PROXY_ERR("%s", "Unable To Read Maximum Pipe Size File"); + } + // Set Maximum Pipe Size + errno = 0; + if (fcntl(fd, F_SETPIPE_SZ, max_size) < max_size) { + PROXY_ERR("Unable To Set Maximum Pipe Size: %s", errno != 0 ? strerror(errno) : "Unknown Error"); + } +} +static void maximize_pipe_size(int pipe[2]) { + maximize_pipe_fd_size(pipe[0]); + maximize_pipe_fd_size(pipe[1]); +} + +// Start Server +__attribute__((constructor)) static void init_media_layer_proxy_server() { + PROXY_INFO("%s", "Starting..."); + + // Create Connection + int server_to_client_pipe[2]; + safe_pipe2(server_to_client_pipe, 0); + maximize_pipe_size(server_to_client_pipe); + int client_to_server_pipe[2]; + safe_pipe2(client_to_server_pipe, 0); + maximize_pipe_size(client_to_server_pipe); + // Set Connection + set_connection(client_to_server_pipe[0], server_to_client_pipe[1]); + + // Start Client + start_media_layer_proxy_client(server_to_client_pipe[0], client_to_server_pipe[1]); + + // Wait For Connection Message + char *str = read_string(); + if (strcmp(str, CONNECTED_MSG) == 0) { + PROXY_INFO("%s", "Connected"); + } else { + PROXY_ERR("%s", "Unable To Connect"); + } + // Free + free(str); +} + +// Assign Unique ID To Function +static std::unordered_map &get_unique_ids() { + static std::unordered_map unique_ids; + return unique_ids; +} +void _assign_unique_id(const char *name, unsigned char id) { + get_unique_ids()[name] = id; +} +__attribute__((const)) static unsigned char get_unique_id(const char *name) { + return get_unique_ids()[name]; // Assume ID Exists +} + +// The Proxy Is Single-Threaded +static pthread_mutex_t proxy_mutex = PTHREAD_MUTEX_INITIALIZER; +void _start_proxy_call(const char *call_name) { + // Lock Proxy + pthread_mutex_lock(&proxy_mutex); + write_byte(get_unique_id(call_name)); +} +void end_proxy_call() { + // Release Proxy + pthread_mutex_unlock(&proxy_mutex); +} diff --git a/media-layer/proxy/src/server/server.h b/media-layer/proxy/src/server/server.h new file mode 100644 index 0000000..788cabf --- /dev/null +++ b/media-layer/proxy/src/server/server.h @@ -0,0 +1,17 @@ +#pragma once + +#define PROXY_LOG_TAG "(Media Layer Proxy Server) " + +// Assign Unique ID To Function +__attribute__((visibility("internal"))) void _assign_unique_id(const char *name, unsigned char id); + +// Must Call After Every Call +__attribute__((visibility("internal"))) void _start_proxy_call(const char *name); +#define start_proxy_call() _start_proxy_call(__func__) +__attribute__((visibility("internal"))) void end_proxy_call(); + +#define CALL(unique_id, name, return_type, args) \ + __attribute__((constructor)) static void _init_##name() { \ + _assign_unique_id(#name, unique_id); \ + } \ + return_type name args diff --git a/media-layer/stubs/CMakeLists.txt b/media-layer/stubs/CMakeLists.txt new file mode 100644 index 0000000..e62dbf2 --- /dev/null +++ b/media-layer/stubs/CMakeLists.txt @@ -0,0 +1,32 @@ +project(media-layer-stubs) + +# Add GLES Stubs For Linking +add_library(GLESv1_CM SHARED src/GLESv1_CM.c) +target_link_libraries(GLESv1_CM media-layer-headers) +set_target_properties(GLESv1_CM PROPERTIES SOVERSION "1") + +if(BUILD_ARM_COMPONENTS) + # Stub RPI-Specific Graphics + add_library(bcm_host SHARED src/bcm_host.c) + # Install + install(TARGETS bcm_host DESTINATION "${MCPI_LIB_DIR}") + + # Stub EGL + add_library(EGL SHARED src/EGL.c) + target_link_libraries(EGL reborn-headers media-layer-headers) + # Install + install(TARGETS EGL DESTINATION "${MCPI_FALLBACK_LIB_DIR}") # Place At The End Of LD_LIBRARY_PATH + + # Install GLESv1_CM Stubs In Server Mode + if(MCPI_SERVER_MODE) + install(TARGETS GLESv1_CM DESTINATION "${MCPI_LIB_DIR}") + endif() + + # Add NOP GLESv2 That Dpends On Actual GLESv1_CM (This Cannot Be A Symlink Because The Location Of GLESv1_CM Is Dynamic) + add_library(GLESv2 SHARED src/nop.c) + target_link_libraries(GLESv2 GLESv1_CM) + # Force Link + target_link_options(GLESv2 PRIVATE "-Wl,--no-as-needed") + # Install + install(TARGETS GLESv2 DESTINATION "${MCPI_LIB_DIR}") +endif() diff --git a/launcher/src/stubs/EGL.c b/media-layer/stubs/src/EGL.c similarity index 65% rename from launcher/src/stubs/EGL.c rename to media-layer/stubs/src/EGL.c index a27852d..512a0ba 100644 --- a/launcher/src/stubs/EGL.c +++ b/media-layer/stubs/src/EGL.c @@ -1,37 +1,53 @@ +/* + * This is only loaded when no other EGL library is present on the system to silence linker errors. + * + * All EGL calls are manually patched out in mods/src/compat/egl.c. + * Unlike with bcm_host, EGL can't just be always stubbed out with LD_PRELOAD, because in some situations GLFW has it as a dependency. + * + * So normally, MCPI just loads the real EGL and never uses it (because MCPI's EGL calls were patched out). + * However, since the real EGL is still loaded, GLFW can use it if it needs to. + * + * This stub library is just in case the system has no EGL library present. + */ + #include +#include + +#define IMPOSSIBLE() ERR("(%s:%i) This Should Never Be Called", __FILE__, __LINE__) + // EGL Is Replaced With GLFW EGLDisplay eglGetDisplay(__attribute__((unused)) NativeDisplayType native_display) { - return 0; + IMPOSSIBLE(); } EGLBoolean eglInitialize(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLint *major, __attribute__((unused)) EGLint *minor) { - return EGL_TRUE; + IMPOSSIBLE(); } EGLBoolean eglChooseConfig(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLint const *attrib_list, __attribute__((unused)) EGLConfig *configs, __attribute__((unused)) EGLint config_size, __attribute__((unused)) EGLint *num_config) { - return EGL_TRUE; + IMPOSSIBLE(); } EGLBoolean eglBindAPI(__attribute__((unused)) EGLenum api) { - return EGL_TRUE; + IMPOSSIBLE(); } EGLContext eglCreateContext(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLConfig config, __attribute__((unused)) EGLContext share_context, __attribute__((unused)) EGLint const *attrib_list) { - return 0; + IMPOSSIBLE(); } EGLSurface eglCreateWindowSurface(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLConfig config, __attribute__((unused)) NativeWindowType native_window, __attribute__((unused)) EGLint const *attrib_list) { - return 0; + IMPOSSIBLE(); } EGLBoolean eglMakeCurrent(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface draw, __attribute__((unused)) EGLSurface read, __attribute__((unused)) EGLContext context) { - return EGL_TRUE; + IMPOSSIBLE(); } EGLBoolean eglDestroySurface(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface) { - return EGL_TRUE; + IMPOSSIBLE(); } EGLBoolean eglDestroyContext(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLContext context) { - return EGL_TRUE; + IMPOSSIBLE(); } EGLBoolean eglTerminate(__attribute__((unused)) EGLDisplay display) { - return EGL_TRUE; + IMPOSSIBLE(); } EGLBoolean eglSwapBuffers(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface) { - return EGL_TRUE; -} \ No newline at end of file + IMPOSSIBLE(); +} diff --git a/media-layer/stubs/src/GLESv1_CM.c b/media-layer/stubs/src/GLESv1_CM.c new file mode 100644 index 0000000..dcf0876 --- /dev/null +++ b/media-layer/stubs/src/GLESv1_CM.c @@ -0,0 +1,108 @@ +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +void glFogfv(GLenum pname, const GLfloat *params) { +} +void glVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) { +} +void glLineWidth(GLfloat width) { +} +void glBlendFunc(GLenum sfactor, GLenum dfactor) { +} +void glDrawArrays(GLenum mode, GLint first, GLsizei count) { +} +void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { +} +void glClear(GLbitfield mask) { +} +void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage) { +} +void glFogx(GLenum pname, GLfixed param) { +} +void glFogf(GLenum pname, GLfloat param) { +} +void glMatrixMode(GLenum mode) { +} +void glColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) { +} +void glScissor(GLint x, GLint y, GLsizei width, GLsizei height) { +} +void glTexParameteri(GLenum target, GLenum pname, GLint param) { +} +void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) { +} +void glEnable(GLenum cap) { +} +void glEnableClientState(GLenum array) { +} +void glPolygonOffset(GLfloat factor, GLfloat units) { +} +void glDisableClientState(GLenum array) { +} +void glDepthRangef(GLclampf near, GLclampf far) { +} +void glDepthFunc(GLenum func) { +} +void glBindBuffer(GLenum target, GLuint buffer) { +} +void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) { +} +void glPopMatrix() { +} +void glLoadIdentity() { +} +void glScalef(GLfloat x, GLfloat y, GLfloat z) { +} +void glPushMatrix() { +} +void glDepthMask(GLboolean flag) { +} +void glHint(GLenum target, GLenum mode) { +} +void glMultMatrixf(const GLfloat *m) { +} +void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) { +} +void glDeleteBuffers(GLsizei n, const GLuint *buffers) { +} +void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { +} +void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) { +} +void glGenTextures(GLsizei n, GLuint *textures) { +} +void glDeleteTextures(GLsizei n, const GLuint *textures) { +} +void glAlphaFunc(GLenum func, GLclampf ref) { +} +void glGetFloatv(GLenum pname, GLfloat *params) { +} +void glBindTexture(GLenum target, GLuint texture) { +} +void glTranslatef(GLfloat x, GLfloat y, GLfloat z) { +} +void glShadeModel(GLenum mode) { +} +void glOrthof(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far) { +} +void glDisable(GLenum cap) { +} +void glCullFace(GLenum mode) { +} +void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) { +} +void glViewport(GLint x, GLint y, GLsizei width, GLsizei height) { +} +void glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) { +} +GLboolean glIsEnabled(GLenum cap) { + return GL_FALSE; +} +void glGetIntegerv(GLenum pname, GLint *data) { +} +void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *data) { +} + +#pragma GCC diagnostic pop diff --git a/launcher/src/stubs/bcm_host.c b/media-layer/stubs/src/bcm_host.c similarity index 100% rename from launcher/src/stubs/bcm_host.c rename to media-layer/stubs/src/bcm_host.c diff --git a/launcher/src/stubs/GLESv2.c b/media-layer/stubs/src/nop.c similarity index 100% rename from launcher/src/stubs/GLESv2.c rename to media-layer/stubs/src/nop.c diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index 97ac219..11ee053 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -1,44 +1,33 @@ -cmake_minimum_required(VERSION 3.13.0) - project(mods) ## Setup -add_compile_options(-Wall -Wextra -Werror) -add_link_options(-Wl,--no-undefined) # Disable C++11 String ABI add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) -# Add libreborn -add_subdirectory(../libreborn libreborn) - -# Find GLFW -find_package(glfw3 3.3 REQUIRED) - ## Mods -add_library(compat SHARED src/compat/compat.c) -target_link_libraries(compat feature input screenshot SDL GLESv1_CM X11 dl glfw Xfixes) +add_library(compat SHARED src/compat/compat.c src/compat/egl.c) +target_link_libraries(compat feature input media-layer-core dl) add_library(readdir SHARED src/readdir/readdir.c) add_library(feature SHARED src/feature/feature.c) target_link_libraries(feature reborn) -add_library(server SHARED src/server/server.cpp src/server/server_properties.cpp) -target_link_libraries(server reborn feature dl SDL pthread) - -add_library(screenshot SHARED src/screenshot/screenshot.c) -target_link_libraries(screenshot reborn GLESv1_CM freeimage) +if(MCPI_SERVER_MODE) + add_library(server SHARED src/server/server.cpp src/server/server_properties.cpp) + target_link_libraries(server reborn feature home compat dl media-layer-core pthread) +endif() add_library(camera SHARED src/camera/camera.cpp) -target_link_libraries(camera reborn screenshot) +target_link_libraries(camera reborn media-layer-core) add_library(game_mode SHARED src/game_mode/game_mode.c src/game_mode/game_mode.cpp) target_link_libraries(game_mode reborn) add_library(input SHARED src/input/input.c src/input/input.cpp) -target_link_libraries(input reborn feature SDL chat) +target_link_libraries(input reborn feature media-layer-core chat) add_library(misc SHARED src/misc/misc.c src/misc/misc.cpp) target_link_libraries(misc reborn feature util) @@ -47,7 +36,7 @@ add_library(options SHARED src/options/options.c) target_link_libraries(options reborn feature) add_library(override SHARED src/override/override.c) -target_link_libraries(override reborn dl) +target_link_libraries(override reborn dl home) add_library(textures SHARED src/textures/textures.cpp) target_link_libraries(textures reborn feature GLESv1_CM) @@ -55,11 +44,20 @@ target_link_libraries(textures reborn feature GLESv1_CM) add_library(chat SHARED src/chat/chat.cpp src/chat/ui.c) target_link_libraries(chat reborn pthread) +add_library(home SHARED src/home/home.c) +target_link_libraries(home reborn) + add_library(test SHARED src/test/test.c) -target_link_libraries(test reborn) +target_link_libraries(test reborn home) add_library(init SHARED src/init/init.c) -target_link_libraries(init compat server game_mode camera input misc options textures chat test) +target_link_libraries(init compat game_mode camera input misc options textures chat home test) +if(MCPI_SERVER_MODE) + target_link_libraries(init server) +endif() ## Install Mods -install(TARGETS init compat readdir feature screenshot override server game_mode camera input misc options textures chat test DESTINATION /mods) \ No newline at end of file +install(TARGETS init compat readdir feature override game_mode camera input misc options textures chat home test DESTINATION "${MCPI_INSTALL_DIR}/mods") +if(MCPI_SERVER_MODE) + install(TARGETS server DESTINATION "${MCPI_INSTALL_DIR}/mods") +endif() diff --git a/mods/README.md b/mods/README.md new file mode 100644 index 0000000..ce0fd2f --- /dev/null +++ b/mods/README.md @@ -0,0 +1,7 @@ +# Mods +Theses are the various mods included in MCPI-Reborn. + +These mods work by patching MCPI's code. + +## Compilation & Linking +Everything is compiled to ARM and directly links to MCPI. diff --git a/mods/src/camera/README.md b/mods/src/camera/README.md new file mode 100644 index 0000000..af39ab3 --- /dev/null +++ b/mods/src/camera/README.md @@ -0,0 +1,2 @@ +# ``camera`` Mod +This mod fixes the Camera's renderer so that it is no longer invisible. diff --git a/mods/src/camera/camera.cpp b/mods/src/camera/camera.cpp index 6246b53..b75b3df 100644 --- a/mods/src/camera/camera.cpp +++ b/mods/src/camera/camera.cpp @@ -1,13 +1,13 @@ #include -#include "../screenshot/screenshot.h" #include "../init/init.h" +#include #include // Take Screenshot Using TripodCamera -static void AppPlatform_linux_saveScreenshot_injection(__attribute__((unused)) unsigned char *app_platform, __attribute__((unused)) std::string const& param1, __attribute__((unused)) std::string const& param_2) { - take_screenshot(); +static void AppPlatform_linux_saveScreenshot_injection(__attribute__((unused)) unsigned char *app_platform, __attribute__((unused)) std::string const& path, __attribute__((unused)) int32_t width, __attribute__((unused)) int32_t height) { + media_take_screenshot(); } // Enable TripodCameraRenderer @@ -38,4 +38,4 @@ void init_camera() { overwrite_calls((void *) EntityRenderDispatcher, (void *) EntityRenderDispatcher_injection); // Display Smoke From TripodCamera Higher overwrite_call((void *) 0x87dc4, (void *) TripodCamera_tick_Level_addParticle_call_injection); -} \ No newline at end of file +} diff --git a/mods/src/chat/README.md b/mods/src/chat/README.md new file mode 100644 index 0000000..ca486cd --- /dev/null +++ b/mods/src/chat/README.md @@ -0,0 +1,2 @@ +# ``chat`` Mod +This mod implements a chat system. diff --git a/mods/src/chat/chat.cpp b/mods/src/chat/chat.cpp index 1c6a63d..d90672c 100644 --- a/mods/src/chat/chat.cpp +++ b/mods/src/chat/chat.cpp @@ -28,8 +28,7 @@ static void send_api_command(unsigned char *minecraft, char *str) { // Send API Chat Command static void send_api_chat_command(unsigned char *minecraft, char *str) { char *command = NULL; - asprintf(&command, "chat.post(%s)\n", str); - ALLOC_CHECK(command); + safe_asprintf(&command, "chat.post(%s)\n", str); send_api_command(minecraft, command); free(command); } @@ -37,8 +36,7 @@ static void send_api_chat_command(unsigned char *minecraft, char *str) { // Send Message To Players static void send_message(unsigned char *server_side_network_handler, char *username, char *message) { char *full_message = NULL; - asprintf(&full_message, "<%s> %s", username, message); - ALLOC_CHECK(full_message); + safe_asprintf(&full_message, "<%s> %s", username, message); sanitize_string(&full_message, MAX_CHAT_MESSAGE_LENGTH, 0); (*ServerSideNetworkHandler_displayGameMessage)(server_side_network_handler, std::string(full_message)); free(full_message); @@ -78,7 +76,7 @@ static void ServerSideNetworkHandler_handle_ChatPacket_injection(unsigned char * static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; static std::vector queue; // Add To Queue -void chat_queue_message(char *message) { +void _chat_queue_message(char *message) { // Lock pthread_mutex_lock(&queue_mutex); // Add diff --git a/mods/src/chat/chat.h b/mods/src/chat/chat.h index caa5b3d..806c361 100644 --- a/mods/src/chat/chat.h +++ b/mods/src/chat/chat.h @@ -6,10 +6,10 @@ extern "C" { void chat_open(); unsigned int chat_get_counter(); - -void chat_queue_message(char *message); void chat_send_messages(unsigned char *minecraft); +__attribute__((visibility("internal"))) void _chat_queue_message(char *message); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/mods/src/chat/ui.c b/mods/src/chat/ui.c index 1f01a41..09c5745 100644 --- a/mods/src/chat/ui.c +++ b/mods/src/chat/ui.c @@ -1,5 +1,3 @@ -#define _GNU_SOURCE - #include #include #include @@ -9,39 +7,11 @@ #include "chat.h" -#define CHAT_WINDOW_TCL \ - "set message \"\"\n" \ - "proc submit {} {\n" \ - "global message\n" \ - "puts \"$message\"\n" \ - "exit\n" \ - "}\n" \ - \ - "wm resizable . false false\n" \ - "wm title . \"Chat\"\n" \ - "wm attributes . -topmost true -type {dialog}\n" \ - \ - "ttk::label .label -text \"Enter Chat Message:\"\n" \ - \ - "ttk::entry .entry -textvariable message\n" \ - "focus .entry\n" \ - "bind .entry submit\n" \ - \ - "ttk::frame .button\n" \ - "ttk::button .button.submit -text \"Submit\" -command submit\n" \ - "ttk::button .button.cancel -text \"Cancel\" -command exit\n" \ - \ - "grid .label -row 0 -padx 6 -pady 6\n" \ - "grid .entry -row 1 -padx 6\n" \ - "grid .button -row 2 -padx 3 -pady 6\n" \ - "grid .button.cancel -row 0 -column 0 -padx 3\n" \ - "grid .button.submit -row 0 -column 1 -padx 3\n" - // Run Command static char *run_command(char *command, int *return_code) { - // Don't Contaminate Child Process - unsetenv("LD_LIBRARY_PATH"); - unsetenv("LD_PRELOAD"); + // Prepare Environment + RESET_ENVIRONMENTAL_VARIABLE("LD_LIBRARY_PATH"); + RESET_ENVIRONMENTAL_VARIABLE("LD_PRELOAD"); // Start FILE *out = popen(command, "r"); @@ -53,8 +23,7 @@ static char *run_command(char *command, int *return_code) { char *output = NULL; int c; while ((c = fgetc(out)) != EOF) { - asprintf(&output, "%s%c", output == NULL ? "" : output, (char) c); - ALLOC_CHECK(output); + string_append(&output, "%c", (char) c); } // Return @@ -71,11 +40,9 @@ unsigned int chat_get_counter() { // Chat Thread static void *chat_thread(__attribute__((unused)) void *nop) { - // Prepare - setenv("CHAT_WINDOW_TCL", CHAT_WINDOW_TCL, 1); // Open int return_code; - char *output = run_command("echo \"${CHAT_WINDOW_TCL}\" | wish -name \"Minecraft - Pi edition\"", &return_code); + char *output = run_command("zenity --title \"Chat\" --class \"Minecraft - Pi edition\" --entry --text \"Enter Chat Message:\"", &return_code); // Handle Message if (output != NULL) { // Check Return Code @@ -89,7 +56,7 @@ static void *chat_thread(__attribute__((unused)) void *nop) { // Don't Allow Empty Strings if (length > 0) { // Submit - chat_queue_message(output); + _chat_queue_message(output); } } // Free Output @@ -112,4 +79,4 @@ void chat_open() { // Start Thread pthread_t thread; pthread_create(&thread, NULL, chat_thread, NULL); -} \ No newline at end of file +} diff --git a/mods/src/compat/README.md b/mods/src/compat/README.md new file mode 100644 index 0000000..82cae72 --- /dev/null +++ b/mods/src/compat/README.md @@ -0,0 +1,2 @@ +# ``compat`` Mod +This utility mod sends keyboard input to other mods. It also patches out all EGL calls. diff --git a/mods/src/compat/compat.c b/mods/src/compat/compat.c index d47d024..c2fa483 100644 --- a/mods/src/compat/compat.c +++ b/mods/src/compat/compat.c @@ -1,263 +1,29 @@ -#define _GNU_SOURCE - #include - -#define GLFW_EXPOSE_NATIVE_X11 -#define GLFW_INCLUDE_ES1 -#include -#include - -#include +#include #include -#include -#include -#include -#include +#include #include #include "../feature/feature.h" #include "../input/input.h" -#include "../screenshot/screenshot.h" #include "../chat/chat.h" #include "../init/init.h" - -static GLFWwindow *glfw_window; - -static int is_server = 0; - -// Handle GLFW Error -static void glfw_error(__attribute__((unused)) int error, const char *description) { - ERR("GLFW Error: %s", description); -} - -// Convert GLFW Key To SDL Key -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; - // Hotbar - 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; - // 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; - // 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; - } -} - -// Pass Key Presses To SDL -static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, __attribute__((unused)) int mods) { - SDL_Event event; - int up = action == GLFW_RELEASE; - event.type = up ? SDL_KEYUP : SDL_KEYDOWN; - event.key.state = up ? SDL_RELEASED : SDL_PRESSED; - event.key.keysym.scancode = scancode; - event.key.keysym.mod = KMOD_NONE; - event.key.keysym.sym = glfw_key_to_sdl_key(key); - SDL_PushEvent(&event); - if (key == GLFW_KEY_BACKSPACE && !up) { - input_key_press((char) '\b'); - } -} - -// Pass Text To Minecraft -static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int codepoint) { - input_key_press((char) codepoint); -} - -static double last_mouse_x = 0; -static double last_mouse_y = 0; -static int ignore_relative_mouse = 1; - -// Pass Mouse Movement To SDL -static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) { - SDL_Event event; - event.type = SDL_MOUSEMOTION; - event.motion.x = xpos; - event.motion.y = ypos; - event.motion.xrel = !ignore_relative_mouse ? (xpos - last_mouse_x) : 0; - event.motion.yrel = !ignore_relative_mouse ? (ypos - last_mouse_y) : 0; - ignore_relative_mouse = 0; - last_mouse_x = xpos; - last_mouse_y = ypos; - SDL_PushEvent(&event); -} - -// Create And Push SDL Mouse Click Event -static void click(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(__attribute__((unused)) GLFWwindow *window, int button, int action, __attribute__((unused)) int mods) { - 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(sdl_button, up); -} - -// Pass Mouse Scroll To SDL -static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) { - if (yoffset != 0) { - int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; - click(sdl_button, 0); - click(sdl_button, 1); - } -} - -// Default Window Size -#define DEFAULT_WIDTH 840 -#define DEFAULT_HEIGHT 480 - -// Init GLFW -HOOK(SDL_WM_SetCaption, void, (const char *title, __attribute__((unused)) const char *icon)) { - // Don't Enable GLFW In Server Mode - if (!is_server) { - glfwSetErrorCallback(glfw_error); - - if (!glfwInit()) { - ERR("%s", "Unable To Initialize GLFW"); - } - - // Create OpenGL ES 1.1 Context - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - // Extra Settings - glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE); - - glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL); - if (!glfw_window) { - ERR("%s", "Unable To Create GLFW Window"); - } - - // Don't Process Events In Server Mode - 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); - - glfwMakeContextCurrent(glfw_window); - } -} - -HOOK(eglSwapBuffers, EGLBoolean, (__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface)) { - if (!is_server) { - // Don't Swap Buffers In A Context-Less Window - glfwSwapBuffers(glfw_window); - } - return EGL_TRUE; -} - -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 -static void 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); - Screen *screen = DefaultScreenOfDisplay(glfwGetX11Display()); - - glfwSetWindowMonitor(glfw_window, glfwGetPrimaryMonitor(), 0, 0, WidthOfScreen(screen), HeightOfScreen(screen), GLFW_DONT_CARE); - } - is_fullscreen = !is_fullscreen; -} +#include "compat.h" // Intercept SDL Events HOOK(SDL_PollEvent, int, (SDL_Event *event)) { - // Process GLFW Events - glfwPollEvents(); - - // Close Window (Ignore In Server Mode) - if (!is_server && glfwWindowShouldClose(glfw_window)) { + // In Server Mode, Exit Requests Are Handled In src/server/server.cpp +#ifndef MCPI_SERVER_MODE + // Check If Exit Is Requested + if (compat_check_exit_requested()) { + // Send SDL_QUIT SDL_Event event; event.type = SDL_QUIT; SDL_PushEvent(&event); - glfwSetWindowShouldClose(glfw_window, GLFW_FALSE); } +#endif // #ifndef MCPI_SERVER_MODE // Poll Events ensure_SDL_PollEvent(); @@ -267,35 +33,49 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) { if (ret == 1 && event != NULL) { int handled = 0; - if (event->type == SDL_KEYDOWN) { - if (event->key.keysym.sym == SDLK_F11) { - toggle_fullscreen(); - handled = 1; - } else if (event->key.keysym.sym == SDLK_F2) { - take_screenshot(); - handled = 1; - } else if (event->key.keysym.sym == SDLK_F1) { - input_hide_gui(); - handled = 1; - } else if (event->key.keysym.sym == SDLK_F5) { - input_third_person(); - handled = 1; - } else if (event->key.keysym.sym == SDLK_t) { - // Only When In-Game With No Other Chat Windows Open - if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON && chat_get_counter() == 0) { - // Release Mouse - input_set_mouse_grab_state(1); - // Open Chat - chat_open(); + switch (event->type) { + case SDL_KEYDOWN: { + // Handle Key Presses + if (event->key.keysym.sym == SDLK_F11) { + media_toggle_fullscreen(); + handled = 1; + } else if (event->key.keysym.sym == SDLK_F2) { + media_take_screenshot(); + handled = 1; + } else if (event->key.keysym.sym == SDLK_F1) { + input_hide_gui(); + handled = 1; + } else if (event->key.keysym.sym == SDLK_F5) { + input_third_person(); + handled = 1; + } else if (event->key.keysym.sym == SDLK_t) { + // Only When In-Game With No Other Chat Windows Open + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON && chat_get_counter() == 0) { + // Release Mouse + input_set_mouse_grab_state(1); + // Open Chat + chat_open(); + } + // Mark Handled + handled = 1; } - // Mark Handled - handled = 1; + break; } - } else if (event->type == SDL_MOUSEBUTTONDOWN || event->type == SDL_MOUSEBUTTONUP) { - if (event->button.button == SDL_BUTTON_RIGHT) { - input_set_is_right_click(event->button.state != SDL_RELEASED); - } else if (event->button.button == SDL_BUTTON_LEFT) { - input_set_is_left_click(event->button.state != SDL_RELEASED); + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + // Track Right-Click State + if (event->button.button == SDL_BUTTON_RIGHT) { + input_set_is_right_click(event->button.state != SDL_RELEASED); + } else if (event->button.button == SDL_BUTTON_LEFT) { + input_set_is_left_click(event->button.state != SDL_RELEASED); + } + break; + } + case SDL_USEREVENT: { + // SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events + input_key_press((char) event->user.code); + handled = 1; + break; } } @@ -308,124 +88,28 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) { return ret; } -// Terminate GLFW -HOOK(SDL_Quit, void, ()) { - ensure_SDL_Quit(); - (*real_SDL_Quit)(); - - // GLFW Is Disabled In Server Mode - if (!is_server) { - glfwDestroyWindow(glfw_window); - glfwTerminate(); - } +// Exit Handler +static void exit_handler(__attribute__((unused)) int data) { + // Request Exit + compat_request_exit(); } - -static SDL_GrabMode fake_grab_mode = SDL_GRAB_OFF; - -// Fix SDL Cursor Visibility/Grabbing -HOOK(SDL_WM_GrabInput, SDL_GrabMode, (SDL_GrabMode mode)) { - if (is_server) { - // Don't Grab Input In Server Mode - if (mode != SDL_GRAB_QUERY) { - fake_grab_mode = mode; - } - return fake_grab_mode; - } else { - if (mode != SDL_GRAB_QUERY && mode != SDL_WM_GrabInput(SDL_GRAB_QUERY)) { - glfwSetInputMode(glfw_window, GLFW_CURSOR, mode == SDL_GRAB_OFF ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); - glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, mode == SDL_GRAB_OFF ? GLFW_FALSE : GLFW_TRUE); - - // GLFW Cursor Hiding is Broken - Display *x11_display = glfwGetX11Display(); - Window x11_window = glfwGetX11Window(glfw_window); - if (mode == SDL_GRAB_OFF) { - XFixesShowCursor(x11_display, x11_window); - } else { - XFixesHideCursor(x11_display, x11_window); - } - XFlush(x11_display); - - // Reset Last Mouse Position - ignore_relative_mouse = 1; - } - return mode == SDL_GRAB_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_GRAB_OFF : SDL_GRAB_ON) : mode; - } -} - -// Stub SDL Cursor Visibility -HOOK(SDL_ShowCursor, int, (int toggle)) { - if (is_server) { - return toggle == SDL_QUERY ? (fake_grab_mode == SDL_GRAB_OFF ? SDL_ENABLE : SDL_DISABLE) : toggle; - } else { - return toggle == SDL_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_ENABLE : SDL_DISABLE) : toggle; - } -} - -// SDL Stub -HOOK(SDL_SetVideoMode, SDL_Surface *, (__attribute__((unused)) int width, __attribute__((unused)) int height, __attribute__((unused)) int bpp, __attribute__((unused)) uint32_t flags)) { - // Return Value Is Only Used For A NULL-Check - return (SDL_Surface *) 1; -} - -HOOK(XTranslateCoordinates, int, (Display *display, Window src_w, Window dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, Window *child_return)) { - if (!is_server) { - // Use X11 - ensure_XTranslateCoordinates(); - return (*real_XTranslateCoordinates)(display, src_w, dest_w, src_x, src_y, dest_x_return, dest_y_return, child_return); - } else { - // No X11 - *dest_x_return = src_x; - *dest_y_return = src_y; - return 1; - } -} - -HOOK(XGetWindowAttributes, int, (Display *display, Window w, XWindowAttributes *window_attributes_return)) { - if (!is_server) { - // Use X11 - ensure_XGetWindowAttributes(); - return (*real_XGetWindowAttributes)(display, w, window_attributes_return); - } else { - // No X11 - XWindowAttributes attributes; - attributes.x = 0; - attributes.y = 0; - attributes.width = DEFAULT_WIDTH; - attributes.height = DEFAULT_HEIGHT; - *window_attributes_return = attributes; - return 1; - } -} - -static void x11_nop() { - // NOP -} -HOOK(SDL_GetWMInfo, int, (SDL_SysWMinfo *info)) { - // Return Fake Lock Functions In Server Mode Since SDL X11 Is Disabled - SDL_SysWMinfo ret; - ret.info.x11.lock_func = x11_nop; - ret.info.x11.unlock_func = x11_nop; - ret.info.x11.display = glfwGetX11Display(); - ret.info.x11.window = glfwGetX11Window(glfw_window); - ret.info.x11.wmwindow = ret.info.x11.window; - *info = ret; - return 1; -} - -#include - -// Use VirGL void init_compat() { - int mode = feature_get_mode(); - if (mode != 1) { - // Force Software Rendering When Not In Native Mode - setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); + // Install Exit Handler + signal(SIGINT, exit_handler); + signal(SIGTERM, exit_handler); +} + +// Store Exit Requests +static int exit_requested = 0; +int compat_check_exit_requested() { + if (exit_requested) { + exit_requested = 0; + return 1; + } else { + return 0; } - if (mode == 0) { - // Use VirGL When In VirGL Mode - setenv("GALLIUM_DRIVER", "virpipe", 1); - } - is_server = mode == 2; - // Video is Handled By GLFW Not SDL - setenv("SDL_VIDEODRIVER", "dummy", 1); -} \ No newline at end of file +} +void compat_request_exit() { + // Request + exit_requested = 1; +} diff --git a/mods/src/compat/compat.h b/mods/src/compat/compat.h new file mode 100644 index 0000000..0d1150d --- /dev/null +++ b/mods/src/compat/compat.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +int compat_check_exit_requested(); +void compat_request_exit(); + +#ifdef __cplusplus +} +#endif diff --git a/mods/src/compat/egl.c b/mods/src/compat/egl.c new file mode 100644 index 0000000..ae77b25 --- /dev/null +++ b/mods/src/compat/egl.c @@ -0,0 +1,39 @@ +#include + +#include +#include + +// Functions That Have Their Return Values Used +static EGLSurface eglCreateWindowSurface_overwrite(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLConfig config, __attribute__((unused)) NativeWindowType native_window, __attribute__((unused)) EGLint const *attrib_list) { + return 0; +} +static EGLDisplay eglGetDisplay_overwrite(__attribute__((unused)) NativeDisplayType native_display) { + return 0; +} +static EGLContext eglCreateContext_overwrite(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLConfig config, __attribute__((unused)) EGLContext share_context, __attribute__((unused)) EGLint const *attrib_list) { + return 0; +} +// Call media_swap_buffers() +static EGLBoolean eglSwapBuffers_overwrite(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface) { + media_swap_buffers(); + return EGL_TRUE; +} + +// Patch EGL Calls +__attribute__((constructor)) static void patch_egl_calls() { + // Disable EGL Calls + unsigned char nop_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop" + patch((void *) 0x1250c, nop_patch); // eglTerminate + patch((void *) 0x12580, nop_patch); // eglBindAPI + overwrite_call((void *) 0x12638, (void *) eglCreateWindowSurface_overwrite); // eglCreateWindowSurface + patch((void *) 0x12578, nop_patch); // eglChooseConfig + patch((void *) 0x1255c, nop_patch); // eglInitialize + patch((void *) 0x124f0, nop_patch); // eglMakeCurrent #1 + patch((void *) 0x12654, nop_patch); // eglMakeCurrent #2 + overwrite_call((void *) 0x124dc, (void *) eglSwapBuffers_overwrite); // eglSwapBuffers #1 + overwrite_call((void *) 0x14b6c, (void *) eglSwapBuffers_overwrite); // eglSwapBuffers #2 + overwrite_call((void *) 0x1254c, (void *) eglGetDisplay_overwrite); // eglGetDisplay + patch((void *) 0x124fc, nop_patch); // eglDestroySurface #1 + patch((void *) 0x12504, nop_patch); // eglDestroySurface #2 + overwrite_call((void *) 0x12594, (void *) eglCreateContext_overwrite); // eglCreateContext +} diff --git a/mods/src/feature/README.md b/mods/src/feature/README.md new file mode 100644 index 0000000..f653377 --- /dev/null +++ b/mods/src/feature/README.md @@ -0,0 +1,2 @@ +# ``feature`` Mod +This utility mod handles feature flags. diff --git a/mods/src/feature/feature.c b/mods/src/feature/feature.c index 1f7f78c..b977360 100644 --- a/mods/src/feature/feature.c +++ b/mods/src/feature/feature.c @@ -7,7 +7,7 @@ // Check For Feature int feature_has(const char *name) { - char *env = getenv("MCPI_FEATURES"); + char *env = getenv("MCPI_FEATURE_FLAGS"); char *features = strdup(env != NULL ? env : ""); char *tok = strtok(features, "|"); int ret = 0; @@ -19,24 +19,8 @@ int feature_has(const char *name) { tok = strtok(NULL, "|"); } free(features); - if (feature_get_mode() != 2) { - INFO("Feature: %s: %s", name, ret ? "Enabled" : "Disabled"); - } +#ifndef MCPI_SERVER_MODE + INFO("Feature: %s: %s", name, ret ? "Enabled" : "Disabled"); +#endif return ret; } - -// Get Graphics Mode -int feature_get_mode() { - char *mode = getenv("MCPI_MODE"); - if (mode == NULL) { - ERR("%s", "MCPI Mode Not Specified"); - } else if (strcmp("virgl", mode) == 0) { - return 0; - } else if (strcmp("native", mode) == 0) { - return 1; - } else if (strcmp("server", mode) == 0) { - return 2; - } else { - ERR("Inavlid MCPI_MODE: %s", mode); - } -} diff --git a/mods/src/feature/feature.h b/mods/src/feature/feature.h index a945fbd..740addc 100644 --- a/mods/src/feature/feature.h +++ b/mods/src/feature/feature.h @@ -5,7 +5,6 @@ extern "C" { #endif int feature_has(const char *name); -int feature_get_mode(); #ifdef __cplusplus } diff --git a/mods/src/game_mode/README.md b/mods/src/game_mode/README.md new file mode 100644 index 0000000..c3268bf --- /dev/null +++ b/mods/src/game_mode/README.md @@ -0,0 +1,2 @@ +# ``game_mode`` Mod +This mod implements Survival Mode and dynamic game-mode switching. diff --git a/mods/src/game_mode/game_mode.c b/mods/src/game_mode/game_mode.c index 47dc4c8..d29f738 100644 --- a/mods/src/game_mode/game_mode.c +++ b/mods/src/game_mode/game_mode.c @@ -53,5 +53,5 @@ void init_game_mode() { patch((void *) 0x6dc70, server_patch); // Init C++ - init_game_mode_cpp(); + _init_game_mode_cpp(); } \ No newline at end of file diff --git a/mods/src/game_mode/game_mode.cpp b/mods/src/game_mode/game_mode.cpp index 9d25c3d..bf67ef2 100644 --- a/mods/src/game_mode/game_mode.cpp +++ b/mods/src/game_mode/game_mode.cpp @@ -48,7 +48,7 @@ static void Touch_SelectWorldScreen_tick_injection(unsigned char *screen) { } } -void init_game_mode_cpp() { +void _init_game_mode_cpp() { // Hijack Create World Button patch_address(SelectWorldScreen_tick_vtable_addr, (void *) SelectWorldScreen_tick_injection); patch_address(Touch_SelectWorldScreen_tick_vtable_addr, (void *) Touch_SelectWorldScreen_tick_injection); diff --git a/mods/src/game_mode/game_mode.h b/mods/src/game_mode/game_mode.h index 0e89d2b..4538ec3 100644 --- a/mods/src/game_mode/game_mode.h +++ b/mods/src/game_mode/game_mode.h @@ -4,7 +4,7 @@ extern "C" { #endif -void init_game_mode_cpp(); +__attribute__((visibility("internal"))) void _init_game_mode_cpp(); #ifdef __cplusplus } diff --git a/mods/src/home/README.md b/mods/src/home/README.md new file mode 100644 index 0000000..cd96f65 --- /dev/null +++ b/mods/src/home/README.md @@ -0,0 +1,4 @@ +# ``home`` Mod +This utility mod handles changing the location where world data is stored. This is so it doesn't conflict with Minecraft: Java Edition. + +Normally, it changes it to ``~/.minecraft-pi``, but in server mode it changes it to the launch directory. diff --git a/mods/src/home/home.c b/mods/src/home/home.c new file mode 100644 index 0000000..d79c6e4 --- /dev/null +++ b/mods/src/home/home.c @@ -0,0 +1,44 @@ +#include +#include + +#include "home.h" +#include "../init/init.h" + +// Minecraft Pi User Data Root +#ifndef MCPI_SERVER_MODE +// Store Game Data In "~/.minecraft-pi" Instead Of "~/.minecraft" To Avoid Conflicts +#define NEW_PATH "/.minecraft-pi" +#else +// Store Game Data In Launch Directory +#define NEW_PATH "" + +static char *launch_directory = NULL; +__attribute__((constructor)) static void init_launch_directory() { + launch_directory = getcwd(NULL, 0); +} +char *home_get_launch_directory() { + return launch_directory; +} + +HOOK(getenv, char *, (const char *name)) { + if (strcmp(name, "HOME") == 0) { + return launch_directory; + } else { + ensure_getenv(); + return (*real_getenv)(name); + } +} +#endif + +// Get MCPI Home Directory +char *home_get() { + char *dir = NULL; + safe_asprintf(&dir, "%s/" NEW_PATH, getenv("HOME")); + return dir; +} + +// Init +void init_home() { + // Store Data In ~/.minecraft-pi Instead Of ~/.minecraft + patch_address((void *) default_path, (void *) NEW_PATH); +} diff --git a/mods/src/home/home.h b/mods/src/home/home.h new file mode 100644 index 0000000..44d6ce8 --- /dev/null +++ b/mods/src/home/home.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef MCPI_SERVER_MODE +char *home_get_launch_directory(); +#endif + +char *home_get(); // Remember To free() + +#ifdef __cplusplus +} +#endif diff --git a/mods/src/init/README.md b/mods/src/init/README.md new file mode 100644 index 0000000..15eb4d6 --- /dev/null +++ b/mods/src/init/README.md @@ -0,0 +1,2 @@ +# ``init`` Mod +This utility mod handles initializing the other mods in the correct order. diff --git a/mods/src/init/init.c b/mods/src/init/init.c index c717c1c..03c459f 100644 --- a/mods/src/init/init.c +++ b/mods/src/init/init.c @@ -3,7 +3,9 @@ __attribute__((constructor)) static void init() { run_tests(); init_compat(); +#ifdef MCPI_SERVER_MODE init_server(); +#endif init_game_mode(); init_input(); init_misc(); @@ -11,4 +13,5 @@ __attribute__((constructor)) static void init() { init_options(); init_textures(); init_chat(); -} \ No newline at end of file + init_home(); +} diff --git a/mods/src/init/init.h b/mods/src/init/init.h index b478a79..3dd76e1 100644 --- a/mods/src/init/init.h +++ b/mods/src/init/init.h @@ -6,7 +6,9 @@ extern "C" { void run_tests(); void init_compat(); +#ifdef MCPI_SERVER_MODE void init_server(); +#endif void init_game_mode(); void init_input(); void init_misc(); @@ -14,7 +16,8 @@ void init_camera(); void init_options(); void init_textures(); void init_chat(); +void init_home(); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/mods/src/input/README.md b/mods/src/input/README.md new file mode 100644 index 0000000..bcd7023 --- /dev/null +++ b/mods/src/input/README.md @@ -0,0 +1,9 @@ +# ``input`` Mod +This mod fixes various input-related bugs, including: +- Bows being broken. +- The cursor interacting with the hotbar while the it is locked. +- Being unable to attack mobs. +- Being unable to specify sign text. + +It also adds various features, including: +- Hide GUI and third person toggle keys. diff --git a/mods/src/input/input.c b/mods/src/input/input.c index 37e740c..fffb9bd 100644 --- a/mods/src/input/input.c +++ b/mods/src/input/input.c @@ -79,7 +79,7 @@ static void Minecraft_tickInput_injection(unsigned char *minecraft) { mouse_grab_state = 0; } -#include +#include // Block UI Interaction When Mouse Is Locked static bool Gui_tickItemDrop_Minecraft_isCreativeMode_call_injection(unsigned char *minecraft) { @@ -149,5 +149,5 @@ void init_input() { } // Init C++ - init_input_cpp(); -} \ No newline at end of file + _init_input_cpp(); +} diff --git a/mods/src/input/input.cpp b/mods/src/input/input.cpp index 1e02d06..ed995f9 100644 --- a/mods/src/input/input.cpp +++ b/mods/src/input/input.cpp @@ -55,7 +55,7 @@ static void TextEditScreen_updateEvents_injection(unsigned char *screen) { input_clear_input(); } -void init_input_cpp() { +void _init_input_cpp() { if (feature_has("Fix Sign Placement")) { // Fix Signs patch_address(LocalPlayer_openTextEdit_vtable_addr, (void *) LocalPlayer_openTextEdit_injection); diff --git a/mods/src/input/input.h b/mods/src/input/input.h index 32f0773..0e2f3f4 100644 --- a/mods/src/input/input.h +++ b/mods/src/input/input.h @@ -15,7 +15,7 @@ void input_set_is_left_click(int val); void input_set_mouse_grab_state(int state); -void init_input_cpp(); +__attribute__((visibility("internal"))) void _init_input_cpp(); #ifdef __cplusplus } diff --git a/mods/src/misc/README.md b/mods/src/misc/README.md new file mode 100644 index 0000000..b0a7dc1 --- /dev/null +++ b/mods/src/misc/README.md @@ -0,0 +1,9 @@ +# ``misc`` Mod +This mod has several miscelaneous mods that are too small to be their own mod, including: +- Rendering text above the hotbar when an item is selected. +- Sanitizing player usernames for invalid characters. +- Removing the red background from unobtainable items in the inventory. +- Loading the bundled language file. +- Optionally expanding the Creative Inventory. +- Printing chat messages to the log. +- Adding death messages. diff --git a/mods/src/misc/misc.c b/mods/src/misc/misc.c index 4348784..a382802 100644 --- a/mods/src/misc/misc.c +++ b/mods/src/misc/misc.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -8,9 +9,6 @@ #include "misc.h" #include "../init/init.h" -// Minecraft Pi User Data Root -#define NEW_PATH "/.minecraft-pi/" - // Maximum Username Length #define MAX_USERNAME_LENGTH 16 @@ -67,9 +65,6 @@ static void LoginPacket_read_injection(unsigned char *packet, unsigned char *bit } void init_misc() { - // Store Data In ~/.minecraft-pi Instead Of ~/.minecraft - patch_address((void *) default_path, (void *) NEW_PATH); - if (feature_has("Remove Invalid Item Background")) { // Remove Invalid Item Background (A Red Background That Appears For Items That Are Not Included In The gui_blocks Atlas) unsigned char invalid_item_background_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop" @@ -84,11 +79,6 @@ void init_misc() { // Sanitize Username patch_address(LoginPacket_read_vtable_addr, (void *) LoginPacket_read_injection); - // Show FPS Monitor - if (feature_has("Show FPS Monitor")) { - setenv("GALLIUM_HUD", "simple,fps", 1); - } - // Init C++ - init_misc_cpp(); -} \ No newline at end of file + _init_misc_cpp(); +} diff --git a/mods/src/misc/misc.cpp b/mods/src/misc/misc.cpp index 39a491b..55fd212 100644 --- a/mods/src/misc/misc.cpp +++ b/mods/src/misc/misc.cpp @@ -149,7 +149,7 @@ static void LocalPlayer_actuallyHurt_injection(unsigned char *player, int32_t da Player_actuallyHurt_injection_helper(player, damage, true); } -void init_misc_cpp() { +void _init_misc_cpp() { // Implement AppPlatform::readAssetFile So Translations Work overwrite((void *) AppPlatform_readAssetFile, (void *) AppPlatform_readAssetFile_injection); diff --git a/mods/src/misc/misc.h b/mods/src/misc/misc.h index 7252f8e..58b2985 100644 --- a/mods/src/misc/misc.h +++ b/mods/src/misc/misc.h @@ -4,7 +4,11 @@ extern "C" { #endif -void init_misc_cpp(); +#ifdef MCPI_SERVER_MODE +char *misc_get_launch_directory(); +#endif + +__attribute__((visibility("internal"))) void _init_misc_cpp(); #ifdef __cplusplus } diff --git a/mods/src/options/README.md b/mods/src/options/README.md new file mode 100644 index 0000000..2ee55b3 --- /dev/null +++ b/mods/src/options/README.md @@ -0,0 +1,12 @@ +# ``options`` Mod +This mod allows various options to be configured, including: +- Mob Spawning +- The Render Distance +- The Username +- Touch GUI +- Peaceful Mode +- 3D Anaglyph +- Autojump +- Player Nametags +- Block Outlines +- Smooth Lighting diff --git a/mods/src/options/options.c b/mods/src/options/options.c index 2aabbf9..23cfa56 100644 --- a/mods/src/options/options.c +++ b/mods/src/options/options.c @@ -13,6 +13,7 @@ static uint32_t LevelData_getSpawnMobs_injection(__attribute__((unused)) unsigne return mob_spawning; } +#ifndef MCPI_SERVER_MODE // Get Custom Render Distance static int get_render_distance() { char *distance_str = getenv("MCPI_RENDER_DISTANCE"); @@ -31,6 +32,8 @@ static int get_render_distance() { ERR("Invalid Render Distance: %s", distance_str); } } +#endif // #ifndef MCPI_SERVER_MODE + // Get Custom Username static char *get_username() { char *username = getenv("MCPI_USERNAME"); @@ -71,8 +74,6 @@ static int32_t Minecraft_isTouchscreen_injection(__attribute__((unused)) unsigne } void init_options() { - int is_server = feature_get_mode() == 2; - int touch_gui = feature_has("Touch GUI"); if (touch_gui) { // Main UI @@ -93,21 +94,21 @@ void init_options() { // 3D Anaglyph anaglyph = feature_has("3D Anaglyph"); // Render Distance - if (!is_server) { - render_distance = get_render_distance(); - INFO("Setting Render Distance: %i", render_distance); - } else { - render_distance = 3; - } +#ifndef MCPI_SERVER_MODE + render_distance = get_render_distance(); + INFO("Setting Render Distance: %i", render_distance); +#else // #ifndef MCPI_SERVER_MODE + render_distance = 3; +#endif // #ifndef MCPI_SERVER_MODE // Set Options overwrite_calls((void *) Minecraft_init, Minecraft_init_injection); // Change Username const char *username = get_username(); - if (!is_server) { - INFO("Setting Username: %s", username); - } +#ifndef MCPI_SERVER_MODE + INFO("Setting Username: %s", username); +#endif // #ifndef MCPI_SERVER_MODE if (strcmp(*default_username, "StevePi") != 0) { ERR("%s", "Default Username Is Invalid"); } diff --git a/mods/src/override/README.md b/mods/src/override/README.md new file mode 100644 index 0000000..f4e23c3 --- /dev/null +++ b/mods/src/override/README.md @@ -0,0 +1,4 @@ +# ``override`` Mod +This mod allows built-in assets to be overriden by placing replacements in an override folder. + +``/data/images/terrain.png`` would be overriden by ``/overrides/images/terrain.png``. diff --git a/mods/src/override/override.c b/mods/src/override/override.c index b585020..c6fe43b 100644 --- a/mods/src/override/override.c +++ b/mods/src/override/override.c @@ -1,5 +1,3 @@ -#define _GNU_SOURCE - #include #include #include @@ -10,28 +8,31 @@ #include +#include "../home/home.h" + static int starts_with(const char *s, const char *t) { return strncmp(s, t, strlen(t)) == 0; } static char *get_override_path(const char *filename) { + // Get MCPI Home Path + char *home_path = home_get(); // Get Asset Override Path char *overrides = NULL; - asprintf(&overrides, "%s/.minecraft-pi/overrides", getenv("HOME")); - ALLOC_CHECK(overrides); - // Get data Path + safe_asprintf(&overrides, "%s/overrides", home_path); + // Free Home Path + free(home_path); + // Get Data Path char *data = NULL; char *cwd = getcwd(NULL, 0); - asprintf(&data, "%s/data", cwd); - ALLOC_CHECK(data); + safe_asprintf(&data, "%s/data", cwd); free(cwd); // Get Full Path char *new_path = NULL; char *full_path = realpath(filename, NULL); if (full_path != NULL) { if (starts_with(full_path, data)) { - asprintf(&new_path, "%s%s", overrides, &full_path[strlen(data)]); - ALLOC_CHECK(new_path); + safe_asprintf(&new_path, "%s%s", overrides, &full_path[strlen(data)]); if (access(new_path, F_OK) == -1) { free(new_path); new_path = NULL; @@ -70,4 +71,4 @@ HOOK(fopen64, FILE *, (const char *filename, const char *mode)) { } // Return File return file; -} \ No newline at end of file +} diff --git a/mods/src/readdir/README.md b/mods/src/readdir/README.md new file mode 100644 index 0000000..5207a4e --- /dev/null +++ b/mods/src/readdir/README.md @@ -0,0 +1,2 @@ +# ``readdir`` Mod +This mod fixes a small bug where the contents of directories cannot be read on a 64-bit filesystem. This notably broke the world selection screen. diff --git a/mods/src/readdir/readdir.c b/mods/src/readdir/readdir.c index 6052c03..2ecf41b 100644 --- a/mods/src/readdir/readdir.c +++ b/mods/src/readdir/readdir.c @@ -1,4 +1,3 @@ -#define _GNU_SOURCE #define __USE_LARGEFILE64 #include diff --git a/mods/src/server/README.md b/mods/src/server/README.md new file mode 100644 index 0000000..14c4a9e --- /dev/null +++ b/mods/src/server/README.md @@ -0,0 +1,2 @@ +# ``server`` Mod +This mod contains most of the code involved in converting MCPI into a server. diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index e6525a4..8546d31 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -1,8 +1,11 @@ +#ifndef MCPI_SERVER_MODE +#error "Server Code Requires Server Mode" +#endif + #include #include #include #include -#include #include #include @@ -10,18 +13,33 @@ #include -#include +#include #include -#include "server_internal.h" #include "server_properties.h" #include "../feature/feature.h" #include "../init/init.h" +#include "../home/home.h" +#include "../compat/compat.h" #include +// --only-generate: Ony Generate World And Then Exit +static bool only_generate = false; +__attribute__((constructor)) static void _init_only_generate(int argc, char *argv[]) { + // Iterate Arguments + for (int i = 1; i < argc; i++) { + // Check Argument + if (strcmp(argv[i], "--only-generate") == 0) { + // Enabled + only_generate = true; + break; + } + } +} + // Server Properties static ServerProperties &get_server_properties() { static ServerProperties properties; @@ -40,30 +58,8 @@ static ServerProperties &get_server_properties() { #define DEFAULT_MAX_PLAYERS "4" #define DEFAULT_WHITELIST "false" -// Read STDIN Thread -static volatile bool stdin_buffer_complete = false; -static volatile char *stdin_buffer = NULL; -static void *read_stdin_thread(__attribute__((unused)) void *data) { - while (1) { - if (!stdin_buffer_complete) { - int x = getchar(); - if (x != EOF) { - if (x == '\n') { - if (stdin_buffer == NULL) { - stdin_buffer = strdup(""); - } - stdin_buffer_complete = true; - } else { - asprintf((char **) &stdin_buffer, "%s%c", stdin_buffer == NULL ? "" : stdin_buffer, (char) x); - ALLOC_CHECK(stdin_buffer); - } - } - } - } -} - // Get World Name -std::string server_internal_get_world_name() { +static std::string get_world_name() { return get_server_properties().get_string("world-name", DEFAULT_WORLD_NAME); } @@ -71,19 +67,26 @@ std::string server_internal_get_world_name() { static void start_world(unsigned char *minecraft) { INFO("%s", "Starting Minecraft: Pi Edition Dedicated Server"); + // 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(NULL); settings.seed = seed; - std::string world_name = server_internal_get_world_name(); + // Select Level + std::string world_name = get_world_name(); (*Minecraft_selectLevel)(minecraft, world_name, world_name, settings); - int port = get_server_properties().get_int("port", DEFAULT_PORT); - (*Minecraft_hostMultiplayer)(minecraft, port); - INFO("Listening On: %i", port); + // Don't Open Port When Using --only-generate + if (!only_generate) { + // Open Port + int port = get_server_properties().get_int("port", DEFAULT_PORT); + (*Minecraft_hostMultiplayer)(minecraft, port); + INFO("Listening On: %i", port); + } + // Open ProgressScreen void *screen = ::operator new(PROGRESS_SCREEN_SIZE); ALLOC_CHECK(screen); screen = (*ProgressScreen)((unsigned char *) screen); @@ -141,39 +144,34 @@ static bool is_whitelist() { } // Get Path Of Blacklist (Or Whitelist) File static std::string get_blacklist_file() { - std::string file(getenv("HOME")); - file.append(is_whitelist() ? "/.minecraft-pi/whitelist.txt" : "/.minecraft-pi/blacklist.txt"); + std::string file(home_get_launch_directory()); + file.append(is_whitelist() ? "/whitelist.txt" : "/blacklist.txt"); return file; } -typedef void (*player_callback_t)(unsigned char *minecraft, std::string username, unsigned char *player); - // Get Vector Of Players In Level -std::vector server_internal_get_players(unsigned char *level) { +static std::vector get_players_in_level(unsigned char *level) { return *(std::vector *) (level + Level_players_property_offset); } // Get Player's Username -std::string server_internal_get_player_username(unsigned char *player) { +static std::string get_player_username(unsigned char *player) { return *(char **) (player + Player_username_property_offset); } // Get Level From Minecraft -unsigned char *server_internal_get_level(unsigned char *minecraft) { +static unsigned char *get_level(unsigned char *minecraft) { return *(unsigned char **) (minecraft + Minecraft_level_property_offset); } -// Get minecraft from ServerPlayer -unsigned char *server_internal_get_minecraft(unsigned char *player) { - return *(unsigned char **) (player + ServerPlayer_minecraft_property_offset); -} // Find Players With Username And Run Callback +typedef void (*player_callback_t)(unsigned char *minecraft, std::string username, unsigned char *player); static void find_players(unsigned char *minecraft, std::string target_username, player_callback_t callback, bool all_players) { - unsigned char *level = server_internal_get_level(minecraft); - std::vector players = server_internal_get_players(level); + unsigned char *level = get_level(minecraft); + std::vector players = get_players_in_level(level); bool found_player = false; for (std::size_t i = 0; i < players.size(); i++) { // Iterate Players unsigned char *player = players[i]; - std::string username = server_internal_get_player_username(player); + std::string username = get_player_username(player); if (all_players || username == target_username) { // Run Callback (*callback)(minecraft, username, player); @@ -185,6 +183,7 @@ static void find_players(unsigned char *minecraft, std::string target_username, } } +// Get RakNet Objects static RakNet_RakNetGUID get_rak_net_guid(unsigned char *player) { return *(RakNet_RakNetGUID *) (player + ServerPlayer_guid_property_offset); } @@ -240,6 +239,7 @@ static void list_callback(unsigned char *minecraft, std::string username, unsign INFO(" - %s (%s)", username.c_str(), get_player_ip(minecraft, player)); } +// Log When Game Is Saved static void Level_saveLevelData_injection(unsigned char *level) { // Print Log Message INFO("%s", "Saving Game"); @@ -248,16 +248,12 @@ static void Level_saveLevelData_injection(unsigned char *level) { (*Level_saveLevelData)(level); } -// Stop Server -static bool exit_requested = false; -static void exit_handler(__attribute__((unused)) int data) { - exit_requested = true; -} +// Handle Server Stop static void handle_server_stop(unsigned char *minecraft) { - if (exit_requested) { + if (compat_check_exit_requested()) { INFO("%s", "Stopping Server"); // Save And Exit - unsigned char *level = server_internal_get_level(minecraft); + unsigned char *level = get_level(minecraft); if (level != NULL) { Level_saveLevelData_injection(level); } @@ -266,8 +262,6 @@ static void handle_server_stop(unsigned char *minecraft) { SDL_Event event; event.type = SDL_QUIT; SDL_PushEvent(&event); - - exit_requested = false; } } @@ -276,9 +270,36 @@ static unsigned char *get_server_side_network_handler(unsigned char *minecraft) return *(unsigned char **) (minecraft + Minecraft_network_handler_property_offset); } +// Read STDIN Thread +static volatile bool stdin_buffer_complete = false; +static volatile char *stdin_buffer = NULL; +static void *read_stdin_thread(__attribute__((unused)) void *data) { + while (1) { + if (!stdin_buffer_complete) { + int x = getchar(); + if (x != EOF) { + if (x == '\n') { + if (stdin_buffer == NULL) { + stdin_buffer = strdup(""); + } + stdin_buffer_complete = true; + } else { + string_append((char **) &stdin_buffer, "%c", (char) x); + } + } + } + } +} +__attribute__((destructor)) static void _free_stdin_buffer() { + free((void *) stdin_buffer); + stdin_buffer = NULL; +} + // Handle Commands static void handle_commands(unsigned char *minecraft) { + // Check If Level Is Generated if ((*Minecraft_isLevelGenerated)(minecraft) && stdin_buffer_complete) { + // Command Ready; Run It if (stdin_buffer != NULL) { unsigned char *server_side_network_handler = get_server_side_network_handler(minecraft); if (server_side_network_handler != NULL) { @@ -309,7 +330,7 @@ static void handle_commands(unsigned char *minecraft) { find_players(minecraft, "", list_callback, true); } else if (data == stop_command) { // Stop Server - exit_handler(-1); + compat_request_exit(); } else if (data == help_command) { INFO("%s", "All Commands:"); if (!is_whitelist()) { @@ -325,6 +346,7 @@ static void handle_commands(unsigned char *minecraft) { } } + // Free free((void *) stdin_buffer); stdin_buffer = NULL; } @@ -341,6 +363,14 @@ static void Minecraft_update_injection(unsigned char *minecraft) { loaded = true; } + // Handle --only-generate + if (only_generate && (*Minecraft_isLevelGenerated)(minecraft)) { + // Request Exit + compat_request_exit(); + // Disable Special Behavior After Requesting Exit + only_generate = false; + } + // Print Progress Reports print_progress(minecraft); @@ -363,11 +393,13 @@ static bool RakNet_RakPeer_IsBanned_injection(__attribute__((unused)) unsigned c if (blacklist_file.good()) { std::string line; while (std::getline(blacklist_file, line)) { + // Check Line if (line.length() > 0) { if (line[0] == '#') { continue; } if (strcmp(line.c_str(), ip) == 0) { + // Is In File ret = true; break; } @@ -425,8 +457,8 @@ static unsigned char get_max_players() { static void server_init() { // Open Properties File - std::string file(getenv("HOME")); - file.append("/.minecraft-pi/server.properties"); + std::string file(home_get_launch_directory()); + file.append("/server.properties"); std::ifstream properties_file(file); @@ -487,17 +519,14 @@ static void server_init() { overwrite_calls((void *) Minecraft_update, (void *) Minecraft_update_injection); // Print Log On Game Save overwrite_calls((void *) Level_saveLevelData, (void *) Level_saveLevelData_injection); - // Exit handler - signal(SIGINT, exit_handler); - signal(SIGTERM, exit_handler); // Set Max Players unsigned char max_players_patch[4] = {get_max_players(), 0x30, 0xa0, 0xe3}; // "mov r3, #MAX_PLAYERS" patch((void *) 0x166d0, max_players_patch); // Custom Banned IP List overwrite((void *) RakNet_RakPeer_IsBanned, (void *) RakNet_RakPeer_IsBanned_injection); + // Show The MineCon Icon Next To MOTD In Server List if (get_server_properties().get_bool("show-minecon-badge", DEFAULT_SHOW_MINECON_BADGE)) { - // Show The MineCon Icon Next To MOTD In Server List unsigned char minecon_badge_patch[4] = {0x04, 0x1a, 0x9f, 0xe5}; // "ldr r1, [0x741f0]" patch((void *) 0x737e4, minecon_badge_patch); } @@ -507,10 +536,9 @@ static void server_init() { pthread_create(&read_stdin_thread_obj, NULL, read_stdin_thread, NULL); } +// Init Server void init_server() { - if (feature_get_mode() == 2) { - server_init(); - setenv("MCPI_FEATURES", get_features(), 1); - setenv("MCPI_USERNAME", get_motd().c_str(), 1); - } -} \ No newline at end of file + server_init(); + setenv("MCPI_FEATURE_FLAGS", get_features(), 1); + setenv("MCPI_USERNAME", get_motd().c_str(), 1); +} diff --git a/mods/src/server/server_internal.h b/mods/src/server/server_internal.h deleted file mode 100644 index 4ed49dd..0000000 --- a/mods/src/server/server_internal.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include - -std::string server_internal_get_world_name(); -unsigned char *server_internal_get_level(unsigned char *minecraft); -std::vector server_internal_get_players(unsigned char *level); -std::string server_internal_get_player_username(unsigned char *player); -unsigned char *server_internal_get_minecraft(unsigned char *player); \ No newline at end of file diff --git a/mods/src/test/README.md b/mods/src/test/README.md new file mode 100644 index 0000000..20c0c77 --- /dev/null +++ b/mods/src/test/README.md @@ -0,0 +1,2 @@ +# ``test`` Mod +This utility mod tests that the system is configured correctly before starting. diff --git a/mods/src/test/test.c b/mods/src/test/test.c index 04af386..ce4ed04 100644 --- a/mods/src/test/test.c +++ b/mods/src/test/test.c @@ -1,60 +1,23 @@ -#define _GNU_SOURCE - #include #include -#include #include -#include -#include #include +#include "../home/home.h" #include "../init/init.h" -// Types -typedef long long time64_t; -struct timespec64 { - time64_t tv_sec; - long tv_nsec; -}; -typedef long int time32_t; -struct timespec32 { - time32_t tv_sec; - long tv_nsec; -}; - void run_tests() { - // Test clock_gettime64 - { - struct timespec64 ts64; - long out = syscall(SYS_clock_gettime64, CLOCK_MONOTONIC, &ts64); - if (out != 0) { - if (errno == ENOSYS) { - // clock_gettime64 Unsupported, Testing clock_gettime - struct timespec32 ts32; - out = syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &ts32); - if (out != 0) { - // Failure - ERR("Unable To Run clock_gettime Syscall: %s", strerror(errno)); - } - } else { - // Failure - ERR("Unable To Run clock_gettime64 Syscall: %s", strerror(errno)); - } - } - } - // Test ~/.minecraft-pi Permissions { - char *path = NULL; - asprintf(&path, "%s/.minecraft-pi", getenv("HOME")); - ALLOC_CHECK(path); - int ret = access(path, R_OK | W_OK); + char *path = home_get(); + int exists = access(path, F_OK) == 0; + int can_write = exists ? access(path, R_OK | W_OK) == 0 : 1; free(path); - if (ret != 0) { + if (!can_write) { // Failure - ERR("%s", "Invalid ~/.minecraft-pi Permissions"); + ERR("%s", "Invalid Data Directory Permissions"); } } -} \ No newline at end of file +} diff --git a/mods/src/textures/README.md b/mods/src/textures/README.md new file mode 100644 index 0000000..8b9cce4 --- /dev/null +++ b/mods/src/textures/README.md @@ -0,0 +1,4 @@ +# ``textures`` Mod +This mod includes various features involving textures, including: +- Animated Water +- Disabling The ``gui_blocks`` Atlas diff --git a/scripts/build.sh b/scripts/build.sh index cb847d1..9229abf 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,5 +2,85 @@ set -e -docker build ${DOCKER_BUILD_OPTIONS} --tag thebrokenrail/minecraft-pi-reborn:client -f Dockerfile.client . -docker build ${DOCKER_BUILD_OPTIONS} --tag thebrokenrail/minecraft-pi-reborn:server -f Dockerfile.server . +# This Script Assumes An x86_64 Host +if [ "$(uname -m)" != "x86_64" ]; then + echo 'Invalid Build Architecture' + exit 1 +fi + +# Build For x86_64 +native_build() { + # Create Build Dir + rm -rf build/$1-x86_64 + mkdir -p build/$1-x86_64 + cd build/$1-x86_64 + + # Create Prefix + local prefix="$(cd ../../; pwd)/out/$1-x86_64" + rm -rf "${prefix}" + mkdir -p "${prefix}" + + # Prepare + local extra_arg='-DMCPI_USE_MEDIA_LAYER_PROXY=ON' + if [ "$1" = "server" ]; then + extra_arg='-DMCPI_SERVER_MODE=ON' + fi + + # Build ARM Components + mkdir arm + cd arm + cmake -DMCPI_BUILD_MODE=arm "${extra_arg}" ../../.. + make -j$(nproc) + make install DESTDIR="${prefix}" + cd ../ + + # Build Native Components + mkdir native + cd native + cmake -DMCPI_BUILD_MODE=native "${extra_arg}" ../../.. + make -j$(nproc) + make install DESTDIR="${prefix}" + cd ../ + + # Exit + cd ../../ +} + +# Build For ARM +arm_build() { + # Create Build Dir + rm -rf build/$1-arm + mkdir -p build/$1-arm + cd build/$1-arm + + # Create Prefix + local prefix="$(cd ../../; pwd)/out/$1-arm" + rm -rf "${prefix}" + mkdir -p "${prefix}" + + # Prepare + local server_mode='OFF' + if [ "$1" = "server" ]; then + server_mode='ON' + fi + + # Build All Components + cmake -DMCPI_BUILD_MODE=both -DMCPI_SERVER_MODE="${server_mode}" ../.. + make -j$(nproc) + make install DESTDIR="${prefix}" + + # Exit + cd ../../ +} + +# Clean Prefix +rm -rf out + +# Build +native_build client +native_build server +if [ ! -z "${ARM_PACKAGES_SUPPORTED}" ]; then + # Requires ARM Versions Of GLFW And FreeImage + arm_build client +fi +arm_build server diff --git a/scripts/ci/run.sh b/scripts/ci/run.sh new file mode 100755 index 0000000..99e0fff --- /dev/null +++ b/scripts/ci/run.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +# Install sudo +apt-get update +apt-get install -y sudo + +# Prepare +export ARM_PACKAGES_SUPPORTED=1 + +# Install Dependencies +echo '==== Installing Dependencies ====' +./scripts/install-dependencies.sh + +# Build +echo '==== Building ====' +./scripts/build.sh + +# Test +echo '==== Testing ====' +./scripts/test.sh + +# Package +echo '==== Packaging ====' +./scripts/package.sh diff --git a/scripts/ci/simulate.sh b/scripts/ci/simulate.sh new file mode 100755 index 0000000..a86c2e7 --- /dev/null +++ b/scripts/ci/simulate.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +# Run +docker run --rm -v "$(pwd):/data" debian:bullseye sh -c "cd /data; ./scripts/ci/run.sh" diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh new file mode 100755 index 0000000..f2c666f --- /dev/null +++ b/scripts/install-dependencies.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +set -e + +# This Script Assumes An x86_64 Host +if [ "$(uname -m)" != "x86_64" ]; then + echo 'Invalid Build Architecture' + exit 1 +fi + +# Add ARM Repository +if [ ! -z "${ARM_PACKAGES_SUPPORTED}" ]; then + sudo dpkg --add-architecture armhf +fi + +# Update APT +sudo apt-get update +sudo apt-get dist-upgrade -y + +# Install +sudo apt-get install --no-install-recommends -y \ + ca-certificates \ + lsb-release \ + git \ + clang \ + lld \ + cmake \ + make \ + libglfw3 libglfw3-dev \ + libfreeimage3 libfreeimage-dev \ + crossbuild-essential-armhf \ + qemu-user-static + +# Install ARM Dependencies +if [ ! -z "${ARM_PACKAGES_SUPPORTED}" ]; then + sudo apt-get install --no-install-recommends -y \ + libglfw3:armhf libglfw3-dev:armhf \ + libfreeimage3:armhf +fi + diff --git a/scripts/package.sh b/scripts/package.sh index c5f692d..b4fb18f 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -1,81 +1,34 @@ #!/bin/sh -# Current Version -DEB_VERSION='1.0.0' - -# Dependencies -REQUIRED_DOCKER_VERSION='19.03' -COMMON_DEPENDENCIES="docker.io (>=${REQUIRED_DOCKER_VERSION}) | docker-ce (>=${REQUIRED_DOCKER_VERSION}), libseccomp2 (>=2.4.2), docker-compose, binfmt-support" -CLIENT_DEPENDENCIES='zenity, login, policykit-1, passwd' -RECOMMENDED_DEPENDENCIES='qemu-user-static' - set -e -# Docker Messes With SetGID -chmod -R g-s debian - -# Clean out Directory -rm -rf out -mkdir -p out/deb - # Prepare -rm -rf debian/tmp -mkdir debian/tmp +VERSION="$(cat VERSION)" -# Version Time -DEB_VERSION_TIME="$(date --utc '+%Y%m%d.%H%M')" - -# Prepare DEBIAN/control -prepare_control() { - sed -i 's/${VERSION}/'"${DEB_VERSION}.${DEB_VERSION_TIME}"'/g' "$1/DEBIAN/control" - sed -i 's/${DEPENDENCIES}/'"${COMMON_DEPENDENCIES}$2"'/g' "$1/DEBIAN/control" - sed -i 's/${RECOMMENDED_DEPENDENCIES}/'"${RECOMMENDED_DEPENDENCIES}$2"'/g' "$1/DEBIAN/control" +# Common +package() { + local dir="out/$1" + + # Create DEBIAN Dir + rm -rf "${dir}/DEBIAN" + mkdir -p "${dir}/DEBIAN" + cp "debian/$1" "${dir}/DEBIAN/control" + + # Format DEBIAN/control + sed -i "s/\${VERSION}/${VERSION}~$(lsb_release -cs)/g" "${dir}/DEBIAN/control" + + # Package + dpkg-deb --root-owner-group --build "${dir}" out } -# Package Client DEBs -docker save thebrokenrail/minecraft-pi-reborn:client | gzip > debian/tmp/client-image.tar.gz -package_client() { - # Clean - rm -rf "debian/tmp/$1" - # Prepare - rsync -r debian/client/common/ "debian/tmp/$1" - rsync -r "debian/client/$1/" "debian/tmp/$1" - cp debian/tmp/client-image.tar.gz "debian/tmp/$1/usr/share/minecraft-pi/client/image.tar.gz" - prepare_control "debian/tmp/$1" ", ${CLIENT_DEPENDENCIES}" - # Build - dpkg-deb -b --root-owner-group "debian/tmp/$1" out/deb -} -package_client virgl -package_client native - -# Package Server DEB -docker save thebrokenrail/minecraft-pi-reborn:server | gzip > debian/tmp/server-image.tar.gz -package_server() { - # Clean - rm -rf debian/tmp/server - # Prepare - rsync -r debian/server/ debian/tmp/server - cp debian/tmp/server-image.tar.gz debian/tmp/server/usr/share/minecraft-pi/server/image.tar.gz - prepare_control debian/tmp/server '' - # Build - dpkg-deb -b --root-owner-group debian/tmp/server out/deb -} -package_server - -# Clean Up -rm -rf debian/tmp - -# Export Libraries -mkdir -p out/lib - -## Extract libreborn - -# Copy Headers -cp -r libreborn/include out/lib/include - -# Copy Shared Library -IMG_ID="$(docker create thebrokenrail/minecraft-pi-reborn:client)" -docker cp "${IMG_ID}:/app/minecraft-pi/mods/libreborn.so" ./out/lib || : -RET=$? -docker rm -v "${IMG_ID}" -exit ${RET} +# Find And Package +for dir in out/*; do + # Check If Directory + if [ -d "${dir}" ]; then + # Check If Debian Package Exists + pkg="$(basename ${dir})" + if [ -f "debian/${pkg}" ]; then + package "${pkg}" + fi + fi +done diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..d5c7cbe --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +# Add minecraft-pi-reborn-server To PATH +export PATH="$(pwd)/out/server-x86_64/usr/bin:${PATH}" + +# Create Test Directory +rm -rf build/test +mkdir -p build/test + +# Run Test +cd build/test +minecraft-pi-reborn-server --only-generate