From e359cbb0bd104a264646a51a3e4efa840941b40b Mon Sep 17 00:00:00 2001 From: dkeruza-neo Date: Sat, 12 Apr 2025 00:28:50 -0400 Subject: [PATCH] Added AppImage, fixed 2Spicy Resolution, again. --- .github/workflows/appimage-build.yml | 29 + .github/workflows/main.yml | 6 +- docs/changelog | 6 + scripts/appimage/install-packages.sh | 11 + scripts/appimage/make-appimage.sh | 103 ++++ scripts/lindbergh-loader.desktop | 7 + scripts/lindbergh-loader.png | Bin 0 -> 61477 bytes src/lindbergh/card_reader.h | 1 + src/lindbergh/lindbergh.c | 781 ++++++++++----------------- src/lindbergh/resolution.c | 40 +- src/lindbergh/shader_patches.c | 14 +- 11 files changed, 482 insertions(+), 516 deletions(-) create mode 100644 .github/workflows/appimage-build.yml create mode 100755 scripts/appimage/install-packages.sh create mode 100755 scripts/appimage/make-appimage.sh create mode 100644 scripts/lindbergh-loader.desktop create mode 100644 scripts/lindbergh-loader.png diff --git a/.github/workflows/appimage-build.yml b/.github/workflows/appimage-build.yml new file mode 100644 index 0000000..32e47ef --- /dev/null +++ b/.github/workflows/appimage-build.yml @@ -0,0 +1,29 @@ +name: 🐧 Linux AppImage + +on: + workflow_call: + workflow_dispatch: + +jobs: + linux-appimage: + name: "Build" + runs-on: ubuntu-22.04 + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Packages + run: scripts/appimage/install-packages.sh + + - name: Compile Build + shell: bash + run: | + make + scripts/appimage/make-appimage.sh + - name: Upload AppImage + uses: actions/upload-artifact@v4 + with: + name: "linux-appimage" + path: "lindbergh-loader.AppImage" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 663b0e8..0a3c183 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,10 +24,13 @@ jobs: linux-flatpak: name: 📦 Linux Flatpak uses: "./.github/workflows/flatpak-build.yml" + linux-appimage: + name: 📦 Linux AppImage + uses: "./.github/workflows/appimage-build.yml" create-release: name: 📤 Create Release - needs: [linux-build, linux-flatpak] + needs: [linux-build, linux-flatpak, linux-appimage] runs-on: ubuntu-22.04 if: github.ref == 'refs/heads/master' steps: @@ -55,3 +58,4 @@ jobs: files: | ./artifacts/linux-flatpak/lindbergh-loader.flatpak ./artifacts/linux-build/build.tar.gz + ./artifacts/linux-appimage/lindbergh-loader.AppImage diff --git a/docs/changelog b/docs/changelog index 80c6bbf..e6641b1 100644 --- a/docs/changelog +++ b/docs/changelog @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## [2.0.12] +### Added + - AppImage support. +### Fixed + - 2Spicy error in the scaling code, again. + ## [2.0.11] ### Added - Resolution patches for 2Spicy and HOD-Ex diff --git a/scripts/appimage/install-packages.sh b/scripts/appimage/install-packages.sh new file mode 100755 index 0000000..485927e --- /dev/null +++ b/scripts/appimage/install-packages.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +echo "Install Packages Running..." +sudo dpkg --add-architecture i386 +sudo apt-get update +sudo apt-get -y install --no-install-recommends \ + build-essential gcc-multilib fuse freeglut3-dev:i386 libsdl2-dev:i386 libfaudio-dev:i386 libvdpau1:i386 \ + libstdc++5:i386 libxmu6:i386 libpcsclite1:i386 libncurses5:i386 unzip + +sudo add-apt-repository ppa:kisak/kisak-mesa -y +sudo apt-get -y update && sudo apt -y dist-upgrade diff --git a/scripts/appimage/make-appimage.sh b/scripts/appimage/make-appimage.sh new file mode 100755 index 0000000..85f049a --- /dev/null +++ b/scripts/appimage/make-appimage.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +APPIMAGENAME=lindbergh-loader +APPIMAGEDIRNAME=lindbergh-loader.AppDir + +declare -a ADDED_LIBS=( + "libOpenGL.so.0" + "libGLdispatch.so.0" + "libGLX.so" + "libGLX_indirect.so.0" + "libGLX_mesa.so.0" + "libGL.so.1" + "libGLU.so.1" + "libglut.so.3" + "libglapi.so.0" + "libX11.so.6" + "libX11-xcb.so.1" + "libxcb.so.1" + "libxcb-dri2.so.0" + "libxcb-dri3.so.0" + "libxcb-glx.so.0" + "libXmu.so.6" + "libncurses.so.5" + "libpcsclite.so.1" + "libstdc++.so.5" + "libdrm.so.2" + "libexpat.so.1" +) + +set -e + +if [ ! -f "linuxdeploy-x86_64" ]; then + wget -O "linuxdeploy-x86_64" https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x "linuxdeploy-x86_64" +fi + +if [ ! -f "appimagetool-x86_64" ]; then + wget -O "appimagetool-x86_64" https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage + chmod +x "appimagetool-x86_64" +fi + +cd build +OUTPUT_FOLDER=$(realpath "./$APPIMAGEDIRNAME") +rm -fr "$OUTPUT_FOLDER" + +ADDED_LIBS_ARGS=() +for lib in "${ADDED_LIBS[@]}"; do + ADDED_LIBS_ARGS+=("--library=/usr/lib/i386-linux-gnu/$lib") +done + + +echo "Copying files into AppDir..." +mkdir -p "$OUTPUT_FOLDER/usr/bin" +mkdir -p "$OUTPUT_FOLDER/usr/lib" +mkdir -p "$OUTPUT_FOLDER/usr/lib32" + +# We create the AppRun file +cat > "$OUTPUT_FOLDER/AppRun" << 'EOF' +#!/bin/bash +APP_IMG_ROOT="$(dirname "$(readlink -f "$0")")" +export APP_IMG_ROOT=$APP_IMG_ROOT +export LD_LIBRARY_PATH="$APP_IMG_ROOT/usr/lib32:$LD_LIBRARY_PATH" +exec "$APP_IMG_ROOT/usr/bin/lindbergh" "$@" +EOF + +chmod +x "$OUTPUT_FOLDER/AppRun" + +cp libkswapapi.so $OUTPUT_FOLDER/usr/lib +cp libsegaapi.so $OUTPUT_FOLDER/usr/lib +cp lindbergh.so $OUTPUT_FOLDER/usr/lib +cp libposixtime.so $OUTPUT_FOLDER/usr/lib +mv lindbergh $OUTPUT_FOLDER/usr/bin +mkdir -p $OUTPUT_FOLDER/usr/lib/dri +cp /usr/lib/i386-linux-gnu/dri/* $OUTPUT_FOLDER/usr/lib/dri +cp ../libs/libcrypto.so.0.9.7 $OUTPUT_FOLDER/usr/lib +cp ../libs/libssl.so.0.9.7 $OUTPUT_FOLDER/usr/lib +cp ../libs/libopenal.so.0 $OUTPUT_FOLDER/usr/lib +cd .. + +echo "Running linuxdeploy to create AppDir..." +./linuxdeploy-x86_64 --appimage-extract-and-run --appdir="$OUTPUT_FOLDER" ${ADDED_LIBS_ARGS[@]} \ +--desktop-file="./scripts/lindbergh-loader.desktop" \ +--icon-file="./scripts/lindbergh-loader.png" + +mv $OUTPUT_FOLDER/usr/lib/libkswapapi.so $OUTPUT_FOLDER/usr/lib32 +mv $OUTPUT_FOLDER/usr/lib/libsegaapi.so $OUTPUT_FOLDER/usr/lib32 +mv $OUTPUT_FOLDER/usr/lib/lindbergh.so $OUTPUT_FOLDER/usr/lib32 +mv $OUTPUT_FOLDER/usr/lib/libposixtime.so $OUTPUT_FOLDER/usr/lib32 +mv $OUTPUT_FOLDER/usr/lib/libcrypto.so.0.9.7 $OUTPUT_FOLDER/usr/lib32 +mv $OUTPUT_FOLDER/usr/lib/libssl.so.0.9.7 $OUTPUT_FOLDER/usr/lib32 +mv $OUTPUT_FOLDER/usr/lib/libopenal.so.0 $OUTPUT_FOLDER/usr/lib32 +mv $OUTPUT_FOLDER/usr/lib/dri $OUTPUT_FOLDER/usr/lib32 + +unzip libs/Cg-3.1.zip -d $OUTPUT_FOLDER/usr/lib32 +cp libs/libCg.so $OUTPUT_FOLDER/usr/lib32/libCg2.so +ln -s -f libposixtime.so $OUTPUT_FOLDER/usr/lib32/libposixtime.so.1 +ln -s -f libposixtime.so $OUTPUT_FOLDER/usr/lib32/libposixtime.so.2.4 +ln -s -f libkswapapi.so $OUTPUT_FOLDER/usr/lib32/libGLcore.so.1 +ln -s -f libkswapapi.so $OUTPUT_FOLDER/usr/lib32/libnvidia-tls.so.1 + +echo "Generating AppImage..." +rm -f "$APPIMAGENAME.AppImage" +./appimagetool-x86_64 -v "$OUTPUT_FOLDER" "$APPIMAGENAME.AppImage" --appimage-extract-and-run diff --git a/scripts/lindbergh-loader.desktop b/scripts/lindbergh-loader.desktop new file mode 100644 index 0000000..68bafc1 --- /dev/null +++ b/scripts/lindbergh-loader.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Lindbergh Loader +Exec=lindbergh +Icon=lindbergh-loader +Type=Application +Categories=Utility;ConsoleOnly; +Terminal=true \ No newline at end of file diff --git a/scripts/lindbergh-loader.png b/scripts/lindbergh-loader.png new file mode 100644 index 0000000000000000000000000000000000000000..4dfcef3d78f5b90f5cc2af8993884517c1ab6410 GIT binary patch literal 61477 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%0DH)d#*$1};a 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 literal 0 HcmV?d00001 diff --git a/src/lindbergh/card_reader.h b/src/lindbergh/card_reader.h index ad8ab2e..fb0d1ea 100644 --- a/src/lindbergh/card_reader.h +++ b/src/lindbergh/card_reader.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/src/lindbergh/lindbergh.c b/src/lindbergh/lindbergh.c index 2a055aa..96dae62 100644 --- a/src/lindbergh/lindbergh.c +++ b/src/lindbergh/lindbergh.c @@ -1,15 +1,14 @@ -#include -#include -#include #include +#include #include #include #include -#include -#include -#include #include +#include +#include +#include +#include "config.h" #include "evdevinput.h" #include "version.h" #include "log.h" @@ -20,59 +19,14 @@ #define TEAM "bobbydilley, retrofan, dkeruza-neo, doozer, francesco, rolel, caviar-x" #define LINDBERGH_CONFIG_PATH "LINDBERGH_CONFIG_PATH" #define LINDBERGH_LOADER_CURRENT_DIR "LINDBERGH_LOADER_CURRENT_DIR" -#define MAX_PATH_LEN 1024 - -/** - * @brief Calculates the CRC32 of a single byte. - * - * This function calculates the CRC32 value for a given byte of data. - * - * @param crc The current CRC32 value. - * @param data The byte of data to calculate the CRC32 for. - * @return The updated CRC32 value. - */ -uint32_t calcCrc32(uint32_t crc, uint8_t data) -{ - crc ^= data; // No shift needed; working in LSB-first order - for (int i = 0; i < 8; i++) - { - if (crc & 1) - { - crc = (crc >> 1) ^ 0xEDB88320; - } - else - { - crc = (crc >> 1); - } - } - return crc; -} uint32_t elf_crc = 0; // List of all lindbergh executables known, not including the test executables -char *games[] = {"main.exe", - "ramboM.elf", - "vt3_Lindbergh", - "hummer_Master.elf", - "drive.elf", - "chopperM.elf", - "vsg", - "Jennifer", - "dsr", - "abc", - "hod4M.elf", - "lgj_final", - "vt3", - "id4.elf", - "id5.elf", - "lgjsp_app", - "gsevo", - "vf5", - "apacheM.elf", - "hodexRI.elf", - "a.elf", - "END"}; +char *games[] = {"a.elf", "abc", "apacheM.elf", "chopperM.elf", "drive.elf", "dsr", + "gsevo", "hod4M.elf", "hodexRI.elf", "hummer_Master.elf", "id4.elf", "id5.elf", + "Jennifer", "lgj_final", "lgjsp_app", "main.exe", "mj4", "ramboM.elf", + "vf5", "vsg", "vt3", "vt3_Lindbergh", "END"}; /** * An array containin clean games elf's CRC32 @@ -129,6 +83,7 @@ uint32_t cleanElfCRC32[] = { 0xB0A96E34, // DVP-0043 | vf5 0xF99E5635, // DVP-0044 | drive.elf 0x4143F6B4, // DVP-0048A | main.exe + 0x65489691, // DVP-0049 | mj4 0x653BC83B, // DVP-0057 | a.elf 0x04D88552, // DVP-0057B | a.elf 0x089D6051, // DVP-0060 | dsr @@ -139,6 +94,7 @@ uint32_t cleanElfCRC32[] = { 0x2E6732A3, // DVP-0070F | id5.elf 0xF99A3CDB, // DVP-0075 | id5.elf 0x05647A8E, // DVP-0079 | hummer_Master.elf + 0x0AD7CF0F, // DVP-0081 | mj4 0x4442EA15, // DVP-0083 | hummer_Master.elf 0x8DF6BBF9, // DVP-0084 | id5.elf 0x2AF8004E, // DVP-0084A | id5.elf @@ -153,67 +109,30 @@ uint32_t cleanElfCRC32[] = { int cleanElfCRC32Count = sizeof(cleanElfCRC32) / sizeof(uint32_t); -/** - * @brief Looks up a CRC value in the cleanElfCRC32 table. - * - * This function searches for a given CRC value within the cleanElfCRC32 array. - * - * @param crc The CRC value to look up. - * @return 1 if the CRC is found in the table, 0 otherwise. - */ -int lookupCrcTable(uint32_t crc) +uint32_t calcCrc32(uint32_t crc, uint8_t data) { - for (int x = 0; x < cleanElfCRC32Count; x++) + crc ^= data; + for (int i = 0; i < 8; i++) { - if (cleanElfCRC32[x] == crc) - return 1; - } - return 0; -} - -/** - * @brief Removes double quotes from a string. - * - * This function iterates through the input string and removes any double quote characters. - * It modifies the string in place. - * - * @param str The string to remove double quotes from. - */ -void removeDoubleQuotes(char *str) -{ - if (str == NULL) - { - return; - } - - int j = 0; - for (int i = 0; str[i] != '\0'; i++) - { - if (str[i] != '"') + if (crc & 1) { - str[j] = str[i]; - j++; + crc = (crc >> 1) ^ 0xEDB88320; + } + else + { + crc = (crc >> 1); } } - str[j] = '\0'; + return crc; } -/** - * @brief Calculates the CRC32 of a file. - * - * This function calculates the CRC32 of the specified file. - * - * @param filename The path to the file. - * @param crc The pointer to the variable that will contain the CRC32. - * @return 0 if the CRC32 was calculated successfully, -1 otherwise. - */ -int calculateFileCRC32(const char *filename, uint32_t *crc) +int calculateCRC32inChunks(const char *filename, uint32_t *crc) { FILE *file = fopen(filename, "rb"); - if (!file) + if (file == NULL) { log_error("Could not open file to calculate the CRC32."); - return -1; + return EXIT_FAILURE; } *crc = 0xFFFFFFFF; @@ -231,32 +150,34 @@ int calculateFileCRC32(const char *filename, uint32_t *crc) return 0; } -bool hasSpaces(const char *str) +int lookupCrcTable(uint32_t crc) { - if (!str) - return false; - while (*str) + for (int x = 0; x < cleanElfCRC32Count; x++) { - if (*str == ' ') - return true; - str++; + if (cleanElfCRC32[x] == crc) + return 1; } - return false; + return 0; } -/** - * @brief Checks if the ELF file is clean based on its CRC32. - * - * This function calculates the CRC32 of the specified ELF file and compares it - * against a list of known clean ELF CRC32 values. - * @param command The command line string containing the path to the ELF file. - */ -void checkIfElfisClean(char *command) +int fileExists(const char *path) { - char *last_space = strrchr(command, ' '); + struct stat buffer; + return stat(path, &buffer) == 0; +} + +int dirExists(const char *path) +{ + struct stat buffer; + return stat(path, &buffer) == 0 && S_ISDIR(buffer.st_mode); +} + +void isCleanElf(char *command) +{ + char *lastSpace = strrchr(command, ' '); size_t length; - if (last_space != NULL && strcmp(last_space, " -t") == 0) - length = (size_t)(last_space - command); + if (lastSpace != NULL && strcmp(lastSpace, " -t") == 0) + length = (size_t)(lastSpace - command); else length = strlen(command); @@ -264,10 +185,14 @@ void checkIfElfisClean(char *command) strncpy(elfName, command, length); elfName[length] = '\0'; - removeDoubleQuotes(elfName); + if (dirExists(elfName)) + { + log_error("There is a folder named \'%s\' in the current directory instead of a game's ELF.", elfName + 2); + exit(EXIT_FAILURE); + } uint32_t crc; - if (calculateFileCRC32(elfName, &crc) != 0) + if (calculateCRC32inChunks(elfName, &crc) != 0) return; crc = crc ^ 0xFFFFFFFF; @@ -282,260 +207,142 @@ void checkIfElfisClean(char *command) } } -/** - * Tests if the game uses a seperate elf for test mode - * and updates the command line to this elf. - * - * @param name The command path to run - */ -void testModePath(char *name) +void extractPathFromProg(const char *input, char *out_path, char *out_prog) { - printf("name: %s\n", name); - char *pos; - // Check if a different testmode elf is used - pos = strstr(name, "hod4M.elf"); - if (pos != NULL) - { - strcpy(pos, "hod4testM.elf"); - return; - } - - pos = strstr(name, "hodexRI.elf"); - if (pos != NULL) - { - strcpy(pos, "hodextestR.elf"); - return; - } - - pos = strstr(name, "/Jennifer/\"Jennifer"); - if (pos != NULL) - { - strcpy(pos, "/Jennifer/\"../JenTest/JenTest"); - return; - } - - pos = strstr(name, "/Jennifer"); - if (pos != NULL) - { - strcpy(pos, "/../JenTest/JenTest"); - return; - } - - pos = strstr(name, "apacheM.elf"); - if (pos != NULL) - { - strcpy(pos, "apachetestM.elf"); - return; - } - - pos = strstr(name, "/vt3_Lindbergh/\"vt3_Lindbergh"); - if (pos != NULL) - { - strcpy(pos, "/vt3_Lindbergh/\"vt3_testmode"); - return; - } - - pos = strstr(name, "/vt3_Lindbergh"); - if (pos != NULL) - { - strcpy(pos, "/vt3_testmode"); - return; - } - - // Otherwise add the standard -t to the end - strcat(name, " -t"); + char tmp1[MAX_PATH_LENGTH], tmp2[MAX_PATH_LENGTH]; + strncpy(tmp1, input, MAX_PATH_LENGTH); + strncpy(tmp2, input, MAX_PATH_LENGTH); + strncpy(out_prog, basename(tmp1), MAX_PATH_LENGTH); + strncpy(out_path, dirname(tmp2), MAX_PATH_LENGTH); } -/** - * @brief Checks if a given path is a valid directory. - * - * This function uses the stat system call to determine if the provided path - * corresponds to a directory. - * @param path The path to check. - */ -bool isValidDirectory(const char *path) +void testModePath(char *program) { - struct stat statbuf; - - if (stat(path, &statbuf) != 0) - return false; - - return S_ISDIR(statbuf.st_mode); + if (strcmp(program, "hod4M.elf") == 0) + strcpy(program, "hod4testM.elf"); + else if (strcmp(program, "hodexRI.elf") == 0) + strcpy(program, "hodextestR.elf"); + else if (strcmp(program, "apacheM.elf") == 0) + strcpy(program, "apachetestM.elf"); + else if (strcmp(program, "vt3_Lindbergh") == 0) + strcpy(program, "vt3_testmode"); + else if (strcmp(program, "Jennifer") == 0) + strcpy(program, "../JenTest/JenTest"); + else + strcat(program, " -t"); } -/** - * @brief Finds a suitable 32-bit library folder. - * - * This function searches for a directory that is likely to contain 32-bit - * libraries, based on a predefined list of candidate paths. - * @return A dynamically allocated string containing the path to the found - * directory, or NULL if no suitable directory is found. - */ -char *find32bLibFolder(void) +char *findPreloadLibrary(const char *originalDir, const char *gameDir) { - const char *folderCandidates[] = {"/app/lib32", "/usr/lib/i386-linux-gnu", "/usr/lib/i686-linux-gnu", "/usr/lib32", "/usr/lib"}; - int numCandidates = sizeof(folderCandidates) / sizeof(folderCandidates[0]); + static char result[MAX_PATH_LENGTH]; + char appImageLib[MAX_PATH_LENGTH]; + snprintf(appImageLib, MAX_PATH_LENGTH, "%s/usr/lib32", getenv("APP_IMG_ROOT")); + const char *folderCandidates[] = {"/app/lib32", appImageLib, originalDir, "/usr/lib/i386-linux-gnu", "/usr/lib/i686-linux-gnu", + "/usr/lib32", "/usr/lib", NULL}; - char currentPath[PATH_MAX]; - struct stat st; - char *resultPath = NULL; - - for (int i = 0; i < numCandidates; ++i) + for (int i = 0; i < sizeof(folderCandidates) / sizeof(folderCandidates[0]); i++) { - int written = snprintf(currentPath, sizeof(currentPath), "%s", folderCandidates[i]); - - if (written < 0 || (size_t)written >= sizeof(currentPath)) - continue; - - if (stat(currentPath, &st) == 0 && S_ISDIR(st.st_mode)) - { - size_t pathLen = strlen(currentPath); - resultPath = (char *)malloc(pathLen + 1); - - if (resultPath == NULL) - { - log_fatal("Failed to allocate memory for result path."); - return NULL; - } - - strcpy(resultPath, currentPath); - return resultPath; - } + snprintf(result, MAX_PATH_LENGTH, "%s/%s", folderCandidates[i], PRELOAD_FILE_NAME); + if (fileExists(result)) + return result; } + if (fileExists(PRELOAD_FILE_NAME)) + return PRELOAD_FILE_NAME; + return NULL; } -/** - * @brief Finds the path to the LD_PRELOAD library. - * - * This function searches for the lindbergh.so library in various locations, - * including the current directory and system library folders. - * @return A dynamically allocated string containing the path to the library, or an empty string if not found. - */ -char *findLDPreloadLibrary() +int pathsDiffer(const char *p1, const char *p2) { - char *foundPathPtr = NULL; - - char *systemLibFolder = find32bLibFolder(); - - if (systemLibFolder == NULL || systemLibFolder[0] == '\0') - { - log_error("Error: library folder not found.\n"); - return ""; - } - - char fullLibPath[PATH_MAX]; - - int lenWritten = snprintf(fullLibPath, sizeof(fullLibPath), "%s/%s", systemLibFolder, PRELOAD_FILE_NAME); - - if (lenWritten < 0 || (size_t)lenWritten >= sizeof(fullLibPath)) - { - log_error("Warning: Constructed path is too long or invalid: %s/%s\n", systemLibFolder, PRELOAD_FILE_NAME); - return ""; - } - - if (access(PRELOAD_FILE_NAME, F_OK) == 0) // lindbergh.so in current folder - { - char curDir[MAX_PATH_LEN]; - if (getcwd(curDir, sizeof(curDir)) == NULL) - return ""; - size_t len = strlen(curDir) + strlen(PRELOAD_FILE_NAME) + 1; - foundPathPtr = (char *)malloc(len + 1); - if (foundPathPtr == NULL) - return ""; - else - snprintf(foundPathPtr, len + 1, "%s/%s", curDir, PRELOAD_FILE_NAME); - } - else if (access(fullLibPath, F_OK) == 0) // lindbergh.so in system lib folder - { - foundPathPtr = (char *)malloc(strlen(fullLibPath) + 1); - if (foundPathPtr == NULL) - return ""; - else - strcpy(foundPathPtr, fullLibPath); - } - else // lindbergh.so in the ELF folder. - { - return PRELOAD_FILE_NAME; - } - return foundPathPtr; + char real1[MAX_PATH_LENGTH], real2[MAX_PATH_LENGTH]; + if (!realpath(p1, real1) || !realpath(p2, real2)) + return 1; + return strcmp(real1, real2) != 0; } -/** - * Makes sure the environment variables are set correctly - * - * This function sets the LD_LIBRARY_PATH and LD_PRELOAD environment variables - * to ensure that the game can find the necessary libraries and preload the - * lindbergh.so library. - * - * @param ldLibPath The path to the LD_PRELOAD library. - * @param curDir The current working directory. - * @param gameDir The directory where the game is located. - * @param zink Flag indicating whether to use Zink. - * @param nvidia Flag indicating whether to use NVIDIA GPU. - * to run the game. - */ -void setEnvironmentVariables(char *ldLibPath, char *curDir, char *gameDir, int zink, int nvidia) +bool hasSpaces(const char *path) { - // Ensure the library path is set correctly - char libraryPath[MAX_PATH_LEN] = {0}; + if (strchr(path, ' ')) + return true; - const char *currentLibraryPath = getenv(LD_LIBRARY_PATH); - if (currentLibraryPath != NULL) + return false; +} + +void setEnvironmentVariables(const char *ldLibPath, const char *originalDir, const char *gameDir, int zink, int nvidia, + const char *confFilePath) +{ + setenv("LD_PRELOAD", ldLibPath, 1); + if (hasSpaces(ldLibPath)) { - strcat(libraryPath, currentLibraryPath); - strcat(libraryPath, ":"); + log_error("The path \'%s\' where lindbergh.so is located cannot contain spaces.", ldLibPath); + exit(EXIT_FAILURE); } - strcat(libraryPath, ".:lib:../lib"); + char *currentLibraryPath = getenv("LD_LIBRARY_PATH"); + char newLdLibPath[MAX_PATH_LENGTH * 3] = ""; + + if (currentLibraryPath && strlen(currentLibraryPath) > 0) + snprintf(newLdLibPath, sizeof(newLdLibPath), "%s:", currentLibraryPath); char *tmpLibPath = dirname(strdup(ldLibPath)); if (strcmp(tmpLibPath, ".") != 0) { - strcat(libraryPath, ":"); - strcat(libraryPath, tmpLibPath); + strcat(newLdLibPath, ":"); + strcat(newLdLibPath, tmpLibPath); } - if (curDir != NULL && curDir[0] != '\0' && strcmp(curDir, tmpLibPath) != 0) + if (originalDir != NULL && originalDir[0] != '\0' && pathsDiffer(originalDir, tmpLibPath) != 0) { - strcat(libraryPath, ":"); - strcat(libraryPath, curDir); + strcat(newLdLibPath, ":"); + strcat(newLdLibPath, originalDir); } - if (gameDir != NULL && gameDir[0] != '\0') + if (gameDir && gameDir[0] != '\0' && pathsDiffer(originalDir, gameDir) && pathsDiffer(tmpLibPath, gameDir)) { - strcat(libraryPath, ":"); - strcat(libraryPath, gameDir); + strcat(newLdLibPath, ":"); + strcat(newLdLibPath, gameDir); } - setenv(LD_LIBRARY_PATH, libraryPath, 1); + strcat(newLdLibPath, ":.:lib:../lib"); - // Ensure the preload path is set correctly - if (strcmp(ldLibPath, "") != 0) - setenv(LD_PRELOAD, ldLibPath, 1); - else - setenv(LD_PRELOAD, PRELOAD_FILE_NAME, 1); // Should not reach here + if (newLdLibPath[0] == ':') + memmove(newLdLibPath, newLdLibPath + 1, strlen(newLdLibPath)); + + setenv("LD_LIBRARY_PATH", newLdLibPath, 1); + + if (strlen(confFilePath) > 0) + { + if (hasSpaces(confFilePath)) + { + log_error("The path \'%s\' for the config file cannot contain spaces.", confFilePath); + exit(EXIT_FAILURE); + } + setenv(LINDBERGH_CONFIG_PATH, confFilePath, 1); + } + + if (zink && nvidia) + { + log_error("Cannot pass both, zink and nvidia options at the same time."); + exit(EXIT_FAILURE); + } if (zink) + { setenv("MESA_LOADER_DRIVER_OVERRIDE", "zink", 1); + } if (nvidia) { setenv("__GLX_VENDOR_LIBRARY_NAME", "nvidia", 1); setenv("__NV_PRIME_RENDER_OFFLOAD", "1", 1); } - setenv("LINDBERGH_LOADER_CURRENT_DIR", curDir, 1); + + char libglDriPath[MAX_PATH_LENGTH]; + sprintf(libglDriPath, "%s/usr/lib32/dri", getenv("APP_IMG_ROOT")); + setenv("LIBGL_DRIVERS_PATH", libglDriPath, 1); } -/** - * @brief Prints the usage information for the Lindbergh loader. - * - * This function displays the command-line options and usage instructions for - * the Lindbergh loader. - * @param argv The command-line arguments array. - * Prints the usage for the loader - */ void printUsage(char *argv[]) { printf("%s [GAME_PATH] [OPTIONS]\n", argv[0]); @@ -552,15 +359,12 @@ void printUsage(char *argv[]) printf(" --gamepath | -g Specifies game path without ELF name\n"); } -/** - * @brief Lists available evdev controllers and their inputs. - * - * This function initializes the controller system and then iterates through - * the available controllers, printing their names and the names of their - * inputs. - * @return EXIT_SUCCESS on success, EXIT_FAILURE on failure. - * Lists available evdev controllers and their inputs - */ +void printVersion() +{ + printf("Lindbergh Loader v%d.%d\n", MAJOR_VERSION, MINOR_VERSION); + printf("Created by: %s\n", TEAM); +} + int listControllers() { Controllers controllers; @@ -568,7 +372,7 @@ int listControllers() ControllerStatus status = initControllers(&controllers); if (status != CONTROLLER_STATUS_SUCCESS) { - printf("Failed to list controllers\n"); + log_error("Failed to list controllers\n"); return EXIT_FAILURE; } @@ -585,33 +389,16 @@ int listControllers() printf(" - %s\n", controller.inputs[i].inputName); } } - + stopControllers(&controllers); return EXIT_SUCCESS; } -/** - * @brief Prints the version information of the Lindbergh loader. - * - * This function displays the version number of the Lindbergh loader and the - * names of the team members. - */ -void printVersion() { - printf("Lindbergh Loader v%d.%d\n", MAJOR_VERSION, MINOR_VERSION); - printf("Created by: %s\n", TEAM); -} - -/** - * Small utility to automatically detect the game and run it without - * having to type a long string in. - */ int main(int argc, char *argv[]) { - char gameELF[128] = {0}; - char gameDir[MAX_PATH_LEN] = {0}; - - if (argc > 1 && strcmp(argv[1], "--version") == 0) { + if (argc > 1 && strcmp(argv[1], "--version") == 0) + { printVersion(); return EXIT_SUCCESS; } @@ -626,16 +413,18 @@ int main(int argc, char *argv[]) { return listControllers(); } - // Build up the command to start the game - int testMode = false; - int gdb = false; - int zink = false; - int nvidia = false; - int forceGame = false; - int segaboot = false; - char *extConfigPath = NULL; - char *passedGamePath = NULL; - char *forcedGamePath = NULL; + + char passedGamePath[MAX_PATH_LENGTH] = ""; + char forcedGamePath[MAX_PATH_LENGTH] = ""; + char forcedGameDir[MAX_PATH_LENGTH] = ""; + char gameELF[MAX_PATH_LENGTH] = ""; + char extConfigPath[MAX_PATH_LENGTH] = ""; + char originalDir[MAX_PATH_LENGTH] = ""; + bool gdb = false; + bool testMode = false; + bool segaboot = false; + bool zink = false; + bool nvidia = false; for (int i = 1; i < argc; i++) { @@ -675,158 +464,159 @@ int main(int argc, char *argv[]) { break; } - extConfigPath = strdup(argv[i + 1]); + strncpy(extConfigPath, argv[i + 1], MAX_PATH_LENGTH); i += 1; continue; } - if (strcmp(argv[i], "-g") == 0 || strcmp(argv[i], "--gamepath") == 0) + if ((strcmp(argv[i], "-g") == 0 || strcmp(argv[i], "--gamepath") == 0) && i + 1 < argc) { - if (i + 1 >= argc) - { - break; - } - passedGamePath = strdup(argv[i + 1]); - - i += 1; + strncpy(passedGamePath, argv[i + 1], MAX_PATH_LENGTH); + i++; continue; } - // Treat the argument as the game name - forcedGamePath = strdup(argv[i]); - forceGame = true; + strncpy(forcedGamePath, argv[i], MAX_PATH_LENGTH); } - // We save the current dir to return to it later - char curDir[MAX_PATH_LEN]; - if (getcwd(curDir, sizeof(curDir)) == NULL) + if (!getcwd(originalDir, sizeof(originalDir))) { - log_error("Error: could not get the current directory."); + perror("getcwd"); return EXIT_FAILURE; } - if (hasSpaces(curDir)) - { - log_warn("The current path contains spaces, this most likely will cause issues."); - log_warn("Please, make sure you don't use spaces in the path."); - } + int useForceCommandPath = strlen(forcedGamePath) > 0; + bool commandOnlyElf = useForceCommandPath && strchr(forcedGamePath, '/') == NULL; - char command[PATH_MAX] = {0}; - char *forcedGameDir; - char *forcedGameElf; - if (forceGame) + if (useForceCommandPath && !commandOnlyElf) { - if (forcedGamePath != NULL) + if (!fileExists(forcedGamePath)) { - forcedGameDir = dirname(strdup(forcedGamePath)); - forcedGameElf = basename(strdup(forcedGamePath)); + log_error("File does not exist: %s\n", forcedGamePath); + return EXIT_FAILURE; + } - if (strcmp(forcedGameDir, ".") == 0) // Only the ELF file name was passed. - { - if (passedGamePath[0] != '\0') - snprintf(gameDir, MAX_PATH_LEN, "%s", passedGamePath); - else - snprintf(gameDir, MAX_PATH_LEN, "./"); - } - else - { - if (passedGamePath != NULL) // ELF with path and also -g option pointing to a folder - log_warn("Warning: elf passed with a path, and -g option enabled. Ignoring -g option."); + extractPathFromProg(forcedGamePath, forcedGameDir, gameELF); + if (hasSpaces(forcedGameDir)) + { + log_warn("The path contains spaces, this most likely will cause issues."); + log_warn("Please, make sure you don't use spaces in the path."); + } - snprintf(gameDir, MAX_PATH_LEN, "%s", forcedGameDir); - } + if (!dirExists(forcedGameDir)) + { + log_error("Directory does not exist: %s\n", forcedGameDir); + return EXIT_FAILURE; + } - if (strcmp(forcedGameElf, ".") != 0) - strcpy(gameELF, forcedGameElf); - // Check if the passed folder is valid - if (!isValidDirectory(gameDir)) + chdir(forcedGameDir); + } + else if (strlen(passedGamePath) > 0) + { + if (hasSpaces(passedGamePath)) + { + log_warn("The path contains spaces, this most likely will cause issues."); + log_warn("Please, make sure you don't use spaces in the path."); + } + + if (!dirExists(passedGamePath)) + { + log_error("Directory does not exist: %s\n", passedGamePath); + return EXIT_FAILURE; + } + + chdir(passedGamePath); + + if (commandOnlyElf) + { + strncpy(gameELF, forcedGamePath, MAX_PATH_LENGTH); + if (!fileExists(gameELF)) { - log_error("%s is not a valid directory here.", gameDir); + log_error("Program '%s' not found in %s\n", gameELF, passedGamePath); return EXIT_FAILURE; } } + else + { + for (int i = 0; games[i] && strcmp(games[i], "END") != 0; i++) + { + if (fileExists(games[i])) + { + strncpy(gameELF, games[i], MAX_PATH_LENGTH); + break; + } + } + + if (strlen(gameELF) == 0) + { + log_error("No known game file found in %s\n", passedGamePath); + return EXIT_FAILURE; + } + } + } + else if (commandOnlyElf) + { + strncpy(gameELF, forcedGamePath, MAX_PATH_LENGTH); + if (!fileExists(gameELF)) + { + log_error("'%s' not found in current directory\n", gameELF); + return EXIT_FAILURE; + } } else { - // Look for the game ELF's - struct dirent *ent; - DIR *dir; - - if (passedGamePath != NULL && passedGamePath[0] != '\0') + for (int i = 0; games[i] && strcmp(games[i], "END") != 0; i++) { - snprintf(gameDir, MAX_PATH_LEN, "%s", passedGamePath); - - // Check if the passed folder is valid - if (!isValidDirectory(gameDir)) + if (access(games[i], F_OK) == 0) { - log_error("%s is not a valid directory.", gameDir); - return EXIT_FAILURE; + strncpy(gameELF, games[i], sizeof(gameELF) - 1); + gameELF[sizeof(gameELF) - 1] = '\0'; + printf("Auto-detected ELF: %s\n", gameELF); + break; } - dir = opendir(gameDir); - } - else - { - snprintf(gameDir, MAX_PATH_LEN, "%s", curDir); - dir = opendir(curDir); } - if (dir == NULL) + if (gameELF[0] == '\0') { - log_error("Could not list files in current directory."); + log_error("No game ELF found in current directory.\n"); + printUsage(argv); return EXIT_FAILURE; } - - bool found = false; - while ((ent = readdir(dir)) != NULL) - { - if (ent->d_type != DT_REG) - continue; - - int index = 0; - while (!found) - { - if (strcmp(games[index], "END") == 0) - break; - - if (strcmp(ent->d_name, games[index]) == 0) - { - strcpy(gameELF, games[index]); - found = true; - break; - } - index++; - } - } - closedir(dir); } + if (testMode) + { + testModePath(gameELF); + } + + char *targetedGameDir = strlen(forcedGameDir) ? forcedGameDir : passedGamePath; + char *lib_path = findPreloadLibrary(originalDir, targetedGameDir); + if (!lib_path) + { + log_error("Error: %s not found in known locations.\n", PRELOAD_FILE_NAME); + return 1; + } + + setEnvironmentVariables(lib_path, originalDir, targetedGameDir, zink, nvidia, extConfigPath); + if (segaboot) { - strcpy(gameELF, "segaboot"); - testMode = true; + strcpy(gameELF, "segaboot -t"); + // testMode = true; } - if (gameELF[0] == '\0') + // Final command + char command[MAX_PATH_LENGTH]; + if (strcmp(gameELF, "../JenTest/JenTest") == 0) { - log_error("No lindbergh game found in this directory."); - printUsage(argv); - return EXIT_FAILURE; + snprintf(command, MAX_PATH_LENGTH, "%s", gameELF); + } + else + { + snprintf(command, MAX_PATH_LENGTH, "./%s", gameELF); } - sprintf(command, "\"%s/\"%s", gameDir, gameELF); - - char *ldPreloadLibrary = findLDPreloadLibrary(); - - if (strcmp(curDir, gameDir) == 0) - ldPreloadLibrary = PRELOAD_FILE_NAME; - - // Ensure environment variables are set correctly - setEnvironmentVariables(ldPreloadLibrary, curDir, gameDir, zink, nvidia); - - if (testMode) - testModePath(command); - - checkIfElfisClean(command); + isCleanElf(command); if (gdb) { @@ -836,35 +626,14 @@ int main(int argc, char *argv[]) strcpy(command, temp); } - if (extConfigPath != NULL && extConfigPath[0] != '\0') - { - if (hasSpaces(extConfigPath)) - { - log_warn("The config path contains spaces, this most likely will cause issues."); - log_warn("Please, make sure you don't use spaces in the path."); - } - setenv(LINDBERGH_CONFIG_PATH, extConfigPath, 1); - } - log_info("Starting $ %s", command); - if (gameDir[0] != '\0') - { - if (chdir(gameDir) != 0) - { - log_error("Could not change to game path."); - return EXIT_FAILURE; - } - } int sysCmd = system(command); - if (gameDir[0] != '\0') + if (chdir(originalDir) != 0) { - if (chdir(curDir) != 0) - { - log_error("Could not return to the original directory."); - return EXIT_FAILURE; - } + log_error("Could not return to the original directory."); + return EXIT_FAILURE; } return sysCmd; diff --git a/src/lindbergh/resolution.c b/src/lindbergh/resolution.c index 86db0a1..0f741cb 100644 --- a/src/lindbergh/resolution.c +++ b/src/lindbergh/resolution.c @@ -1338,7 +1338,11 @@ void glVertex3f2SP(GLfloat x, GLfloat y, GLfloat z) x *= scaleX; y *= scaleY; } - else if (curTextureID != 498) // We skip deadend + else if (curTextureID == 498) + { // deadend + ; + } + else { x += OffsetX; y -= OffsetY; @@ -1468,6 +1472,38 @@ void glVertex3fHarley(GLfloat x, GLfloat y, GLfloat z) _glVertex3f(x, y, z); } +void glVertex3fHodEx(GLfloat x, GLfloat y, GLfloat z) +{ + int (*_glVertex3f)(GLfloat x, GLfloat y, GLfloat z) = dlsym(RTLD_NEXT, "glVertex3f"); + + float scaleX = 600.0 / gWidth; + float scaleY = 240.0 / gHeight; + + if (z == -1.210000f && (y == 0.0035231018f || y == -0.0152667547f || y == -0.0209428426f || // coin/start + y == -0.0397327021f || y == -0.0454087853f || y == -0.0641986430f || y == -0.0790739134f || + y == -0.0931663215f || y == -0.0692875385f || y == -0.0833799466f || + + y == 0.0456045270f || y == 0.0268146675f || y == 0.0211385805f || y == 0.0023487248f || y == -0.0033273622f || + y == -0.0221172161f || y == -0.0787803382f || y == -0.0934598967f || + + y == 0.0524549857f || y == 0.0336651318f || y == 0.0279890448f || y == 0.0091991890f + + )) + { + + x += scaleX; + y -= scaleY; + } + else if (y == 0.0042380672f || y == -0.0169522688f || y == 0.0360235684f || y == 0.0148332343f) + { // Main bd + + x += scaleX; + y -= scaleY; + } + + _glVertex3f(x, y, z); +} + int initResolutionPatches() { gWidth = getConfig()->width; @@ -2834,8 +2870,6 @@ int initResolutionPatches() patchMemory(0x081775d2, "b801000000"); // target patchMemory(0x0804e5d2, "909090909090"); - // detourFunction(0x0804d254, glVertex3fHODEX); - setVariable(0x087aa080, gWidth); // render res patchMemory(0x0804e60c, "909090909090"); patchMemory(0x0828cc2f, "909090909090"); diff --git a/src/lindbergh/shader_patches.c b/src/lindbergh/shader_patches.c index 926556e..9c2fd46 100644 --- a/src/lindbergh/shader_patches.c +++ b/src/lindbergh/shader_patches.c @@ -1547,7 +1547,9 @@ char *findLibCg() const char *foundPath = NULL; FILE *libCgF = NULL; - char *pathsToCheck[] = {NULL, "/app/lib32/libCg2.so", "./libCg.so", NULL}; + char appImageLib[MAX_PATH_LENGTH]; + snprintf(appImageLib, MAX_PATH_LENGTH, "%s/usr/lib32/libCg2.so", getenv("APP_IMG_ROOT")); + char *pathsToCheck[] = {NULL, "/app/lib32/libCg2.so", appImageLib, "./libCg.so", NULL}; if (strcmp(getConfig()->libCgPath, "") != 0) { @@ -1559,13 +1561,13 @@ char *findLibCg() { size_t pathLen = strlen(currentDir) + strlen("/libCg.so") + 1; pathsToCheck[3] = malloc(pathLen); - if (pathsToCheck[3] != NULL) + if (pathsToCheck[4] != NULL) { - snprintf(pathsToCheck[3], pathLen, "%s/%s", currentDir, "libCg.so"); + snprintf(pathsToCheck[4], pathLen, "%s/%s", currentDir, "libCg.so"); } } - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 5; ++i) { if (pathsToCheck[i] == NULL) continue; @@ -1577,9 +1579,9 @@ char *findLibCg() } } - if (foundPath != pathsToCheck[3] && pathsToCheck[3] != NULL) + if (foundPath != pathsToCheck[4] && pathsToCheck[4] != NULL) { - free(pathsToCheck[3]); + free(pathsToCheck[4]); } if (foundPath == NULL)