More C++ Refactors

This commit is contained in:
TheBrokenRail 2024-11-17 22:48:25 -05:00
parent 58a6706cf9
commit 57503d6a31
63 changed files with 830 additions and 1006 deletions

View File

@ -62,6 +62,13 @@ string(CONCAT COMPILE_FLAGS_SETUP
# Skip RPath
"set(CMAKE_SKIP_BUILD_RPATH TRUE)"
)
if(BUILD_ARM_COMPONENTS)
string(CONCAT COMPILE_FLAGS_SETUP
"${COMPILE_FLAGS_SETUP}\n"
# Disable C++11 String ABI
"add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0)"
)
endif()
cmake_language(EVAL CODE "${COMPILE_FLAGS_SETUP}")
# Fast Math
@ -79,7 +86,7 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
endif()
endif()
# Buld Dependencies
# Build Dependencies
add_subdirectory(dependencies)
# Build libreborn
@ -117,10 +124,10 @@ endif()
if(BUILD_ARM_COMPONENTS)
install(EXPORT sdk DESTINATION "${MCPI_SDK_DIR}" FILE "sdk-targets.cmake" EXPORT_LINK_INTERFACE_LIBRARIES)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/sdk.cmake"
# Sanity Check
"${ARM_SANITY_CHECK}\n"
# Compile Flags
"${COMPILE_FLAGS_SETUP}\n"
# Snaity Check
"${ARM_SANITY_CHECK}\n"
# Log
"message(STATUS \"Using Reborn SDK v${MCPI_VERSION}\")\n"
# Include Targets

View File

@ -2,12 +2,15 @@ project(launcher)
# Launcher
add_executable(launcher
src/bootstrap.cpp
src/patchelf.cpp
src/util.cpp
src/crash-report.cpp
src/sdk.cpp
src/mods.cpp
src/bootstrap/bootstrap.cpp
src/bootstrap/mods.cpp
src/bootstrap/assets.cpp
src/bootstrap/patchelf.cpp
src/bootstrap/debug.cpp
src/util/util.cpp
src/util/sdk.cpp
src/logger/logger.cpp
src/logger/crash-report.cpp
src/options/parser.cpp
src/main.cpp
src/client/configuration.cpp
@ -19,6 +22,8 @@ target_link_libraries(launcher reborn-util LIB_LIEF trampoline-headers)
# RPath
set_target_properties(launcher PROPERTIES INSTALL_RPATH "$ORIGIN/lib/native")
target_link_options(launcher PRIVATE "LINKER:--disable-new-dtags")
# Files
target_compile_definitions(launcher PRIVATE _FILE_OFFSET_BITS=64)
# Install
install(TARGETS launcher DESTINATION "${MCPI_INSTALL_DIR}")

View File

@ -1,190 +0,0 @@
#define _FILE_OFFSET_BITS 64
#include <string>
#include <vector>
#include <libreborn/libreborn.h>
#include "util.h"
#include "bootstrap.h"
#include "patchelf.h"
#define MCPI_BINARY "minecraft-pi"
#define REQUIRED_PAGE_SIZE 4096
// Debug Information
static void run_debug_command(const char *const command[], const char *prefix) {
int status = 0;
char *output = run_command(command, &status, nullptr);
if (output != nullptr) {
// Remove Newline
size_t length = strlen(output);
if (length > 0 && output[length - 1] == '\n') {
output[length - 1] = '\0';
}
// Print
DEBUG("%s: %s", prefix, output);
free(output);
}
if (!is_exit_status_success(status)) {
ERR("Unable To Gather Debug Information");
}
}
static void print_debug_information() {
// System Information
const char *const command[] = {"uname", "-a", nullptr};
run_debug_command(command, "System Information");
// Version
DEBUG("Reborn Version: v%s", MCPI_VERSION);
// Architecture
const char *arch =
#ifdef __x86_64__
"AMD64"
#elif defined(__aarch64__)
"ARM64"
#elif defined(__arm__)
"ARM32"
#else
"Unknown"
#endif
;
DEBUG("Reborn Target Architecture: %s", arch);
}
// Bootstrap
void bootstrap(const options_t &options) {
// Debug Information
print_debug_information();
// Check Page Size
long page_size = sysconf(_SC_PAGESIZE);
if (page_size != REQUIRED_PAGE_SIZE) {
CONDITIONAL_ERR(!options.skip_pagesize_check, "Invalid page size! A page size of %ld bytes is required, but the system size is %ld bytes.", (long) REQUIRED_PAGE_SIZE, page_size);
}
// Get Binary Directory
const std::string binary_directory = get_binary_directory();
DEBUG("Binary Directory: %s", binary_directory.c_str());
// Copy SDK
if (!reborn_is_server()) {
copy_sdk(binary_directory, true);
}
// Set MCPI_REBORN_ASSETS_PATH
{
std::string assets_path = safe_realpath("/proc/self/exe");
chop_last_component(assets_path);
assets_path += "/data";
set_and_print_env(_MCPI_REBORN_ASSETS_PATH_ENV, assets_path.c_str());
}
// Resolve Binary Path & Set MCPI_DIRECTORY
std::string original_game_binary;
std::string game_binary;
{
// Log
DEBUG("Resolving File Paths...");
// Resolve Full Binary Path
const std::string full_path = binary_directory + ("/" MCPI_BINARY);
original_game_binary = safe_realpath(full_path);
const char *custom_binary = getenv(MCPI_BINARY_ENV);
if (custom_binary != nullptr) {
game_binary = safe_realpath(custom_binary);
} else {
game_binary = original_game_binary;
}
}
// Configure Preloaded Objects
std::vector<std::string> mcpi_ld_preload;
{
// Log
DEBUG("Locating Mods...");
// ARM Components
mcpi_ld_preload = bootstrap_mods(binary_directory);
}
// Configure Library Search Path
std::vector<std::string> mcpi_ld_path;
{
// Log
DEBUG("Setting Linker Search Paths...");
// Library Search Path For ARM Components
{
// Add ARM Library Directory
mcpi_ld_path.push_back("lib/arm");
// Add ARM Sysroot Libraries (Ensure Priority) (Ignore On Actual ARM System)
#ifdef MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN
mcpi_ld_path.push_back("sysroot/lib");
mcpi_ld_path.push_back("sysroot/lib/arm-linux-gnueabihf");
mcpi_ld_path.push_back("sysroot/usr/lib");
mcpi_ld_path.push_back("sysroot/usr/lib/arm-linux-gnueabihf");
#endif
// Fix Paths
for (std::string &path : mcpi_ld_path) {
path = binary_directory + '/' + path;
}
}
}
// Fix MCPI Dependencies
char new_mcpi_exe_path[] = MCPI_PATCHED_DIR "/XXXXXX";
{
// Log
DEBUG("Patching ELF...");
// Find Linker
std::string linker = "/lib/ld-linux-armhf.so.3";
#ifdef MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN
// Use ARM Sysroot Linker
linker = binary_directory + "/sysroot" + linker;
#endif
// Patch
patch_mcpi_elf_dependencies(game_binary, new_mcpi_exe_path, linker, mcpi_ld_path, mcpi_ld_preload);
// Verify
if (!starts_with(new_mcpi_exe_path, MCPI_PATCHED_DIR)) {
IMPOSSIBLE();
}
}
// Set MCPI_VANILLA_ASSETS_PATH
{
std::string assets_path = original_game_binary;
chop_last_component(assets_path);
assets_path += "/data";
set_and_print_env(_MCPI_VANILLA_ASSETS_PATH_ENV, assets_path.c_str());
}
// Start Game
INFO("Starting Game...");
// Arguments
std::vector<std::string> args;
// Use Extra If Needed
#ifdef MCPI_BUILD_RUNTIME
args.push_back("runtime");
#endif
// Specify MCPI Binary
args.push_back(new_mcpi_exe_path);
// Run
const char *new_argv[args.size() + 1];
for (std::vector<std::string>::size_type i = 0; i < args.size(); i++) {
new_argv[i] = args[i].c_str();
}
new_argv[args.size()] = nullptr;
safe_execvpe(new_argv, environ);
}

View File

@ -1,10 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include "options/parser.h"
void bootstrap(const options_t &options);
void copy_sdk(const std::string &binary_directory, bool log_with_debug);
std::vector<std::string> bootstrap_mods(const std::string &binary_directory);

View File

@ -0,0 +1,15 @@
#include <libreborn/libreborn.h>
#include "bootstrap.h"
#include "../util/util.h"
// Setup Asset Paths
static void setup_path(const char *env_name, std::string assets_path) {
chop_last_component(assets_path);
assets_path += "/data";
set_and_print_env(env_name, assets_path.c_str());
}
void bootstrap_assets(const std::string &original_game_binary) {
setup_path(_MCPI_REBORN_ASSETS_PATH_ENV, safe_realpath("/proc/self/exe"));
setup_path(_MCPI_VANILLA_ASSETS_PATH_ENV, original_game_binary);
}

View File

@ -0,0 +1,75 @@
#include <string>
#include <vector>
#include <libreborn/libreborn.h>
#include "../util/util.h"
#include "bootstrap.h"
#define MCPI_BINARY "minecraft-pi"
#define REQUIRED_PAGE_SIZE 4096
// Bootstrap
void bootstrap(const options_t &options) {
// Debug Information
print_debug_information();
// Check Page Size
const long page_size = sysconf(_SC_PAGESIZE);
if (page_size != REQUIRED_PAGE_SIZE) {
CONDITIONAL_ERR(!options.skip_pagesize_check, "Invalid page size! A page size of %ld bytes is required, but the system size is %ld bytes.", (long) REQUIRED_PAGE_SIZE, page_size);
}
// Get Binary Directory
const std::string binary_directory = get_binary_directory();
DEBUG("Binary Directory: %s", binary_directory.c_str());
// Copy SDK
if (!reborn_is_server()) {
copy_sdk(binary_directory, true);
}
// Resolve Binary Path
DEBUG("Resolving File Paths...");
std::string original_game_binary = binary_directory + ("/" MCPI_BINARY);
original_game_binary = safe_realpath(original_game_binary);
const char *custom_binary = getenv(MCPI_BINARY_ENV);
const std::string game_binary = custom_binary ? safe_realpath(custom_binary) : original_game_binary;
// Configure Preloaded Objects
DEBUG("Locating Mods...");
const std::vector<std::string> mcpi_ld_preload = bootstrap_mods(binary_directory);
// Configure Library Search Path
DEBUG("Setting Linker Search Paths...");
const std::vector<std::string> mcpi_ld_path = get_ld_path(binary_directory);
// Assets
DEBUG("Finding Assets...");
bootstrap_assets(original_game_binary);
// Patch Binary
char new_mcpi_exe_path[] = MCPI_PATCHED_DIR "/XXXXXX";
DEBUG("Patching ELF...");
patch_mcpi_elf_dependencies(game_binary, new_mcpi_exe_path, get_new_linker(binary_directory), mcpi_ld_path, mcpi_ld_preload);
// Start Game
INFO("Starting Game...");
// Arguments
const std::vector<std::string> args {
#ifdef MCPI_BUILD_RUNTIME
"runtime",
#endif
new_mcpi_exe_path
};
// Run
const char *new_argv[args.size() + 1];
for (std::vector<std::string>::size_type i = 0; i < args.size(); i++) {
new_argv[i] = args[i].c_str();
}
new_argv[args.size()] = nullptr;
safe_execvpe(new_argv, environ);
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <vector>
#include "../options/parser.h"
#define MCPI_PATCHED_DIR "/tmp/.minecraft-pi-patched"
void bootstrap(const options_t &options);
// Debugging
void print_debug_information();
// Mods
std::vector<std::string> bootstrap_mods(const std::string &binary_directory);
// Assets
void bootstrap_assets(const std::string &original_game_binary);
// ELF
std::string get_new_linker(const std::string &binary_directory);
std::vector<std::string> get_ld_path(const std::string &binary_directory);
void patch_mcpi_elf_dependencies(const std::string &original_path, char *new_path, const std::string &interpreter, const std::vector<std::string> &rpath, const std::vector<std::string> &mods);

View File

@ -0,0 +1,43 @@
#include <libreborn/libreborn.h>
#include "bootstrap.h"
// Debug Information
static void run_debug_command(const char *const command[], const char *prefix) {
int status = 0;
const std::vector<unsigned char> *output = run_command(command, &status);
if (!is_exit_status_success(status)) {
ERR("Unable To Gather Debug Information");
}
std::string output_str = (const char *) output->data();
delete output;
// Trim
const std::string::size_type length = output_str.length();
if (length > 0 && output_str[length - 1] == '\n') {
output_str.pop_back();
}
// Print
DEBUG("%s: %s", prefix, output_str.c_str());
}
void print_debug_information() {
// System Information
constexpr const char *const command[] = {"uname", "-a", nullptr};
run_debug_command(command, "System Information");
// Version
DEBUG("Reborn Version: v%s", MCPI_VERSION);
// Architecture
const char *arch =
#ifdef __x86_64__
"AMD64"
#elif defined(__aarch64__)
"ARM64"
#elif defined(__arm__)
"ARM32"
#else
"Unknown"
#endif
;
DEBUG("Reborn Target Architecture: %s", arch);
}

View File

@ -9,7 +9,7 @@
#include <libreborn/libreborn.h>
#include "patchelf.h"
#include "bootstrap.h"
// Duplicate MCPI Executable Into /tmp
static void duplicate_mcpi_executable(char *new_path) {
@ -43,12 +43,10 @@ void patch_mcpi_elf_dependencies(const std::string &original_path, char *new_pat
duplicate_mcpi_executable(new_path);
// Load Binary
std::unique_ptr<LIEF::ELF::Binary> binary = LIEF::ELF::Parser::parse(original_path);
const std::unique_ptr<LIEF::ELF::Binary> binary = LIEF::ELF::Parser::parse(original_path);
// Set Interpreter
if (!interpreter.empty()) {
binary->interpreter(interpreter);
}
binary->interpreter(interpreter);
// Remove Existing Needed Libraries
std::vector<std::string> to_remove;
@ -96,3 +94,34 @@ void patch_mcpi_elf_dependencies(const std::string &original_path, char *new_pat
ERR("Unable To Set File Permissions: %s: %s", new_path, strerror(errno));
}
}
// Linker
std::string get_new_linker(const std::string &binary_directory) {
std::string linker = "/lib/ld-linux-armhf.so.3";
#ifdef MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN
linker = binary_directory + "/sysroot" + linker;
#else
(void) binary_directory;
#endif
return linker;
}
std::vector<std::string> get_ld_path(const std::string &binary_directory) {
std::vector<std::string> mcpi_ld_path = {
// ARM Sysroot
#ifdef MCPI_USE_PREBUILT_ARMHF_TOOLCHAIN
"sysroot/lib",
"sysroot/lib/arm-linux-gnueabihf",
"sysroot/usr/lib",
"sysroot/usr/lib/arm-linux-gnueabihf",
#endif
// Libraries
"lib/arm"
};
// Fix Paths
for (std::string &path : mcpi_ld_path) {
path.insert(0, 1, '/');
path.insert(0, binary_directory);
}
// Return
return mcpi_ld_path;
}

View File

@ -7,11 +7,11 @@
#define CACHE_VERSION 0
// Load Cache
typedef struct {
struct launcher_cache {
std::string username;
std::string render_distance;
std::unordered_map<std::string, bool> feature_flags;
} launcher_cache;
};
extern launcher_cache empty_cache;
launcher_cache load_cache();

View File

@ -9,7 +9,7 @@
#include <libreborn/libreborn.h>
#include "../util.h"
#include "../util/util.h"
#include "configuration.h"
#include "cache.h"
@ -83,18 +83,16 @@ static void run_command_and_set_env(const char *env_name, const char *command[])
reborn_check_display();
// Run
int return_code;
char *output = run_command(command, &return_code, nullptr);
if (output != nullptr) {
// Trim
const size_t length = strlen(output);
if (output[length - 1] == '\n') {
output[length - 1] = '\0';
}
// Set
set_and_print_env(env_name, output);
// Free
free(output);
const std::vector<unsigned char> *output = run_command(command, &return_code);
std::string output_str = (const char *) output->data();
delete output;
// Trim
const std::string::size_type length = output_str.length();
if (length > 0 && output_str[length - 1] == '\n') {
output_str.pop_back();
}
// Set
set_and_print_env(env_name, output_str.c_str());
// Check Return Code
if (!is_exit_status_success(return_code)) {
// Launch Interrupted
@ -161,7 +159,7 @@ void configure_client(const options_t &options) {
if (options.use_default) {
// Use Default Feature Flags
set_env_if_unset(MCPI_FEATURE_FLAGS_ENV, [&cache]() {
std::string feature_flags = "";
std::string feature_flags;
load_available_feature_flags([&feature_flags, &cache](const std::string &flag) {
bool value;
// Strip Default Value

View File

@ -1,259 +0,0 @@
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include <csignal>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <ctime>
#include <string>
#include <libreborn/libreborn.h>
#include "crash-report.h"
// Show Crash Report Dialog
#define DIALOG_TITLE "Crash Report"
#define CRASH_REPORT_DIALOG_WIDTH "640"
#define CRASH_REPORT_DIALOG_HEIGHT "480"
static void show_report(const char *log_filename) {
// Fork
pid_t pid = fork();
if (pid == 0) {
// Child
setsid();
ALLOC_CHECK(freopen("/dev/null", "w", stdout));
ALLOC_CHECK(freopen("/dev/null", "w", stderr));
ALLOC_CHECK(freopen("/dev/null", "r", stdin));
const char *command[] = {
"zenity",
"--title", DIALOG_TITLE,
"--name", MCPI_APP_ID,
"--width", CRASH_REPORT_DIALOG_WIDTH,
"--height", CRASH_REPORT_DIALOG_HEIGHT,
"--text-info",
"--text", MCPI_APP_TITLE " has crashed!\n\nNeed help? Consider asking on the <a href=\"" MCPI_DISCORD_INVITE "\">Discord server</a>! <i>If you believe this is a problem with " MCPI_APP_TITLE " itself, please upload this crash report to the #bugs Discord channel.</i>",
"--filename", log_filename,
"--no-wrap",
"--font", "Monospace",
"--save-filename", MCPI_VARIANT_NAME "-crash-report.log",
"--ok-label", "Exit",
NULL
};
safe_execvpe(command, (const char *const *) environ);
}
}
// Exit Handler
static pid_t child_pid = -1;
static void exit_handler(__attribute__((unused)) int signal) {
// Murder
kill(child_pid, SIGTERM);
}
// Log File
static std::string log_filename;
static int log_fd;
static void setup_log_file() {
// Get Log Directory
const std::string home = std::string(getenv(_MCPI_HOME_ENV)) + get_home_subdirectory_for_game_data();
ensure_directory(home.c_str());
const std::string logs = home + "/logs";
ensure_directory(logs.c_str());
// Get Timestamp
time_t raw_time;
time(&raw_time);
const tm *time_info = localtime(&raw_time);
char time[512];
strftime(time, 512, "%Y-%m-%d", time_info);
// Get Log Filename
std::string file;
int num = 1;
do {
file = std::string(time) + '-' + std::to_string(num) + ".log";
log_filename = logs + '/' + file;
num++;
} while (access(log_filename.c_str(), F_OK) != -1);
// Create latest.log Symlink
const std::string latest_log = logs + "/latest.log";
unlink(latest_log.c_str());
if (symlink(file.c_str(), latest_log.c_str()) != 0) {
WARN("Unable To Create Latest Log Symlink: %s", strerror(errno));
}
// Create File
log_fd = open(log_filename.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (log_fd == -1) {
ERR("Unable To Create Log File: %s", strerror(errno));
}
reborn_set_log(log_fd);
}
// Setup
#define PIPE_READ 0
#define PIPE_WRITE 1
#define BUFFER_SIZE 1024
static void safe_write(int fd, const void *buf, size_t size) {
const ssize_t bytes_written = write(fd, buf, size);
if (bytes_written < 0) {
ERR("Unable To Write Data: %s", strerror(errno));
}
}
void setup_crash_report() {
// Setup Logging
setup_log_file();
// Store Output
int output_pipe[2];
safe_pipe2(output_pipe, 0);
int error_pipe[2];
safe_pipe2(error_pipe, 0);
int input_pipe[2];
safe_pipe2(input_pipe, 0);
// Fork
pid_t ret = fork();
if (ret == -1) {
ERR("Unable To Fork: %s", strerror(errno));
} else if (ret == 0) {
// Child Process
// Pipe stdio
dup2(output_pipe[PIPE_WRITE], STDOUT_FILENO);
close(output_pipe[PIPE_READ]);
close(output_pipe[PIPE_WRITE]);
dup2(error_pipe[PIPE_WRITE], STDERR_FILENO);
close(error_pipe[PIPE_READ]);
close(error_pipe[PIPE_WRITE]);
dup2(input_pipe[PIPE_READ], STDIN_FILENO);
close(input_pipe[PIPE_READ]);
close(input_pipe[PIPE_WRITE]);
// Create New Process Group
setpgid(0, 0);
// Kill Child If Parent Exits First
prctl(PR_SET_PDEATHSIG, SIGKILL);
// Continue Execution
} else {
// Install Signal Handlers
child_pid = ret;
struct sigaction act_sigint = {};
act_sigint.sa_flags = SA_RESTART;
act_sigint.sa_handler = &exit_handler;
sigaction(SIGINT, &act_sigint, nullptr);
struct sigaction act_sigterm = {};
act_sigterm.sa_flags = SA_RESTART;
act_sigterm.sa_handler = &exit_handler;
sigaction(SIGTERM, &act_sigterm, nullptr);
// Close Unneeded File Descriptors
close(output_pipe[PIPE_WRITE]);
close(error_pipe[PIPE_WRITE]);
close(input_pipe[PIPE_READ]);
// Set Debug Tag
reborn_debug_tag = "(Crash Reporter) ";
// Setup Polling
const int number_fds = 3;
pollfd poll_fds[number_fds];
poll_fds[0].fd = output_pipe[PIPE_READ];
poll_fds[1].fd = error_pipe[PIPE_READ];
poll_fds[2].fd = STDIN_FILENO;
for (pollfd &poll_fd : poll_fds) {
poll_fd.events = POLLIN;
}
// Poll Data
int status;
while (waitpid(ret, &status, WNOHANG) != ret) {
const int poll_ret = poll(poll_fds, number_fds, -1);
if (poll_ret == -1) {
if (errno == EINTR) {
continue;
} else {
ERR("Unable To Poll Data: %s", strerror(errno));
}
}
// Handle Data
for (pollfd &poll_fd : poll_fds) {
if (poll_fd.revents != 0) {
if (poll_fd.revents & POLLIN) {
char buf[BUFFER_SIZE];
if (poll_fd.fd == STDIN_FILENO) {
// Data Available From stdin
int bytes_available;
if (ioctl(fileno(stdin), FIONREAD, &bytes_available) == -1) {
bytes_available = 0;
}
// Read
const ssize_t bytes_read = read(poll_fd.fd, buf, BUFFER_SIZE);
if (bytes_read == -1) {
ERR("Unable To Read Input: %s", strerror(errno));
}
// Write To Child
safe_write(input_pipe[PIPE_WRITE], buf, bytes_read);
} else {
// Data Available From Child's stdout/stderr
const ssize_t bytes_read = read(poll_fd.fd, buf, BUFFER_SIZE);
if (bytes_read == -1) {
ERR("Unable To Read Log Data: %s", strerror(errno));
}
// Print To Terminal
safe_write(poll_fd.fd == output_pipe[PIPE_READ] ? STDOUT_FILENO : STDERR_FILENO, buf, bytes_read);
// Write To log
safe_write(reborn_get_log_fd(), buf, bytes_read);
}
} else {
// File Descriptor No Longer Accessible
poll_fd.events = 0;
}
}
}
}
// Close Pipes
close(output_pipe[PIPE_READ]);
close(error_pipe[PIPE_READ]);
close(input_pipe[PIPE_WRITE]);
// Check If Is Crash
const bool is_crash = !is_exit_status_success(status);
// Log Exit Code To log If Crash
if (is_crash) {
// Create Exit Code Log Line
char *exit_status = nullptr;
get_exit_status_string(status, &exit_status);
const std::string exit_code_line = "[CRASH]: Terminated" + std::string(exit_status) + '\n';
free(exit_status);
// Print Exit Code Log Line
safe_write(STDERR_FILENO, exit_code_line.c_str(), strlen(exit_code_line.c_str()));
// Write Exit Code Log Line
safe_write(reborn_get_log_fd(), exit_code_line.c_str(), strlen(exit_code_line.c_str()));
}
// Close Log File
close(log_fd);
unsetenv(_MCPI_LOG_FD_ENV);
// Show Crash Log
if (is_crash && !reborn_is_headless()) {
show_report(log_filename.c_str());
}
// Exit
exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
}
}

View File

@ -1,11 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void setup_crash_report();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,35 @@
#include <libreborn/libreborn.h>
#include "logger.h"
// Show Crash Report Dialog
#define DIALOG_TITLE "Crash Report"
#define CRASH_REPORT_DIALOG_WIDTH "640"
#define CRASH_REPORT_DIALOG_HEIGHT "480"
void show_report(const char *log_filename) {
// Fork
pid_t pid = fork();
if (pid == 0) {
// Child
setsid();
ALLOC_CHECK(freopen("/dev/null", "w", stdout));
ALLOC_CHECK(freopen("/dev/null", "w", stderr));
ALLOC_CHECK(freopen("/dev/null", "r", stdin));
const char *command[] = {
"zenity",
"--title", DIALOG_TITLE,
"--name", MCPI_APP_ID,
"--width", CRASH_REPORT_DIALOG_WIDTH,
"--height", CRASH_REPORT_DIALOG_HEIGHT,
"--text-info",
"--text", MCPI_APP_TITLE " has crashed!\n\nNeed help? Consider asking on the <a href=\"" MCPI_DISCORD_INVITE "\">Discord server</a>! <i>If you believe this is a problem with " MCPI_APP_TITLE " itself, please upload this crash report to the #bugs Discord channel.</i>",
"--filename", log_filename,
"--no-wrap",
"--font", "Monospace",
"--save-filename", MCPI_VARIANT_NAME "-crash-report.log",
"--ok-label", "Exit",
nullptr
};
safe_execvpe(command, (const char *const *) environ);
}
}

View File

@ -0,0 +1,142 @@
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include <csignal>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <ctime>
#include <string>
#include <fcntl.h>
#include <libreborn/libreborn.h>
#include "logger.h"
// Exit Handler
static pid_t child_pid = -1;
static void exit_handler(__attribute__((unused)) int signal) {
// Murder
kill(child_pid, SIGTERM);
}
// Log File
static std::string log_filename;
static int log_fd;
static void setup_log_file() {
// Get Log Directory
const std::string home = std::string(getenv(_MCPI_HOME_ENV)) + get_home_subdirectory_for_game_data();
ensure_directory(home.c_str());
const std::string logs = home + "/logs";
ensure_directory(logs.c_str());
// Get Timestamp
time_t raw_time;
time(&raw_time);
const tm *time_info = localtime(&raw_time);
char time[512];
strftime(time, 512, "%Y-%m-%d", time_info);
// Get Log Filename
std::string file;
int num = 1;
do {
file = std::string(time) + '-' + std::to_string(num) + ".log";
log_filename = logs + '/' + file;
num++;
} while (access(log_filename.c_str(), F_OK) != -1);
// Create latest.log Symlink
const std::string latest_log = logs + "/latest.log";
unlink(latest_log.c_str());
if (symlink(file.c_str(), latest_log.c_str()) != 0) {
WARN("Unable To Create Latest Log Symlink: %s", strerror(errno));
}
// Create File
log_fd = open(log_filename.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (log_fd == -1) {
ERR("Unable To Create Log File: %s", strerror(errno));
}
reborn_set_log(log_fd);
}
// Setup
void setup_logger() {
// Setup Logging
setup_log_file();
// Fork
std::optional<Process> child = fork_with_stdio();
if (!child) {
// Child Process
// Create New Process Group
setpgid(0, 0);
// Continue Execution
} else {
// Set Debug Tag
reborn_debug_tag = "(Logger) ";
DEBUG("Writing To: %s", log_filename.c_str());
// Install Signal Handlers
child_pid = child->pid;
struct sigaction act_sigint = {};
act_sigint.sa_flags = SA_RESTART;
act_sigint.sa_handler = &exit_handler;
sigaction(SIGINT, &act_sigint, nullptr);
struct sigaction act_sigterm = {};
act_sigterm.sa_flags = SA_RESTART;
act_sigterm.sa_handler = &exit_handler;
sigaction(SIGTERM, &act_sigterm, nullptr);
// Poll
poll_fds({child->fds[0], child->fds[1], STDIN_FILENO}, [&child](const int i, const size_t size, unsigned char *buf) {
if (i == 0 || i == 1) {
// stdout/stderr
// Print To Terminal
safe_write(i == 0 ? STDOUT_FILENO : STDERR_FILENO, buf, size);
// Write To log
safe_write(reborn_get_log_fd(), buf, size);
} else {
// stdin
// Write To Child
safe_write(child->fds[2], buf, size);
}
});
// Get Exit Status
const int status = child->close();
const bool is_crash = !is_exit_status_success(status);
// Log Exit Code To log If Crash
if (is_crash) {
// Create Exit Code Log Line
const std::string exit_status = get_exit_status_string(status);
const std::string exit_code_line = "[CRASH]: Terminated" + exit_status + '\n';
// Print Exit Code Log Line
safe_write(STDERR_FILENO, exit_code_line.c_str(), exit_code_line.size());
// Write Exit Code Log Line
safe_write(reborn_get_log_fd(), exit_code_line.c_str(), exit_code_line.size());
}
// Close Log File
close(log_fd);
unsetenv(_MCPI_LOG_FD_ENV);
// Show Crash Log
if (is_crash && !reborn_is_headless()) {
show_report(log_filename.c_str());
}
// Exit
exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
}
}

View File

@ -0,0 +1,4 @@
#pragma once
void setup_logger();
void show_report(const char *log_filename);

View File

@ -1,11 +1,10 @@
#include <cstdlib>
#include <libreborn/libreborn.h>
#include <sys/stat.h>
#include "bootstrap.h"
#include "bootstrap/bootstrap.h"
#include "options/parser.h"
#include "crash-report.h"
#include "util.h"
#include "logger/logger.h"
#include "util/util.h"
#include "client/configuration.h"
// Bind Options To Environmental Variable
@ -45,7 +44,8 @@ static void setup_environment(const options_t &options) {
}
// Setup MCPI_HOME
if (const char *custom_profile_directory = getenv(MCPI_PROFILE_DIRECTORY_ENV); custom_profile_directory != nullptr) {
const char *custom_profile_directory = getenv(MCPI_PROFILE_DIRECTORY_ENV);
if (custom_profile_directory != nullptr) {
// Custom Directory
custom_profile_directory = realpath(custom_profile_directory, nullptr);
ALLOC_CHECK(custom_profile_directory);
@ -86,8 +86,8 @@ static void start_game(const options_t &options) {
setvbuf(stdout, nullptr, _IONBF, 0);
// Setup Crash Reporting
if (!options.disable_crash_report) {
setup_crash_report();
if (!options.disable_logger) {
setup_logger();
}
// Configure Client Options

View File

@ -1,6 +1,6 @@
OPTION(debug, "debug", 'd', "Enable Debug Logging")
OPTION(copy_sdk, "copy-sdk", -2, "Extract Modding SDK And Exit")
OPTION(disable_crash_report, "disable-crash-report", -1, "Disable Crash Report Dialog")
OPTION(disable_logger, "disable-logger", -1, "Disable Logger (And Crash Report Dialog)")
OPTION(use_default, "default", -3, "Skip Client-Mode Configuration Dialogs")
OPTION(no_cache, "no-cache", -4, "Disable Client-Mode Configuration Cache")
OPTION(wipe_cache, "wipe-cache", -5, "Wipe Cached Client-Mode Configuration And Exit")

View File

@ -1,8 +0,0 @@
#pragma once
#include <string>
#include <vector>
#define MCPI_PATCHED_DIR "/tmp/.minecraft-pi-patched"
void patch_mcpi_elf_dependencies(const std::string &original_path, char *new_path, const std::string &interpreter, const std::vector<std::string> &rpath, const std::vector<std::string> &mods);

View File

@ -1,6 +1,6 @@
#include <libreborn/libreborn.h>
#include "bootstrap.h"
#include "../bootstrap/bootstrap.h"
#include "util.h"
// Log

View File

@ -5,10 +5,8 @@
// Simpler Version Of run_command()
void run_simple_command(const char *const command[], const char *error) {
int status = 0;
char *output = run_command(command, &status, nullptr);
if (output != nullptr) {
free(output);
}
const std::vector<unsigned char> *output = run_command(command, &status);
delete output;
if (!is_exit_status_success(status)) {
ERR("%s", error);
}

View File

@ -7,3 +7,5 @@ void run_simple_command(const char *const command[], const char *error);
void chop_last_component(std::string &str);
std::string safe_realpath(const std::string &path);
std::string get_binary_directory();
void copy_sdk(const std::string &binary_directory, bool log_with_debug);

View File

@ -6,12 +6,12 @@ configure_file(include/libreborn/config.h.in "${CMAKE_CURRENT_BINARY_DIR}/includ
# Util
add_library(reborn-util SHARED
src/util/exec.c
src/util/string.c
src/util/util.c
src/util/log.c
src/util/exec.cpp
src/util/string.cpp
src/util/util.cpp
src/util/log.cpp
src/util/cp437.cpp
src/util/env.c
src/util/env.cpp
)
target_include_directories(
reborn-util

View File

@ -1,16 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#define ENV(name, ...) extern const char *const name##_ENV;
#include "env-list.h"
#undef ENV
int is_env_var_internal(const char *env);
bool is_env_var_internal(const char *env);
void clear_internal_env_vars();
#ifdef __cplusplus
}
#endif
// Set Environmental Variable
void set_and_print_env(const char *name, const char *value);

View File

@ -1,23 +1,21 @@
#pragma once
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <string>
#include <optional>
#include <array>
#include <vector>
#include <functional>
#include "log.h"
#include "string.h"
#include "util.h"
#ifdef __cplusplus
extern "C" {
#endif
// Set Environmental Variable
void set_and_print_env(const char *name, const char *value);
// fork() With I/O
struct Process {
static constexpr int fd_count = 3;
Process(pid_t pid_, std::array<int, fd_count> fds_);
[[nodiscard]] int close() const;
const pid_t pid;
const std::array<int, fd_count> fds;
};
std::optional<Process> fork_with_stdio();
void poll_fds(const std::vector<int> &fds, const std::function<void(int, size_t, unsigned char *)> &on_data);
// Safe execvpe()
__attribute__((noreturn)) void safe_execvpe(const char *const argv[], const char *const envp[]);
@ -26,12 +24,8 @@ __attribute__((noreturn)) void safe_execvpe(const char *const argv[], const char
#define CHILD_PROCESS_TAG "(Child Process) "
// Run Command And Get Output
char *run_command(const char *const command[], int *exit_status, size_t *output_size);
std::vector<unsigned char> *run_command(const char *const command[], int *exit_status);
#define is_exit_status_success(status) (WIFEXITED(status) && WEXITSTATUS(status) == 0)
// Get Exit Status String
void get_exit_status_string(int status, char **out);
#ifdef __cplusplus
}
#endif
std::string get_exit_status_string(int status);

View File

@ -1,11 +1,7 @@
#pragma once
#include <stdio.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <cstdio>
#include <cstdlib>
// Log File
int reborn_get_log_fd();
@ -29,7 +25,3 @@ int reborn_get_debug_fd();
WARN(__VA_ARGS__); \
} \
}
#ifdef __cplusplus
}
#endif

View File

@ -1,11 +1,10 @@
#pragma once
// Patching Functions
#if defined(REBORN_HAS_PATCH_CODE) && defined(__cplusplus)
#include <string>
// Patching Functions
#if defined(REBORN_HAS_PATCH_CODE)
// Init
void reborn_init_patch();

View File

@ -1,19 +1,10 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <string>
// Sanitize String
void sanitize_string(char *str, int max_length, int allow_newlines);
void sanitize_string(std::string &str, int max_length, bool allow_newlines);
// CP437
char *to_cp437(const char *input);
char *from_cp437(const char *input);
// Starts With
int starts_with(const char *str, const char *prefix);
#ifdef __cplusplus
}
#endif
std::string to_cp437(const std::string &input);
std::string from_cp437(const std::string &input);

View File

@ -1,16 +1,17 @@
#pragma once
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <cstring>
#include <cerrno>
#include <dlfcn.h>
#include <array>
#include "log.h"
// Check Memory Allocation
#define ALLOC_CHECK(obj) \
{ \
if (obj == NULL) { \
if ((obj) == nullptr) { \
ERR("Memory Allocation Failed"); \
} \
}
@ -41,23 +42,18 @@
} \
return func; \
}
#ifdef __cplusplus
#define hooked_function_setup extern "C"
#else
#define hooked_function_setup
#endif
#define HOOK(name, return_type, args) \
EXTERNAL_FUNC(name, return_type, args) \
hooked_function_setup __attribute__((__used__)) return_type name args