diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e347c5e..1fd29f6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -134,7 +134,7 @@ jobs: # Use jq to build the JSON payload and pipe it to curl jq -n \ - --arg content "New lindbergh loader development build **v${{ needs.prepare-source.outputs.release_version }}** has been released!" \ + --arg content "New lindbergh loader build **v${{ needs.prepare-source.outputs.release_version }}** has been released!" \ --arg changelog "$CHANGELOG_BODY" \ --arg flatpak_url "$FLATPAK_URL" \ --arg appimage_url "$APPIMAGE_URL" \ diff --git a/Makefile b/Makefile index 9893fdb..da45e31 100644 --- a/Makefile +++ b/Makefile @@ -18,9 +18,10 @@ OBJS := $(filter-out src/lindbergh/lindbergh.o, $(OBJS)) all: lindbergh libxdiff.a lindbergh.so libsegaapi.so libkswapapi.so libposixtime.so -lindbergh: src/lindbergh/lindbergh.c src/lindbergh/iniParser.c src/lindbergh/sdlInput.c src/lindbergh/controlIniGen.c src/lindbergh/log.c src/lindbergh/jvs.c src/lindbergh/config.c src/lindbergh/evdevInput.c src/lindbergh/gameData.c src/lindbergh/configIni.c +lindbergh: src/lindbergh/lindbergh.c src/lindbergh/iniParser.c src/lindbergh/sdlInput.c src/lindbergh/controlIniGen.c src/lindbergh/log.c src/lindbergh/output.c src/lindbergh/jvs.c src/lindbergh/config.c src/lindbergh/evdevInput.c src/lindbergh/gameData.c src/lindbergh/configIni.c mkdir -p $(BUILD) - $(CC) -m32 -DCOMPILING_LINDBERGH_ELF src/lindbergh/lindbergh.c src/lindbergh/iniParser.c src/lindbergh/sdlInput.c src/lindbergh/controlIniGen.c src/lindbergh/log.c src/lindbergh/jvs.c src/lindbergh/config.c src/lindbergh/evdevInput.c src/lindbergh/gameData.c src/lindbergh/configIni.c -o $(BUILD)/lindbergh -lm -lSDL3 -L/app/lib32 + $(CC) -m32 -DCOMPILING_LINDBERGH_ELF src/lindbergh/lindbergh.c src/lindbergh/iniParser.c src/lindbergh/sdlInput.c src/lindbergh/controlIniGen.c src/lindbergh/log.c src/lindbergh/output.c src/lindbergh/jvs.c src/lindbergh/config.c src/lindbergh/evdevInput.c src/lindbergh/gameData.c src/lindbergh/configIni.c -o $(BUILD)/lindbergh -lm -lSDL3 -L/app/lib32 + libxdiff.a: $(XDIFF_OBJS) mkdir -p $(BUILD) diff --git a/docs/changelog b/docs/changelog index 3246b44..af9c7a6 100644 --- a/docs/changelog +++ b/docs/changelog @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. +## [2.1.2] +### Added + - Virtual Card file support for ID4 and ID5 (read the documentation). + - Show / Hide crosshairs in parts of games like HOD4, HOD-SP and RAMBO should be hidden. + - WiiRemote support for lightgun games. (Needs Testing) + - Custom crosshairs for 2 players,rendered directly on-screen rather than replacing mouse cursor. + - Preliminary Forcefeedback support with game controllers. (No Steering wheels yet) + - Touch screen support for MJ4 and AxA games. +### Fixed + - Patched Outrun ELF at original resolution would not get fixed. + - Possible memory leak (Still nothing major, ABC has its own memory leaks). + - Fixed a bug in the resolution patch for 2Spicy. + ## [2.1.1] ### Added - HOD4 Boss battle targets should be fine now. diff --git a/libs/lib32ID.tar b/libs/lib32ID.tar deleted file mode 100644 index 483101a..0000000 Binary files a/libs/lib32ID.tar and /dev/null differ diff --git a/scripts/appimage/install-packages-24.04.sh b/scripts/appimage/install-packages-24.04.sh new file mode 100755 index 0000000..27de036 --- /dev/null +++ b/scripts/appimage/install-packages-24.04.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +echo "Install Packages Running..." +sudo dpkg --add-architecture i386 +sudo apt update # && sudo apt upgrade -y +sudo apt -y install --no-install-recommends \ + build-essential gcc-multilib g++-multilib cmake fuse freeglut3-dev:i386 libvdpau1:i386 libstdc++5:i386 libxmu6:i386 \ + libpcsclite1:i386 unzip libsndio-dev libsndio-dev:i386 pulseaudio-utils:i386 zlib1g:i386 libgpg-error0:i386 \ + libfreetype6-dev:i386 libdbus-1-dev libpulse-dev libdbus-1-dev:i386 \ + libudev-dev:i386 libxcursor-dev:i386 libxfixes-dev:i386 libxi-dev:i386 libxrandr-dev:i386 libxss-dev:i386 libxxf86vm-dev:i386 git libvulkan1:i386 \ + mesa-vulkan-drivers:i386 libxtst-dev:i386 diff --git a/scripts/appimage/install-packages.sh b/scripts/appimage/install-packages.sh index e48b83e..f679630 100755 --- a/scripts/appimage/install-packages.sh +++ b/scripts/appimage/install-packages.sh @@ -8,4 +8,4 @@ sudo apt -y install --no-install-recommends \ libpcsclite1:i386 libncurses5:i386 unzip libsndio-dev libsndio-dev:i386 pulseaudio-utils:i386 zlib1g:i386 libgpg-error0:i386 \ libasound2 libasound2-dev libasound2:i386 libasound2-dev:i386 libfreetype6-dev:i386 libdbus-1-dev libpulse-dev libdbus-1-dev:i386 \ libudev-dev:i386 libxcursor-dev:i386 libxfixes-dev:i386 libxi-dev:i386 libxrandr-dev:i386 libxss-dev:i386 libxxf86vm-dev:i386 git libvulkan1:i386 \ - mesa-vulkan-drivers:i386 + mesa-vulkan-drivers:i386 libxtst-dev:i386 diff --git a/src/libsegaapi/libsegaapi.c b/src/libsegaapi/libsegaapi.c index f12af6c..fdfcd96 100644 --- a/src/libsegaapi/libsegaapi.c +++ b/src/libsegaapi/libsegaapi.c @@ -60,6 +60,7 @@ typedef struct Buffer SynthParams synthParams[MOD_ENV_TO_FILTER_CUTOFF + 1]; int lastStatus; + int ownsData; } Buffer; @@ -940,17 +941,20 @@ int SEGAAPI_CreateBuffer(BufferConfig *pConfig, BufferCallback pCallback, unsign if (dwFlags & ALLOC_USER_MEM) { buffer->data = (uint8_t *)pConfig->mapData.hBufferHdr; + buffer->ownsData = 0; } // Reuse buffer else if (dwFlags & USE_MAPPED_MEM) { buffer->data = (uint8_t *)pConfig->mapData.hBufferHdr; + buffer->ownsData = 0; } // Allocate new buffer (caller will fill it later) else { buffer->data = (uint8_t *)malloc(buffer->size); memset(buffer->data, 0, buffer->size); + buffer->ownsData = 1; } pConfig->mapData.hBufferHdr = buffer->data; @@ -1014,6 +1018,14 @@ int SEGAAPI_DestroyBuffer(void *hHandle) FAudioVoice_DestroyVoice(buffer->fAudioSourceVoice); + if (buffer->fAudioVoiceCallback) + free(buffer->fAudioVoiceCallback); + + if (buffer->ownsData && buffer->data) + free(buffer->data); + + free(buffer); + // TODO: hHandle = NULL? debug(1, "SEGAAPI_DestroyBuffer called\n"); @@ -1155,5 +1167,31 @@ int SEGAAPI_Exit(void) { debug(1, "SEGAAPI_Exit called\n"); + pthread_mutex_lock(&fAudioMutex); + + for (int i = 0; i < OUTPUT_CHANNELS; i++) + { + if (fAudioSubmixVoices[i]) + { + FAudioVoice_DestroyVoice(fAudioSubmixVoices[i]); + fAudioSubmixVoices[i] = NULL; + } + } + + if (fAudioMasteringVoice) + { + FAudioVoice_DestroyVoice(fAudioMasteringVoice); + fAudioMasteringVoice = NULL; + } + + if (fAudio) + { + FAudio_Release(fAudio); + fAudio = NULL; + } + + pthread_mutex_unlock(&fAudioMutex); + pthread_mutex_destroy(&fAudioMutex); + return SEGA_SUCCESS; } diff --git a/src/lindbergh/cardReader.c b/src/lindbergh/cardReader.c index 91088cb..043ef76 100644 --- a/src/lindbergh/cardReader.c +++ b/src/lindbergh/cardReader.c @@ -15,6 +15,8 @@ extern uint32_t gId; extern int gGrp; bool cardReaderInitialized = false; +void (*idWriteFileHeader)(void *) = NULL; + uint8_t cardHeader[] = {0x08, 0x00, 0x00, 0x00, 0x81, 0x00, 0x59, 0xda, 0x00, 0x00, 0x54, 0x4d, 0x50, 0x06, 0x03, 0x23, 0x10, 0x41, 0x62, 0xad, 0x00, 0x2b, 0x0b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x71, 0x52, 0x70, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xd2}; @@ -243,3 +245,156 @@ void cardReaderSetFd(int serial, int fd, char *fName) log_info("Cardfile %s created.\n", fName); } } + + +void idPatchEject() +{ + switch (gId) + { + case INITIALD_4_EXP_REVB: + { + patchMemoryFromString(0x082939f9, "84"); // tickInsertWait + patchMemoryFromString(0x08293a2b, "85"); + patchMemoryFromString(0x0819251e, "eb"); // reqRead + } + break; + case INITIALD_4_EXP_REVC: + { + patchMemoryFromString(0x08293aa9, "84"); // tickInsertWait + patchMemoryFromString(0x08293adb, "85"); + patchMemoryFromString(0x081925ce, "eb"); // reqRead + } + break; + case INITIALD_4_EXP_REVD: + { + patchMemoryFromString(0x08295479, "84"); // tickInsertWait + patchMemoryFromString(0x082954ab, "85"); + patchMemoryFromString(0x0819349e, "eb"); // reqRead + } + break; + case INITIALD_4_REVA: + { + patchMemoryFromString(0x0828149d, "85"); // tickInsertWait + patchMemoryFromString(0x08281603, "85"); + patchMemoryFromString(0x0817943e, "eb"); // reqRead + } + break; + case INITIALD_4_REVB: + { + patchMemoryFromString(0x082815c5, "85"); // tickInsertWait + patchMemoryFromString(0x0828172b, "85"); + patchMemoryFromString(0x081793ce, "eb"); // reqRead + } + break; + case INITIALD_4_REVC: + { + patchMemoryFromString(0x08281ae9, "85"); // tickInsertWait + patchMemoryFromString(0x08281c51, "85"); + patchMemoryFromString(0x0817990c, "eb"); // reqRead + } + break; + case INITIALD_4_REVD: + { + patchMemoryFromString(0x082833dd, "85"); // tickInsertWait + patchMemoryFromString(0x08283543, "85"); + patchMemoryFromString(0x08179c1c, "eb"); // reqRead + } + break; + case INITIALD_4_REVG: + { + patchMemoryFromString(0x082a4ae5, "84"); // tickInsertWait + patchMemoryFromString(0x082a4b17, "85"); + patchMemoryFromString(0x08190cbe, "eb"); // reqRead + } + break; + case INITIALD_5_EXP: + { + patchMemoryFromString(0x083e1eb9, "84"); // tickInsertWait + patchMemoryFromString(0x083e1eeb, "85"); + } + break; + case INITIALD_5_EXP_20: + { + patchMemoryFromString(0x083ebd1b, "84"); // tickInsertWait + patchMemoryFromString(0x083ebd51, "85"); + } + break; + case INITIALD_5_EXP_20A: + { + patchMemoryFromString(0x083ec07b, "84"); // tickInsertWait + patchMemoryFromString(0x083ec0b1, "85"); + } + break; + case INITIALD_5_JAP_REVA: + { + patchMemoryFromString(0x083d92bd, "84"); // tickInsertWait + patchMemoryFromString(0x083d92f1, "85"); + } + break; + case INITIALD_5_JAP_REVF: + { + patchMemoryFromString(0x083ec6b9, "84"); // tickInsertWait + patchMemoryFromString(0x083ec6eb, "85"); + } + } +} + +void idWriteFileHeaderTram(void *param1) +{ + idPatchEject(); + idWriteFileHeader(param1); +} + +bool idCardFileExists(const char *folderPath, long expectedSize, bool twoDigits) +{ + if (folderPath != NULL && strlen(folderPath) > 0) + { + struct stat buffer; + stat(folderPath, &buffer); + if (!S_ISDIR(buffer.st_mode)) + { + log_error("ID Card Folder does not exist! Check the config file."); + exit(EXIT_FAILURE); + } + } + + char fileName[20]; + char currentFullPath[1024]; + char *fmt; + int maxCards = 999; + char *cardFilename = "InidCrd%03d.crd"; + + if (twoDigits) + { + maxCards = 99; + cardFilename = "InidCard%02d.crd"; + } + + for (int i = 0; i <= maxCards; ++i) + { + fmt = "%s%s"; + sprintf(fileName, cardFilename, i); + if (folderPath != NULL && strlen(folderPath) > 0) + { + char lastChar = folderPath[strlen(folderPath) - 1]; + if (lastChar != '/') + fmt = "%s/%s"; + } + + if (snprintf(currentFullPath, sizeof(currentFullPath), fmt, folderPath, fileName) >= sizeof(currentFullPath)) + { + fprintf(stderr, "Error: Constructed path for '%s' in folder '%s' is too long.\n", fileName, folderPath); + continue; + } + + struct stat fileStat; + if (stat(currentFullPath, &fileStat) == 0) + { + if (fileStat.st_size == expectedSize) + { + return true; + } + } + } + return false; +} \ No newline at end of file diff --git a/src/lindbergh/cardReader.h b/src/lindbergh/cardReader.h index 6c0d480..d49819f 100644 --- a/src/lindbergh/cardReader.h +++ b/src/lindbergh/cardReader.h @@ -33,3 +33,6 @@ int initCardReader(); size_t cardReaderWrite(int fd, const void *buf, size_t count); size_t cardReaderRead(int fd, void *buf, size_t count); void cardReaderSetFd(int serial, int fd, char *fName); +void idPatchEject(); +void idWriteFileHeaderTram(void *param1); +bool idCardFileExists(const char *folderPath, long expectedSize, bool twoDigits); \ No newline at end of file diff --git a/src/lindbergh/config.c b/src/lindbergh/config.c index 918aae6..b47296e 100644 --- a/src/lindbergh/config.c +++ b/src/lindbergh/config.c @@ -14,8 +14,6 @@ EmulatorConfig config = {0}; extern uint32_t elf_crc; -FILE *configFile = NULL; - #define CONFIG_PATH "lindbergh.ini" #define MAX_LINE_LENGTH 1024 @@ -47,6 +45,7 @@ static int detectGame(uint32_t elf_crc) config.emulateDriveboard = gameData->emulateDriveboard; config.emulateMotionboard = gameData->emulateMotionboard; config.emulateHW210CardReader = gameData->emulateHW210CardReader; + config.emulateIDCardReader = gameData->emulateIDCardReader; config.emulateTouchscreen = gameData->emulateTouchscreen; config.gameLindberghColour = gameData->gameLindberghColour; return 0; @@ -116,9 +115,12 @@ void setDefaultValues(EmulatorConfig *cfg) cfg->emulateDriveboard = 0; cfg->emulateMotionboard = 0; cfg->emulateHW210CardReader = 0; + cfg->emulateIDCardReader = 0; + cfg->idCardFileAutoload = 1; cfg->emulateTouchscreen = 0; strcpy(cfg->cardFile1, "Card_01.crd"); strcpy(cfg->cardFile2, "Card_02.crd"); + strcpy(cfg->idCardFolder, ""); cfg->emulateJVS = 1; cfg->fullscreen = 0; cfg->lindberghColour = YELLOW; @@ -187,6 +189,11 @@ void setDefaultValues(EmulatorConfig *cfg) cfg->whiteBorderPercentage = 0.02f; cfg->blackBorderPercentage = 0.0f; cfg->inputMode = 1; + cfg->enableCrosshairs = 0; + strcpy(cfg->p1CrossHairPath, ""); + strcpy(cfg->p2CrossHairPath, ""); + cfg->customCrossHairWidth = 64; + cfg->customCrossHairHeight = 64; } static const char *getValue(const IniConfig *ini, const char *sectionName, const char *key) @@ -326,11 +333,14 @@ void applyIniConfig(EmulatorConfig *config, const IniConfig *ini) config->emulateDriveboard = getInt(ini, "Emulation", "EMULATE_DRIVEBOARD", config->emulateDriveboard); config->emulateMotionboard = getInt(ini, "Emulation", "EMULATE_MOTIONBOARD", config->emulateMotionboard); config->emulateHW210CardReader = getInt(ini, "Emulation", "EMULATE_HW210_CARDREADER", config->emulateHW210CardReader); + config->emulateIDCardReader = getInt(ini, "Emulation", "EMULATE_ID_CARDREADER", config->emulateIDCardReader); config->emulateTouchscreen = getInt(ini, "Emulation", "EMULATE_TOUCHSCREEN", config->emulateTouchscreen); // [Cards] + config->idCardFileAutoload = getInt(ini, "Cards", "ID_CARDFILE_AUTOLOAD", config->idCardFileAutoload); getString(ini, "Cards", "CARDFILE_01", config->cardFile1, MAX_PATH_LENGTH); getString(ini, "Cards", "CARDFILE_02", config->cardFile2, MAX_PATH_LENGTH); + getString(ini, "Cards", "ID_CARDFOLDER", config->idCardFolder, MAX_PATH_LENGTH); // [Paths] getString(ini, "Paths", "JVS_PATH", config->jvsPath, MAX_PATH_LENGTH); @@ -369,6 +379,13 @@ void applyIniConfig(EmulatorConfig *config, const IniConfig *ini) config->id5ChineseLanguage = getInt(ini, "GameSpecific", "ID5_CHINESE_LANGUAGE", config->id5ChineseLanguage); config->idSteeringPercentageReduction = getFloat(ini, "GameSpecific", "ID_STEERING_REDUCTION_PERCENTAGE", config->idSteeringPercentageReduction); + // [CrossHairs] + config->enableCrosshairs = getInt(ini, "CrossHairs", "ENABLE_CROSSHAIRS", config->enableCrosshairs); + getString(ini, "CrossHairs", "P1_CROSSHAIR_PATH", config->p1CrossHairPath, MAX_PATH_LENGTH); + getString(ini, "CrossHairs", "P2_CROSSHAIR_PATH", config->p2CrossHairPath, MAX_PATH_LENGTH); + config->customCrossHairWidth = getInt(ini, "CrossHairs", "CUSTOM_CROSSHAIRS_WIDTH", config->customCrossHairWidth); + config->customCrossHairHeight = getInt(ini, "CrossHairs", "CUSTOM_CROSSHAIRS_HEIGHT", config->customCrossHairHeight); + config->showDebugMessages = getInt(ini, "System", "DEBUG_MSGS", config->showDebugMessages); const char *colourRaw = getValue(ini, "System", "LINDBERGH_COLOUR"); if (colourRaw) diff --git a/src/lindbergh/config.h b/src/lindbergh/config.h index 4f1fc7d..37dc184 100644 --- a/src/lindbergh/config.h +++ b/src/lindbergh/config.h @@ -248,9 +248,12 @@ typedef struct int emulateDriveboard; int emulateMotionboard; int emulateHW210CardReader; + int emulateIDCardReader; + int idCardFileAutoload; int emulateTouchscreen; char cardFile1[MAX_PATH_LENGTH]; char cardFile2[MAX_PATH_LENGTH]; + char idCardFolder[MAX_PATH_LENGTH]; int emulateJVS; int fullscreen; char eepromPath[MAX_PATH_LENGTH]; @@ -324,6 +327,11 @@ typedef struct float whiteBorderPercentage; float blackBorderPercentage; int borderEnabled; + int enableCrosshairs; + char p1CrossHairPath[MAX_PATH_LENGTH]; + char p2CrossHairPath[MAX_PATH_LENGTH]; + int customCrossHairWidth; + int customCrossHairHeight; } EmulatorConfig; int initConfig(const char* configFilePath); diff --git a/src/lindbergh/configIni.c b/src/lindbergh/configIni.c index 636f3ca..6903788 100644 --- a/src/lindbergh/configIni.c +++ b/src/lindbergh/configIni.c @@ -12,7 +12,7 @@ void createDefaultIni(const char *filePath) return; } - EmulatorConfig defaults; + EmulatorConfig defaults = {0}; setDefaultValues(&defaults); fprintf(file, "# SEGA Lindbergh Emulator Configuration File\n"); @@ -58,12 +58,16 @@ void createDefaultIni(const char *filePath) fprintf(file, "# If this is set to false, then the emulator will route the traffic to one of the serial ports\n"); fprintf(file, "EMULATE_MOTIONBOARD = AUTO\n\n"); fprintf(file, "# Set to true to enable card reader emulation in Virtua Tennis 3 or R-Tuned\nEMULATE_HW210_CARDREADER = AUTO\n\n"); + fprintf(file, "# Set to true to enable card reader emulation in ID4 and ID5 file \nEMULATE_ID_CARD READER = AUTO\n\n"); fprintf(file, "# Set to true to enable touchscreen emulation with the mouse\nEMULATE_TOUCHSCREEN = AUTO\n\n"); // [Cards] fprintf(file, "[Cards]\n"); + fprintf(file, "# Set to false to use a button to insert a card manually in ID4 or ID5.\n"); + fprintf(file, "# Or true to make the loader auto load\nID_CARDFILE_AUTOLOAD = %s\n\n", defaults.idCardFileAutoload ? "true" : "false"); fprintf(file, "# Card File for reader 1 in VT3 or R-Tuned\nCARDFILE_01 = \"%s\"\n\n", defaults.cardFile1); fprintf(file, "# Card File for reader 2 in VT3 or R-Tuned\nCARDFILE_02 = \"%s\"\n\n", defaults.cardFile2); + fprintf(file, "# Folder for ID Card files\nID_CARDFOLDER = \"%s\"\n\n", defaults.idCardFolder); // [Paths] fprintf(file, "[Paths]\n"); @@ -101,7 +105,7 @@ void createDefaultIni(const char *filePath) fprintf(file, "CUSTOM_CURSOR = \"%s\"\n\n", defaults.customCursor); fprintf(file, "# Set the width of the custom cursor\nCUSTOM_CURSOR_WIDTH = %d\n\n", defaults.customCursorWidth); fprintf(file, "# Set the height of the custom cursor\nCUSTOM_CURSOR_HEIGHT = %d\n\n", defaults.customCursorHeight); - fprintf(file, "# Set a custom cursor for the touch screen in Primeval Hunt\n"); + fprintf(file, "# Set a custom cursor for the touch screen in Primeval Hunt, MJ4 Games and AxA Games\n"); fprintf(file, "TOUCH_CURSOR = \"%s\"\n\n", defaults.touchCursor); fprintf(file, "# Set the width of the custom cursor\nTOUCH_CURSOR_WIDTH = %d\n\n", defaults.touchCursorWidth); fprintf(file, "# Set the height of the custom cursor\nTOUCH_CURSOR_HEIGHT = %d\n\n", defaults.touchCursorHeight); @@ -127,6 +131,18 @@ void createDefaultIni(const char *filePath) fprintf(file, "# Set the percentage of the steering wheel travel reduction\n"); fprintf(file, "ID_STEERING_REDUCTION_PERCENTAGE = %.1f\n\n", defaults.idSteeringPercentageReduction); + fprintf(file, "[CrossHairs]\n"); + fprintf(file, "# Set to true to enable Crosshairs even when using GunLights\n"); + fprintf(file, "ENABLE_CROSSHAIRS = %s\n\n", defaults.enableCrosshairs ? "true" : "false"); + fprintf(file, "# Set the Crosshair image from a PNG file for Player 1\n"); + fprintf(file, "P1_CROSSHAIR_PATH = \"%s\"\n\n", defaults.p1CrossHairPath); + fprintf(file, "# Set the Crosshair image from a PNG file for Player 2\n"); + fprintf(file, "P2_CROSSHAIR_PATH = \"%s\"\n\n", defaults.p2CrossHairPath); + fprintf(file, "# Set the width of the Crosshair image\n"); + fprintf(file, "CUSTOM_CROSSHAIRS_WIDTH = %d\n\n", defaults.customCrossHairWidth); + fprintf(file, "# Set the height of the Crosshair image\n"); + fprintf(file, "CUSTOM_CROSSHAIRS_HEIGHT = %d\n\n", defaults.customCrossHairHeight); + // [System] fprintf(file, "[System]\n"); fprintf(file, "# Set to true to see debug messages in the console\n"); diff --git a/src/lindbergh/controlIniGen.c b/src/lindbergh/controlIniGen.c index a691b7f..1b3fbfb 100644 --- a/src/lindbergh/controlIniGen.c +++ b/src/lindbergh/controlIniGen.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "controlIniGen.h" #include "iniParser.h" @@ -211,6 +212,9 @@ const size_t gDefaultMahjongBindingsSize = sizeof(gDefaultMahjongBindings) / siz char *toUpperCase(const char *str) { + if (str == NULL) + return NULL; + char *result = (char *)malloc(strlen(str) + 1); if (result == NULL) @@ -269,9 +273,15 @@ static void getBindingString(const ControlBinding *binding, char *buffer, size_t snprintf(temp_buffer, sizeof(temp_buffer), "JOY%d_HAT%d_RIGHT", binding->deviceIndex, binding->sdlId); break; case INPUT_TYPE_GAMEPAD_BUTTON: - snprintf(temp_buffer, sizeof(temp_buffer), "GC%d_BUTTON_%s", binding->deviceIndex, - toUpperCase(SDL_GetGamepadStringForButton(binding->sdlId))); + { + char *buttonName = toUpperCase(SDL_GetGamepadStringForButton(binding->sdlId)); + if (buttonName) + { + snprintf(temp_buffer, sizeof(temp_buffer), "GC%d_BUTTON_%s", binding->deviceIndex, buttonName); + free(buttonName); + } break; + } case INPUT_TYPE_JOY_AXIS: case INPUT_TYPE_GAMEPAD_AXIS: { @@ -279,8 +289,14 @@ static void getBindingString(const ControlBinding *binding, char *buffer, size_t if (binding->type == INPUT_TYPE_JOY_AXIS) snprintf(prefix, sizeof(prefix), "JOY%d_AXIS_%d", binding->deviceIndex, binding->sdlId); else - snprintf(prefix, sizeof(prefix), "GC%d_AXIS_%s", binding->deviceIndex, - toUpperCase(SDL_GetGamepadStringForAxis(binding->sdlId))); + { + char *axisName = toUpperCase(SDL_GetGamepadStringForAxis(binding->sdlId)); + if (axisName) + { + snprintf(prefix, sizeof(prefix), "GC%d_AXIS_%s", binding->deviceIndex, axisName); + free(axisName); + } + } switch (binding->axisMode) { diff --git a/src/lindbergh/crossHair.c b/src/lindbergh/crossHair.c new file mode 100644 index 0000000..558dad1 --- /dev/null +++ b/src/lindbergh/crossHair.c @@ -0,0 +1,555 @@ +// #include +#include +#include +#include +#define GL_GLEXT_PROTOTYPES +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "crossHair.h" +#include "resolution.h" + +#define MAX_PLAYERS 2 +#define INACTIVITY_TIMEOUT 3 + +extern uint32_t gId; +extern int gGrp; +extern int gWidth; +extern int gHeight; +extern int drawableW; +extern int drawableH; + +extern int phX, phY, phW, phH; +extern int phX2, phY2, phW2, phH2; + +static Crosshair crossHair[MAX_PLAYERS]; +static GLuint gShaderProgram = 0; +static GLint gUProjectionLoc = -1; +static GLint gUTextureLoc = -1; +static pthread_t pollingThreadId = 0; + +void (*real_glDrawArrays)(GLenum, GLint, GLsizei) = NULL; + +bool p1CrossHairInitialized = false; +bool p2CrossHairInitialized = false; +bool testMode = false; + +int textureIdIdxAdjust = 0; + + +static const char *vertex_shader_source = "#version 120\n" + "attribute vec2 a_pos;\n" + "attribute vec2 a_tex_coord;\n" + "varying vec2 v_tex_coord;\n" + "uniform mat4 u_projection;\n" + "void main() {\n" + " gl_Position = u_projection * vec4(a_pos.x, a_pos.y, 0.0, 1.0);\n" + " v_tex_coord = a_tex_coord;\n" + "}\n"; +static const char *fragment_shader_source = "#version 120\n" + "varying vec2 v_tex_coord;\n" + "uniform sampler2D u_texture;\n" + "void main() {\n" + " gl_FragColor = texture2D(u_texture, v_tex_coord);\n" + "}\n"; + +GLuint compileShader(GLenum type, const char *source) +{ + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) + { + char infoLog[512]; + glGetShaderInfoLog(shader, 512, NULL, infoLog); + fprintf(stderr, "ERROR: Shader compilation failed\n%s\n", infoLog); + glDeleteShader(shader); + return 0; + } + return shader; +} + +GLuint createShaderProgram() +{ + GLuint vert = compileShader(GL_VERTEX_SHADER, vertex_shader_source); + if (vert == 0) + return 0; + GLuint frag = compileShader(GL_FRAGMENT_SHADER, fragment_shader_source); + if (frag == 0) + { + glDeleteShader(vert); + return 0; + } + GLuint program = glCreateProgram(); + glAttachShader(program, vert); + glAttachShader(program, frag); + glBindAttribLocation(program, 0, "a_pos"); + glBindAttribLocation(program, 1, "a_tex_coord"); + glLinkProgram(program); + GLint success; + glGetProgramiv(program, GL_LINK_STATUS, &success); + if (!success) + { + char infoLog[512]; + glGetProgramInfoLog(program, 512, NULL, infoLog); + fprintf(stderr, "ERROR: Shader linking failed\n%s\n", infoLog); + } + glDeleteShader(vert); + glDeleteShader(frag); + return program; +} + +void createCrosshairGeometry(Crosshair *ch) +{ + float w = (float)ch->width; + float h = (float)ch->height; + float vertices[] = {0.0f, h, 0.0f, 1.0f, w, h, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + w, h, 1.0f, 1.0f, w, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + glGenVertexArrays(1, &ch->vao); + glGenBuffers(1, &ch->vbo); + glBindVertexArray(ch->vao); + glBindBuffer(GL_ARRAY_BUFFER, ch->vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(2 * sizeof(float))); + glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +void initCrossHairs() +{ + if (gShaderProgram == 0) + { + gShaderProgram = createShaderProgram(); + gUProjectionLoc = glGetUniformLocation(gShaderProgram, "u_projection"); + gUTextureLoc = glGetUniformLocation(gShaderProgram, "u_texture"); + } + + if (gShaderProgram == 0) + return; + + if (loadCrosshairImage(0, getConfig()->p1CrossHairPath)) + p1CrossHairInitialized = true; + + if (loadCrosshairImage(1, getConfig()->p2CrossHairPath)) + p2CrossHairInitialized = true; + + if (isTestMode() || gGrp == GROUP_HOD4_TEST || gGrp == GROUP_HOD4_SP_TEST) + testMode = true; + + if (!testMode && gId != PRIMEVAL_HUNT) + startPollingThread(); + + if (!real_glDrawArrays) + real_glDrawArrays = dlsym(RTLD_NEXT, "glDrawArrays"); + + textureIdIdxAdjust = p1CrossHairInitialized + p2CrossHairInitialized; +} + +int loadCrosshairImage(int player, const char *filepath) +{ + if (player < 0 || player >= MAX_PLAYERS) + return 0; + + if (crossHair[player].surface) + { + SDL_DestroySurface(crossHair[player].surface); + crossHair[player].surface = NULL; + } + if (crossHair[player].texture) + { + glDeleteTextures(1, &crossHair[player].texture); + crossHair[player].texture = 0; + } + if (crossHair[player].vao) + { + glDeleteVertexArrays(1, &crossHair[player].vao); + crossHair[player].vao = 0; + } + if (crossHair[player].vbo) + { + glDeleteBuffers(1, &crossHair[player].vbo); + crossHair[player].vbo = 0; + } + + SDL_Surface *surface = IMG_Load(filepath); + if (!surface) + { + fprintf(stderr, "Failed to load PNG for player %d: %s\n", player + 1, SDL_GetError()); + return 0; + } + crossHair[player].surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); + SDL_DestroySurface(surface); + + crossHair[player].x = (int)(drawableW / 2.0f); + crossHair[player].y = (int)(drawableH / 2.0f); + crossHair[player].visible = false; + // crossHair[player].lastMovementTime = time(NULL); + crossHair[player].texture = 0; + crossHair[player].vao = 0; + crossHair[player].vbo = 0; + + if (gId == GHOST_SQUAD_EVOLUTION) + { + GLuint tex; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, crossHair[player].surface->w, crossHair[player].surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, + crossHair[player].surface->pixels); + + SDL_DestroySurface(crossHair[player].surface); + + crossHair[player].width = getConfig()->customCrossHairWidth; + crossHair[player].height = getConfig()->customCrossHairHeight; + crossHair[player].texture = tex; + } + + return 1; +} + +void updateCrosshairPosition(int player, float normX, float normY) +{ + if (player < 0 || player >= MAX_PLAYERS) + return; + crossHair[player].x = normX * drawableW; + crossHair[player].y = normY * drawableH; + // crossHair[player].lastMovementTime = time(NULL); + // crossHair[player].visible = true; + if (testMode || gId == PRIMEVAL_HUNT) + crossHair[player].visible = true; +} + +void renderCrosshairs(void) +{ + if (gId == PRIMEVAL_HUNT) + glViewport(phX, phY, phW, phH); + else if (gGrp == GROUP_HOD4_SP) + glViewport(0, 0, gWidth, gHeight); + + GLint texFormat = 0; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &texFormat); + + if (texFormat == 0x1908 && gId == GHOST_SQUAD_EVOLUTION) + return; + + GLint last_program, last_vao, last_vbo, last_active_texture, last_depth_func; + GLboolean last_blend_enabled, last_depth_enabled; //, last_srgb_enabled; + GLint last_blend_src, last_blend_dst; + glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vao); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_vbo); + glGetIntegerv(GL_ACTIVE_TEXTURE, &last_active_texture); + last_blend_enabled = glIsEnabled(GL_BLEND); + last_depth_enabled = glIsEnabled(GL_DEPTH_TEST); + // last_srgb_enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB); + glGetIntegerv(GL_BLEND_SRC_RGB, &last_blend_src); + glGetIntegerv(GL_BLEND_DST_RGB, &last_blend_dst); + glGetIntegerv(GL_DEPTH_FUNC, &last_depth_func); + + // if (last_srgb_enabled) + // glDisable(GL_FRAMEBUFFER_SRGB); + glDisable(GL_DEPTH_TEST); + glDepthFunc(GL_ALWAYS); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glUseProgram(gShaderProgram); + glActiveTexture(GL_TEXTURE0); + glUniform1i(gUTextureLoc, 0); + + time_t currentTime = time(NULL); + for (int i = 0; i < MAX_PLAYERS; ++i) + { + // if (difftime(currentTime, crossHair[i].lastMovementTime) > INACTIVITY_TIMEOUT) + // { + // crossHair[i].visible = false; + // } + if (!crossHair[i].visible) + continue; + + if (crossHair[i].texture == 0 && crossHair[i].surface != NULL) + { + glGenTextures(1, &crossHair[i].texture); + glBindTexture(GL_TEXTURE_2D, crossHair[i].texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, crossHair[i].surface->w, crossHair[i].surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, + crossHair[i].surface->pixels); + + crossHair[i].width = getConfig()->customCrossHairWidth; + crossHair[i].height = getConfig()->customCrossHairHeight; + createCrosshairGeometry(&crossHair[i]); + + SDL_DestroySurface(crossHair[i].surface); + crossHair[i].surface = NULL; + } + + if (crossHair[i].texture != 0 && crossHair[i].vao != 0) + { + float x = crossHair[i].x - (crossHair[i].width / 2.0f); + float y = crossHair[i].y - (crossHair[i].height / 2.0f); + float projection[16] = {0.0f}; + projection[0] = 2.0f / drawableW; + projection[5] = -2.0f / drawableH; + projection[10] = -1.0f; + projection[12] = -1.0f + (x * projection[0]); + projection[13] = 1.0f + (y * projection[5]); + projection[15] = 1.0f; + glUniformMatrix4fv(gUProjectionLoc, 1, GL_FALSE, projection); + + glBindTexture(GL_TEXTURE_2D, crossHair[i].texture); + glBindVertexArray(crossHair[i].vao); + real_glDrawArrays(GL_TRIANGLES, 0, 6); + } + } + + glBindVertexArray(last_vao); + glBindBuffer(GL_ARRAY_BUFFER, last_vbo); + glUseProgram(last_program); + glActiveTexture(last_active_texture); + glBlendFunc(last_blend_src, last_blend_dst); + if (last_blend_enabled) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + if (last_depth_enabled) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + glDepthFunc(last_depth_func); + // if (last_srgb_enabled) + // glEnable(GL_FRAMEBUFFER_SRGB); +} + +void bindAndPosition(Crosshair *c) +{ + if (!c->texture) + return; + ; + + float x = c->x - c->width / 2.0f; + float y = c->y - c->height / 2.0f; + + glBindTexture(GL_TEXTURE_2D, c->texture); + glColor4f(1, 1, 1, 1); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(x, y); + glTexCoord2f(1, 0); + glVertex2f(x + c->width, y); + glTexCoord2f(1, 1); + glVertex2f(x + c->width, y + c->height); + glTexCoord2f(0, 1); + glVertex2f(x, y + c->height); + glEnd(); +} + +void renderGsEvoCrosshairs(void) +{ + GLint texFormat = 0; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &texFormat); + + if (texFormat == 0x1908 && gId == GHOST_SQUAD_EVOLUTION) + return; + + glPushAttrib(GL_ALL_ATTRIB_BITS); + glDisable(GL_DEPTH_TEST); // for HOD4 + glDepthMask(GL_FALSE); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, drawableW, drawableH, 0, -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + if (p1CrossHairInitialized && crossHair[0].visible) + bindAndPosition(&crossHair[0]); + + if (p2CrossHairInitialized && crossHair[1].visible) + bindAndPosition(&crossHair[1]); + + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glPopAttrib(); +} + +void destroyCrosshairs(void) +{ + stopPollingThread(); + for (int i = 0; i < MAX_PLAYERS; i++) + { + if (crossHair[i].texture) + glDeleteTextures(1, &crossHair[i].texture); + if (crossHair[i].vao) + glDeleteVertexArrays(1, &crossHair[i].vao); + if (crossHair[i].vbo) + glDeleteBuffers(1, &crossHair[i].vbo); + if (crossHair[i].surface) + SDL_DestroySurface(crossHair[i].surface); + } + if (gShaderProgram) + glDeleteProgram(gShaderProgram); +} + +void glDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + if (gId == GHOST_SQUAD_EVOLUTION) + { + GLint currentFBO = 0; + glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, ¤tFBO); + renderGsEvoCrosshairs(); + } + if (!real_glDrawArrays) + real_glDrawArrays = dlsym(RTLD_NEXT, "glDrawArrays"); + real_glDrawArrays(mode, first, count); +} + +typedef struct +{ + bool keepRunning; +} PollingArgs; + +PollingArgs gPollingArgs; + +static void *gsevoPollingThreadFunc(void *arg) +{ + PollingArgs *args = (PollingArgs *)arg; + + while (args->keepRunning) + { + uint8_t p1Mode = *(uint8_t *)0x086617E8; + uint8_t p2Mode = *(uint8_t *)0x08661994; + if (p1Mode == 0x2) + crossHair[0].visible = true; + else + crossHair[0].visible = false; + + if (p2Mode == 0x2) + crossHair[1].visible = true; + else + crossHair[1].visible = false; + + SDL_Delay(10); + } + return NULL; +} + +static void *pollingThreadFunc(void *arg) +{ + PollingArgs *args = (PollingArgs *)arg; + uint32_t *pGunMgr; + uint32_t *pPlayerMgr; + + if (gId == THE_HOUSE_OF_THE_DEAD_4_REVA) + { + pGunMgr = (uint32_t *)0x0a711758; + pPlayerMgr = (uint32_t *)0x0a7117a8; + } + else if (gId == THE_HOUSE_OF_THE_DEAD_4_REVB || gId == THE_HOUSE_OF_THE_DEAD_4_REVC) + { + pGunMgr = (uint32_t *)0x0a6f27a8; + pPlayerMgr = (uint32_t *)0x0a6f27f8; + } + else if (gId == RAMBO) + pPlayerMgr = (uint32_t *)0x0842fe9c; + else if (gId == RAMBO_CHINA) + pPlayerMgr = (uint32_t *)0x084304fc; + else if (gId == THE_HOUSE_OF_THE_DEAD_4_SPECIAL_REVB) + pPlayerMgr = (uint32_t *)0x0A69F92C; + else + args->keepRunning = false; + + while (args->keepRunning) + { + if (gGrp != GROUP_HOD4 || *pGunMgr != 0x0) + { + uint8_t *gameMode; + if (gGrp == GROUP_HOD4) + { + uint32_t *gameModeAddress = *(void **)pGunMgr + 0x2c; + gameMode = *(uint8_t **)gameModeAddress + 0x38; + } + if (gGrp != GROUP_HOD4 || *gameMode == 8) + { + if (*pPlayerMgr != 0x0) + { + uint32_t *p1ModeAddress = *(void **)pPlayerMgr + 0x34; + uint32_t *p2ModeAddress = *(void **)pPlayerMgr + 0x38; + + if (*p1ModeAddress != 0x0) + { + uint8_t *p1Mode = *(uint8_t **)p1ModeAddress + 0x38; + if (*p1Mode == 3 || *p1Mode == 5) + crossHair[0].visible = true; + else + crossHair[0].visible = false; + } + + if (*p2ModeAddress != 0x0) + { + uint8_t *p2Mode = *(uint8_t **)p2ModeAddress + 0x38; + if (*p2Mode == 3 || *p2Mode == 5) + crossHair[1].visible = true; + else + crossHair[1].visible = false; + } + } + } + } + SDL_Delay(10); + } + return NULL; +} + +void startPollingThread() +{ + if (gPollingArgs.keepRunning) + return; + + gPollingArgs.keepRunning = true; + if (gId == GHOST_SQUAD_EVOLUTION) + { + pthread_create(&pollingThreadId, NULL, gsevoPollingThreadFunc, &gPollingArgs); + } + else + { + pthread_create(&pollingThreadId, NULL, pollingThreadFunc, &gPollingArgs); + } +} + +void stopPollingThread() +{ + if (gPollingArgs.keepRunning) + { + gPollingArgs.keepRunning = false; + if (pollingThreadId) + { + pthread_join(pollingThreadId, NULL); + pollingThreadId = 0; + } + } +} diff --git a/src/lindbergh/crossHair.h b/src/lindbergh/crossHair.h new file mode 100644 index 0000000..b6aee5d --- /dev/null +++ b/src/lindbergh/crossHair.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +typedef struct +{ + float x, y; + int width, height; + + unsigned int texture; + unsigned int vao; + unsigned int vbo; + SDL_Surface *surface; + time_t lastMovementTime; + bool visible; +} Crosshair; + +extern bool p1CrossHairInitialized; +extern bool p2CrossHairInitialized; + +void initCrossHairs(); +int loadCrosshairImage(int player, const char *filepath); +void updateCrosshairPosition(int player, float normX, float normY); +void renderCrosshairs(void); +void destroyCrosshairs(void); + +void startPollingThread(); +void stopPollingThread(); \ No newline at end of file diff --git a/src/lindbergh/driveBoard.c b/src/lindbergh/driveBoard.c index b5b5e3e..88a7b60 100644 --- a/src/lindbergh/driveBoard.c +++ b/src/lindbergh/driveBoard.c @@ -1,131 +1,226 @@ -#include "string.h" +#include +#include + +#include "config.h" #include "driveBoard.h" +#include "forceFeedback.h" +#include "hook.h" #include "jvs.h" #define DRIVEBOARD_READY 0x00 #define DRIVEBOARD_NOT_INIT 0x11 #define DRIVEBOARD_BUSY 0x44 -int wheelTest = 0; -unsigned char response = DRIVEBOARD_READY; +extern uint32_t gId; +extern int gGrp; +extern DeviceType hooks[5]; -double steerValue = 0.5; -double force = 0.00; +unsigned char response; +int wheelInitialized; + +uint8_t buffer[64]; +uint8_t bufferIdx = 0; int initDriveboard() { - wheelTest = 0; response = DRIVEBOARD_NOT_INIT; - - setAnalogue(ANALOGUE_1, steerValue); - + wheelInitialized = 0; return 0; } +int enableRead = 0; + ssize_t driveboardRead(int fd, void *buf, size_t count) { - memcpy(buf, &response, 1); + memset(buffer, '\0', 64); + if (enableRead) + { + char *mybuf = (char *)buf; + mybuf[0] = response; + enableRead = 0; + } return 1; } ssize_t driveboardWrite(int fd, const void *buf, size_t count) { - - if (count != 4) + const uint8_t *bytes = (const uint8_t *)buf; + for (size_t i = 0; i < count; ++i) { - //printf("Error: Drive board count not what expected\n"); - return 1; - } - - unsigned char *buffer = (unsigned char *)buf; - - switch (buffer[0]) - { - case 0xFF: - { - //printf("Driveboard: Drive board reset\n"); - response = DRIVEBOARD_READY; - } - break; - - case 0x81: - { - //printf("Driveboard: Drive board reset 2\n"); - response = DRIVEBOARD_NOT_INIT; - } - break; - - case 0xFC: - { - //printf("Driveboard: Start wheel bounds testing\n"); - wheelTest = 1; - } - break; - - case 0x80: - { - if (buffer[1] == 0 && buffer[2] == 0) + buffer[bufferIdx++] = bytes[i]; + if (bufferIdx == 4) { - force = 0; + // if (buffer[0] != 0xfd)// && buffer[0] != 0x00 && buffer[0] != 0xff && buffer[0] != 0x01 && buffer[0] != 0xfa) + // { + // printf("Write: fd: %d,count: %zu, bufferIdx: %d\n", fd, count, bufferIdx); + // for (int x = 0; x < 4; x++) + // { + // printf("0x%02x ", buffer[x]); + // } + // printf("\n"); + // } + processDrivePacket(buffer, 1); + sdlFfbDriveboard(buffer, 4); + if (count != 7) + bufferIdx = 0; } - - if (buffer[1] == 1 && buffer[2] == 1) + else if (bufferIdx == 7) { - - if (steerValue >= 0.9 && force > 0) - break; - - if (steerValue <= 0.1 && force < 0) - break; - - steerValue += force; - setAnalogue(ANALOGUE_1, (int)(steerValue * 1024)); + uint8_t player2Buffer[4]; + player2Buffer[0] = buffer[0]; + player2Buffer[1] = buffer[4]; + player2Buffer[2] = buffer[5]; + player2Buffer[3] = buffer[6]; + processDrivePacket(player2Buffer, 2); + // if (player2Buffer[0] != 0xfd && player2Buffer[0] != 0x00 && player2Buffer[0] != 0xff && player2Buffer[0] != 0x01) + // { + // printf("Write P2: fd: %d,count: %zu, bufferIdx: %d\n", fd, count, bufferIdx); + // for (int x = 0; x < 4; x++) + // { + // printf("0x%02x ", player2Buffer[x]); + // } + // printf("\n"); + // } + bufferIdx = 0; } - - //printf("Driveboard move %f %f\n", steerValue, force); } - break; + return count; +} - case 0x9e: - case 0x84: +void processDrivePacket(uint8_t *buf, int player) +{ + static int fcP1 = 0; + static int fcP2 = 0; + + switch (buf[0]) { - response = DRIVEBOARD_READY; - - if (buffer[1] == 1) - force = ((-1 * ((double)buffer[2] / 128.0)) * 2) / 100; - - if (buffer[1] == 0) - force = ((1 - ((double)buffer[2] / 128.0)) * 2) / 100; - - //printf("Driveboard set force%f %f\n", steerValue, force); - } - break; - - case 0xFA: - case 0xFD: - { - //printf("Driveboard: auto turn wheel mode\n"); - - if (wheelTest) + case 0xFF: { - // printf("Increment wheel until 0.9 -> %d\n", (int)(steerValue * 255)); - steerValue += 0.09; - setAnalogue(ANALOGUE_1, (int)(steerValue * 1024)); - response = DRIVEBOARD_BUSY; - if (steerValue >= 0.9) + printf("Driveboard: Drive board reset\n"); + // response = 0x88; + response = DRIVEBOARD_READY; + } + break; + + case 0x81: + { + printf("Driveboard: Drive board reset 2\n"); + response = DRIVEBOARD_NOT_INIT; + } + break; + + case 0xFC: + { + printf("Driveboard: Start wheel bounds testing\n"); + if (player == 1) { - wheelTest = 0; - response = DRIVEBOARD_READY; + if (fcP1 == 0) + { + response = DRIVEBOARD_BUSY; + fcP1++; + break; + } + } + else if (player == 2) + { + if (fcP2 == 0) + { + response = DRIVEBOARD_BUSY; + fcP2++; + break; + } + } + response = DRIVEBOARD_READY; + } + break; + + case 0x80: + { + if ((buf[1] == 0x00 && buf[2] == 0x00 && buf[3] == 0x00) || (buf[1] == 0x01 && buf[2] == 0x00 && buf[3] == 0x01) || + (buf[1] == 0x01 && buf[2] == 0x00 && buf[3] == 0x00)) + wheelInitialized = 1; + response = DRIVEBOARD_READY; + } + break; + + case 0x83: + case 0x86: + { + response = DRIVEBOARD_READY; + } + break; + + case 0x9e: + { + response = DRIVEBOARD_READY; + } + break; + case 0x84: + { + response = DRIVEBOARD_READY; + + if ((buf[1] == 0x01 && buf[2] == 0x00 && buf[3] == 0x05) && !wheelInitialized) + { + if (player == 1) + setAnalogue(ANALOGUE_1, (int)(0.5f * 1024.0f)); + else + setAnalogue(ANALOGUE_5, (int)(0.5f * 1024.0f)); + break; + } + + uint8_t cmd1 = buf[1]; + if (gGrp == GROUP_OUTRUN_TEST) + { + if (buf[4] != 0) + cmd1 = buf[4]; + } + + float scaled = 0.0f; + if (cmd1 == 1) + scaled = (128.0f - (buf[2] - 1)) / 256.0f; + + if (cmd1 == 0) + scaled = (256.0f - (buf[2] + 1)) / 256.0f; + + if (!wheelInitialized) + { + if (player == 1) + setAnalogue(ANALOGUE_1, (int)(scaled * 1024.0f)); + else + setAnalogue(ANALOGUE_5, (int)(scaled * 1024.0f)); } } - } - break; - - default: - //printf("Driveboard: Unknown command received %X\n", buffer[0]); break; + + case 0xF1: + { + response = DRIVEBOARD_READY; + } + break; + + case 0xFA: + case 0xFD: + { + response = DRIVEBOARD_READY; + } + break; + + default: + // printf("Driveboard: Unknown command received %X\n", buffer[0]); + break; } - return 0; + enableRead = 1; } + +int driveBoardioctl(int fd, unsigned int request, void *data) +{ + if (enableRead) + { + uint8_t d = 1; + memcpy(data, &d, sizeof(uint8_t)); + return 0; + } + return -1; +} \ No newline at end of file diff --git a/src/lindbergh/driveBoard.h b/src/lindbergh/driveBoard.h index a96dcf0..de3a391 100644 --- a/src/lindbergh/driveBoard.h +++ b/src/lindbergh/driveBoard.h @@ -1,5 +1,8 @@ +#include #include int initDriveboard(); +void processDrivePacket(uint8_t *buf, int player); ssize_t driveboardRead(int fd, void *buf, size_t count); ssize_t driveboardWrite(int fd, const void *buf, size_t count); +int driveBoardioctl(int fd, unsigned int request, void *data); \ No newline at end of file diff --git a/src/lindbergh/evdevInput.c b/src/lindbergh/evdevInput.c index bb72dd7..554cfe6 100644 --- a/src/lindbergh/evdevInput.c +++ b/src/lindbergh/evdevInput.c @@ -9,12 +9,14 @@ #include #include #include +#include #include #include #include #include "evdevInput.h" #include "config.h" +#include "crossHair.h" #include "jvs.h" #include "log.h" @@ -843,13 +845,138 @@ void normaliseName(char *string) } } +typedef struct +{ + int fd; + struct ff_effect effect; +} FFBThreadData; + +void *ffbEffectThread(void *arg) +{ + FFBThreadData *data = (FFBThreadData *)arg; + int fd = data->fd; + struct ff_effect effect = data->effect; + + // Trigger the effect + struct input_event ffb_event; + ffb_event.type = EV_FF; + ffb_event.code = effect.id; + ffb_event.value = 1; + + if (write(fd, &ffb_event, sizeof(ffb_event)) == -1) + { + perror("Failed to trigger FFB effect"); + } + else + { + printf("FFB effect triggered with ID %d\n", effect.id); + + usleep(effect.replay.length * 1000); + } + + // Erase FFB effect + if (ioctl(fd, EVIOCRMFF, effect.id) == -1) + { + perror("Failed to erase FFB effect"); + } + else + { + printf("FFB effect with ID %d erased\n", effect.id); + } + + close(fd); + free(data); + return NULL; +} + +void initFFB(Controller *controller, uint16_t weak_magnitude, uint16_t strong_magnitude, uint16_t length) +{ + if (!controller->hasFFB) + { + printf("Controller does not support FFB.\n"); + return; + } + + int fd = open(controller->path, O_RDWR); + if (fd < 0) + { + perror("Failed to open FFB device"); + return; + } + + struct ff_effect effect; + memset(&effect, 0, sizeof(effect)); + + // // Configure the FFB effect + // effect.type = FF_RUMBLE; + // effect.id = -1; // Let the system assign an ID + // effect.u.rumble.weak_magnitude = weak_magnitude; + // effect.u.rumble.strong_magnitude = strong_magnitude; + // effect.replay.length = length; // Duration + // effect.replay.delay = 0; + + // Configure as periodic + effect.type = FF_PERIODIC; + effect.id = -1; // Let the system assign an ID + effect.u.periodic.waveform = FF_SINE; // FF_SINE/FF_SQUARE/FF_TRIANGLE + effect.u.periodic.magnitude = 0x100; // magnitude + effect.u.periodic.offset = 0; + effect.u.periodic.phase = 0; + effect.u.periodic.period = 100; // period between bumps + effect.u.periodic.envelope.attack_length = 100; // attack + effect.u.periodic.envelope.attack_level = 0; + effect.u.periodic.envelope.fade_length = 100; // fade + effect.u.periodic.envelope.fade_level = 0; + effect.replay.length = 500; // duration + effect.replay.delay = 0; // No delay + + // Upload the effect + if (ioctl(fd, EVIOCSFF, &effect) == -1) + { + perror("Failed to upload FFB effect"); + close(fd); + return; + } + + printf("FFB effect initialized with ID %d\n", effect.id); + + // Prepare thread data + FFBThreadData *threadData = malloc(sizeof(FFBThreadData)); + if (!threadData) + { + perror("Failed to allocate thread data"); + close(fd); + return; + } + threadData->fd = fd; + threadData->effect = effect; + + // Start the thread + pthread_t thread; + if (pthread_create(&thread, NULL, ffbEffectThread, threadData) != 0) + { + perror("Failed to create FFB thread"); + free(threadData); + close(fd); + } + else + { + pthread_detach(thread); + } +} + ControllerStatus loadEvdevControllers(Controllers *controllers) { struct dirent **namelist; - controllers->count = scandir("/dev/input", &namelist, isEventDevice, alphasort); + int count = scandir("/dev/input", &namelist, isEventDevice, alphasort); + if (count < 0) + { + return CONTROLLER_STATUS_ERROR; + } + controllers->count = count; controllers->controller = malloc(sizeof(Controller) * controllers->count); - if (controllers == NULL) + if (controllers->controller == NULL) { for (int i = 0; i < controllers->count; i++) { @@ -1020,6 +1147,28 @@ ControllerStatus loadEvdevControllers(Controllers *controllers) } } } + // Test if Controller support FFB + if (test_bit(EV_FF, bit[0])) + { + controllers->controller[i].hasFFB = 1; + printf("FFB is available on controller %s\n", controllers->controller[i].name); + + // FFB effect + initFFB(&controllers->controller[i], 0xFFFF, 0xFFFF, 2000); + + if (controllers->controller[i].ffbEffectId >= 0) + { + printf("FFB effect initialized with ID: %d\n", controllers->controller[i].ffbEffectId); + } + else + { + printf("Failed to initialize FFB effect on controller %s\n", controllers->controller[i].name); + } + } + else + { + controllers->controller[i].hasFFB = 0; + } close(controller); } @@ -1111,7 +1260,6 @@ void *controllerThread(void *_args) else setSwitch(args->controller->keyTriggers[event.code].player, args->controller->keyTriggers[event.code].channel, event.value == 0 ? 0 : 1); - } break; @@ -1168,6 +1316,26 @@ void *controllerThread(void *_args) if(channel == ANALOGUE_1 && (gGrp == GROUP_ID4_EXP || gGrp == GROUP_ID4_JAP || gGrp == GROUP_ID5)) scaled = (scaled - 0.5f) * 0.35f + 0.5f; + if (channel == ANALOGUE_1 || channel == ANALOGUE_2) + { + args->controller->lastAnalogueValue[channel] = scaled; + + double x_pos = (channel == ANALOGUE_1) ? scaled : args->controller->lastAnalogueValue[ANALOGUE_1]; + double y_pos = (channel == ANALOGUE_2) ? scaled : args->controller->lastAnalogueValue[ANALOGUE_2]; + + updateCrosshairPosition(0, x_pos, y_pos); + } + + if (channel == ANALOGUE_3 || channel == ANALOGUE_4) + { + args->controller->lastAnalogueValue[channel] = scaled; + + double x_pos = (channel == ANALOGUE_3) ? scaled : args->controller->lastAnalogueValue[ANALOGUE_3]; + double y_pos = (channel == ANALOGUE_4) ? scaled : args->controller->lastAnalogueValue[ANALOGUE_4]; + + updateCrosshairPosition(1, x_pos, y_pos); + } + setAnalogue(channel, scaled * (pow(2, jvsBits) - 1)); } else diff --git a/src/lindbergh/evdevInput.h b/src/lindbergh/evdevInput.h index 66414bc..ab47b42 100644 --- a/src/lindbergh/evdevInput.h +++ b/src/lindbergh/evdevInput.h @@ -82,6 +82,8 @@ typedef struct int enabled; int inUse; + int hasFFB; + int ffbEffectId; double lastAnalogueValue[8]; } Controller; diff --git a/src/lindbergh/forceFeedback.c b/src/lindbergh/forceFeedback.c new file mode 100644 index 0000000..05a52bf --- /dev/null +++ b/src/lindbergh/forceFeedback.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include + +#include "forceFeedback.h" +#include "sdlInput.h" + +static int effect_id = -1; +extern SDLControllers sdlJoysticks; + +void sdlFfbInit(void) +{ + int i, num_haptics; + SDL_HapticID *haptics = SDL_GetHaptics(&num_haptics); + if (haptics) + { + for (i = 0; i < num_haptics; ++i) + { + SDL_HapticID instance_id = haptics[i]; + SDL_Log("Haptic %" SDL_PRIu32 ": %s", instance_id, SDL_GetHapticNameForID(instance_id)); + sdlJoysticks.haptics[i] = SDL_OpenHaptic(instance_id); + sdlFfbRumble(1.0, 1.0, 200); + } + + SDL_free(haptics); + } + else { + SDL_Log("No haptic support!"); + } + + // int num_joysticks; + // SDL_JoystickID *joystics = SDL_GetJoysticks(&num_joysticks); + // if (joystics) + // { + // SDL_JoystickID instance_id; + // for (int i = 0; i < num_joysticks; ++i) + // { + // instance_id = joystics[i]; + // // sdlJoysticks.haptics[i] = SDL_HapticOpenFromJoystick(sdlJoysticks.joysticks[i]); + // // sdlJoysticks.haptics[i] = SDL_OpenHapticFromJoystick(SDL_GetGamepadJoystick(sdlJoysticks.controllers[i])); + // sdlJoysticks.haptics[i] = SDL_OpenHaptic(instance_id); + // printf("JoyName: %s\n", SDL_GetJoystickNameForID(instance_id)); + // if (sdlJoysticks.haptics[i] && SDL_GetHapticFeatures(sdlJoysticks.haptics[i]) & SDL_HAPTIC_LEFTRIGHT) + // { + // printf("Haptic initialized!\n"); + // sdlFfbRumble(1.0, 1.0, 200); + // } + // else + // { + // printf("No haptic support!\n"); + // sdlJoysticks.haptics[i] = NULL; + // } + // } + // } +} + +void sdlFfbRumble(float left, float right, int duration_ms) +{ + if (!sdlJoysticks.haptics[0]) + return; + SDL_HapticEffect effect; + memset(&effect, 0, sizeof(effect)); + effect.type = SDL_HAPTIC_LEFTRIGHT; + effect.leftright.length = duration_ms; + effect.leftright.large_magnitude = (Uint16)(left * 0xFFFF); + effect.leftright.small_magnitude = (Uint16)(right * 0xFFFF); + if (effect_id >= 0) + SDL_DestroyHapticEffect(sdlJoysticks.haptics[0], effect_id); + effect_id = SDL_CreateHapticEffect(sdlJoysticks.haptics[0], &effect); + SDL_RunHapticEffect(sdlJoysticks.haptics[0], effect_id, 1); +} + +void sdlFfbShutdown(void) +{ + if (sdlJoysticks.haptics[0]) + { + SDL_CloseHaptic(sdlJoysticks.haptics[0]); + sdlJoysticks.haptics[0] = NULL; + } +} + +void sdlFfbDriveboard(const unsigned char *buffer, size_t count) +{ + + // printf("FFB driveboard: count=%zu, data:", count); + // for (size_t i = 0; i < count; ++i) { + // printf(" 0x%02x", buffer[i]); + // } + // printf("\n"); + + switch (buffer[0]) + { + case 0x80: + { // power + if (buffer[2] == 0x01) + { + printf("0x80 command: Triggering full-power rumble\n"); + // ffb_rumble(1.0f, 1.0f, 500); + } + break; + } + case 0x85: + { // Rumble/vibrate + uint8_t speed = buffer[1]; + uint8_t power = buffer[2]; + + float force = (float)power / 63.0f; // Map power 1-63 to 0.0-1.0 + + int duration = 100; + printf("Triggering rumble: force=%.2f, duration=%dms\n", force, duration); + sdlFfbRumble(force, force, duration); + break; + } + case 0x84: + { // Movement command + uint8_t direction = buffer[1]; + uint8_t value = buffer[2]; + + float left = 0.0f, right = 0.0f; + int duration = 100; // ms + + if (direction == 0x00) + { // right - value from 0x7F (min) to 0x01 (max) + + right = (127.0f - (float)value) / 127.0f; + } + else if (direction == 0x01) + { // left - value goes from 0x00 (min) to 0x7F (max) + + left = (float)value / 127.0f; + } + + printf("Triggering movement rumble: left=%.2f, right=%.2f, duration=%dms\n", left, right, duration); + // sdlFfbRumble(left, right, duration); + break; + } + case 0x86: // Friction + printf("Friction:"); + printf(" Power: %d", buffer[1]); + printf(" / Percentage: %d\n", buffer[2]); + break; + case 0x8: + break; + case 0x87: // move and set target point + printf("Move and set target point\n"); + break; + case 0x88: // move to current target point + printf("Move to current target point\n"); + break; + case 0x8B: // centering strength + printf("Centering strength:"); + printf(" param1: %d", buffer[1]); + printf(" / param2: %d\n", buffer[2]); + break; + case 0xFB: + { // Playback sud package + + uint8_t effect = buffer[2]; + printf("Outrun FFB 0xFB effect: 0x%02x\n", effect); + + if (effect == 0x02) + { + sdlFfbRumble(1.0f, 1.0f, 120); + } + else if (effect == 0x10 || effect == 0x0B || effect == 0x04) + { + sdlFfbRumble(0.0f, 1.0f, 120); + } + else if (effect == 0x00 || effect == 0x1B || effect == 0x14) + { + sdlFfbRumble(1.0f, 0.0f, 120); + } + break; + } + default: + break; + } +} + +void ffb_output(const unsigned char *buffer, size_t count) +{ + /* printf("FFB output: count=%zu, data:", count); + for (size_t i = 0; i < count; ++i) + printf(" 0x%02x", buffer[i]); + printf("\n"); */ + + if (count > 0) + { + if (buffer[0] & 0x40) + { + // printf("GPO: ABC Vibration triggered (bit 6 set)\n"); + // sdlFfbRumble(1.0f, 1.0f, 120); + } + } +} \ No newline at end of file diff --git a/src/lindbergh/forceFeedback.h b/src/lindbergh/forceFeedback.h new file mode 100644 index 0000000..1d7974d --- /dev/null +++ b/src/lindbergh/forceFeedback.h @@ -0,0 +1,12 @@ +#ifndef FFB_H +#define FFB_H + +#include + +void sdlFfbInit(void); +void sdlFfbDhutdown(void); +void sdlFfbRumble(float left, float right, int duration_ms); +void sdlFfbDriveboard(const unsigned char *buffer, size_t count); +void sdlFfbOutput(const unsigned char *buffer, size_t count); + +#endif \ No newline at end of file diff --git a/src/lindbergh/glutHooks.c b/src/lindbergh/glutHooks.c index 7a31f7c..d57376c 100644 --- a/src/lindbergh/glutHooks.c +++ b/src/lindbergh/glutHooks.c @@ -13,6 +13,7 @@ #include "blitStretching.h" #include "border.h" #include "config.h" +#include "crossHair.h" #include "fpsLimiter.h" #include "resolution.h" #include "sdlCalls.h" @@ -41,6 +42,8 @@ extern TTF_Font *font; int penX = 0; int penY = 0; +char windowTitle[512]; + void FGAPIENTRY glutInit(int *argcp, char **argv) { void FGAPIENTRY (*_glutInit)(int *argcp, char **argv) = dlsym(RTLD_NEXT, "glutInit"); @@ -87,6 +90,9 @@ void FGAPIENTRY glutSwapBuffers(void) } } + if ((p1CrossHairInitialized || p2CrossHairInitialized) && gId != GHOST_SQUAD_EVOLUTION) + renderCrosshairs(); + blitStretch(); if (config->borderEnabled) @@ -97,9 +103,11 @@ void FGAPIENTRY glutSwapBuffers(void) if (config->fpsLimiter) frameTiming(); - char windowTitle[512]; sprintf(windowTitle, "%s - FPS: %.2f", sdlGameTitle, calculateFps()); SDL_SetWindowTitle(sdlWindow, windowTitle); + + if (gId == MJ4_REVG || gId == MJ4_EVO) + mj4TouchHolding(); } int FGAPIENTRY glutGet(GLenum type) @@ -336,6 +344,12 @@ void glutBitmapCharacter(void *fontID, int character) int pitch = (glyph->w + 7) / 8; uint8_t *bitmap = malloc(pitch * glyph->h); + if (!bitmap) + { + printf("Failed to allocate memory for bitmap\n"); + SDL_DestroySurface(glyph); + return; + } convertSurfaceTo1Bit(glyph, bitmap, pitch); GLint swbytes, lsbfirst, rowlen, skiprows, skippix, align; diff --git a/src/lindbergh/glxHooks.c b/src/lindbergh/glxHooks.c index 63ce801..99717a2 100644 --- a/src/lindbergh/glxHooks.c +++ b/src/lindbergh/glxHooks.c @@ -13,6 +13,7 @@ #include "blitStretching.h" #include "border.h" #include "config.h" +#include "crossHair.h" #include "flowControl.h" #include "fpsLimiter.h" #include "sdlCalls.h" @@ -30,6 +31,8 @@ double localFps = 0.0; bool createContextCalled = false; int ctxCnt = 0; +extern char windowTitle[512]; + void glXSwapBuffers(Display *dpy, GLXDrawable drawable) { void (*_glXSwapBuffers)(Display *dpy, GLXDrawable drawable) = dlsym(RTLD_NEXT, "glXSwapBuffers"); @@ -39,6 +42,9 @@ void glXSwapBuffers(Display *dpy, GLXDrawable drawable) if (config->borderEnabled) drawGameBorder(config->width, config->height, config->whiteBorderPercentage, config->blackBorderPercentage); + if (p1CrossHairInitialized || p2CrossHairInitialized) + renderCrosshairs(); + blitStretch(); pollEvents(); @@ -48,10 +54,12 @@ void glXSwapBuffers(Display *dpy, GLXDrawable drawable) if (config->fpsLimiter) frameTiming(); - char windowTitle[512]; localFps = calculateFps(); sprintf(windowTitle, "%s - FPS: %.2f", sdlGameTitle, localFps); SDL_SetWindowTitle(sdlWindow, windowTitle); + + if (gId == QUIZ_AXA || gId == QUIZ_AXA_LIVE) + mj4TouchHolding(); } GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct) @@ -81,7 +89,8 @@ GLXFBConfig *glXChooseFBConfig(Display *dpy, int screen, const int *attrib_list, if (__NV_PRIME_RENDER_OFFLOAD == NULL) __NV_PRIME_RENDER_OFFLOAD = " "; - if (gGrp == GROUP_HOD4 || gGrp == GROUP_HOD4_SP || gId == THE_HOUSE_OF_THE_DEAD_EX || gId == TOO_SPICY || gId == QUIZ_AXA || gId == QUIZ_AXA_LIVE) + if (gGrp == GROUP_HOD4 || gGrp == GROUP_HOD4_SP || gId == THE_HOUSE_OF_THE_DEAD_EX || gId == TOO_SPICY || gId == QUIZ_AXA || + gId == QUIZ_AXA_LIVE) { if (strcmp(__GLX_VENDOR_LIBRARY_NAME, "nvidia") == 0 && strcmp(__NV_PRIME_RENDER_OFFLOAD, "1") == 0) { @@ -112,7 +121,6 @@ void glXDestroyGLXPbufferSGIX(Display *dpy, GLXPbufferSGIX pbuf) GLXFBConfigSGIX *glXChooseFBConfigSGIX(Display *dpy, int screen, int *attrib_list, int *nelements) { - int gId = getConfig()->crc32; char *__GLX_VENDOR_LIBRARY_NAME = getenv("__GLX_VENDOR_LIBRARY_NAME"); char *__NV_PRIME_RENDER_OFFLOAD = getenv("__NV_PRIME_RENDER_OFFLOAD"); if (__GLX_VENDOR_LIBRARY_NAME == NULL) diff --git a/src/lindbergh/glxhooks.h b/src/lindbergh/glxhooks.h deleted file mode 100644 index f2061a1..0000000 --- a/src/lindbergh/glxhooks.h +++ /dev/null @@ -1,4 +0,0 @@ -#include - -void glXToSDLSwapBuffers(Display *dpy, GLXDrawable drawable); -int glxSDLmyCreateWindow(int *param1); \ No newline at end of file diff --git a/src/lindbergh/gpuVendor.c b/src/lindbergh/gpuVendor.c index 5ecbb3e..86dbe8c 100644 --- a/src/lindbergh/gpuVendor.c +++ b/src/lindbergh/gpuVendor.c @@ -44,10 +44,12 @@ int getGPUVendorID() if (!config->GPUVendorString) { fprintf(stderr, "Error: Could not get renderer string\n"); + SDL_GL_DestroyContext(context); SDL_DestroyWindow(window); return ERROR_GPU; } gettingGPUVendor = false; + SDL_GL_DestroyContext(context); SDL_DestroyWindow(window); if (strstr(config->GPUVendorString, "NVIDIA") != NULL) diff --git a/src/lindbergh/hook.c b/src/lindbergh/hook.c index 56306ac..338c1b5 100644 --- a/src/lindbergh/hook.c +++ b/src/lindbergh/hook.c @@ -64,6 +64,8 @@ extern bool cachedShaderFilesLoaded; extern char vf5StageNameAbbr[5]; bool phShowCursorInGame = false; +extern bool mj4ResponseReady; + extern FpsLimit fpsLimit; Controllers controllers = {0}; extern SDLControllers sdlJoysticks; @@ -181,6 +183,31 @@ uint32_t getCrc32(const char *s, ssize_t n) return ~crc; } +int setConfigFolder() +{ + char *home = getenv("HOME"); + if (!home) + { + fprintf(stderr, "HOME environment variable not set.\n"); + return 1; + } + + char config_path[PATH_MAX]; + snprintf(config_path, sizeof(config_path), "%s/.config/lindbergh-loader", home); + configFolder = strdup(config_path); + + char config_base[PATH_MAX]; + snprintf(config_base, sizeof(config_base), "%s/.config", home); + mkdir(config_base, 0755); + + if (mkdir(config_path, 0755) == -1) + { + if (errno != EEXIST) + return 1; + } + return 0; +} + /** * @brief Trims leading and trailing whitespace and quotes from a string. * @@ -282,10 +309,13 @@ void __attribute__((constructor)) hook_init() // Implement SIGSEGV handler struct sigaction act; + memset(&act, 0, sizeof(act)); act.sa_sigaction = handleSegfault; act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &act, NULL); + // setConfigFolder(); + char *envPath = getenv("LINDBERGH_CONFIG_PATH"); initConfig(envPath); @@ -343,6 +373,11 @@ void __attribute__((constructor)) hook_init() } envPath = getenv("LINDBERGH_CONTROLS_PATH"); + if (envPath == NULL) + { + envPath = ""; + } + if (initSdlInput(envPath) != 0) exit(1); @@ -409,6 +444,20 @@ DIR *opendir(const char *dirname) return _opendir(dirname + 1); } + if (strcmp(getConfig()->idCardFolder, "") != 0 && strcmp(dirname, ".") == 0) + { + if (gGrp == GROUP_ID5 || gGrp == GROUP_ID4_EXP || gGrp == GROUP_ID4_JAP) + { + char *newDirName = getConfig()->idCardFolder; + char lastChar = newDirName[strlen(newDirName) - 1]; + if (lastChar == '/') + { + newDirName[strlen(newDirName) - 1] = '\0'; + } + return _opendir(newDirName); + } + } + // Fix for Outrun high scores if (strcmp(dirname, "/home/disk1/rankingdata") == 0 && (gGrp == GROUP_OUTRUN || gGrp == GROUP_OUTRUN_TEST)) { @@ -763,6 +812,28 @@ FILE *fopen(const char *restrict pathname, const char *restrict mode) return fopen(pathname + 1, mode); } + if (strcmp(getConfig()->idCardFolder, "") != 0) + { + if (gGrp == GROUP_ID5 || gGrp == GROUP_ID4_EXP || gGrp == GROUP_ID4_JAP) + { + if (strncmp(pathname, "InidCrd", 7) == 0 || strncmp(pathname, "InidCard", 8) == 0) + { + char *fmt = "%s%s"; + char newPathname[MAX_PATH_LENGTH]; + int newPathnameLen = strlen(getConfig()->idCardFolder) + strlen(pathname) + 1; + char lastChar = getConfig()->idCardFolder[strlen(getConfig()->idCardFolder) - 1]; + if (lastChar != '/') + { + fmt = "%s/%s"; + newPathnameLen++; + } + + snprintf(newPathname, newPathnameLen, fmt, getConfig()->idCardFolder, pathname); + return fopen(newPathname, mode); + } + } + } + if (gId == PRIMEVAL_HUNT) { if (strstr(pathname, "/data/lua/texture/start_stage") != NULL) @@ -1041,6 +1112,11 @@ ssize_t read(int fd, void *buf, size_t count) phRead(fd, buf, count); return 1; } + if ((gId == MJ4_EVO || gId == MJ4_REVG || gId == QUIZ_AXA || gId == QUIZ_AXA_LIVE) && getConfig()->emulateTouchscreen == 1 && + mj4ResponseReady) + { + return mj4ReadTouchPacket(buf, count); + } return -1; } @@ -1217,6 +1293,11 @@ ssize_t write(int fd, const void *buf, size_t count) return driveboardWrite(fd, buf, count); } + if (fd == hooks[SERIAL1] && (gId == MJ4_EVO || gId == MJ4_REVG || gId == QUIZ_AXA || gId == QUIZ_AXA_LIVE)) + { + return mj4WriteTouchPacket(buf, count); + } + if ((fd == hooks[SERIAL0] || fd == hooks[SERIAL1]) && getConfig()->emulateHW210CardReader) { return cardReaderWrite(fd, buf, count); @@ -1255,16 +1336,20 @@ int ioctl(int fd, unsigned long int request, ...) if (fd == hooks[SERIAL0] || fd == hooks[SERIAL1]) { - if (request == 0x541b && (gId == R_TUNED) && fd == hooks[SERIAL1]) + if (request == 0x541b && (gId == R_TUNED || gId == MJ4_REVG || gId == MJ4_EVO) && fd == hooks[SERIAL1]) { uint8_t d = 1; memcpy(argp, &d, sizeof(uint8_t)); } + else if (getConfig()->emulateDriveboard) + { + return driveBoardioctl(fd, request, argp); + } return 0; } // Replace "eth0" with your interface name set in the config file. - if ((gGrp == GROUP_ID4_EXP || gId == INITIALD_5_EXP_20 || gId == INITIALD_5_EXP_20A ) && + if ((gGrp == GROUP_ID4_EXP || gId == INITIALD_5_EXP_20 || gId == INITIALD_5_EXP_20A) && (request == SIOCGIFFLAGS || request == SIOCGIFADDR) && getConfig()->enableNetworkPatches && strcmp(getConfig()->nicName, "") != 0) { struct ifreq *ifr = (struct ifreq *)argp; @@ -1389,14 +1474,19 @@ int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set int (*_select)(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout) = dlsym(RTLD_NEXT, "select"); - if (readfds != NULL && FD_ISSET(hooks[BASEBOARD], readfds)) - { - return baseboardSelect(nfds, readfds, writefds, exceptfds, timeout); - } + int baseboardFd = hooks[BASEBOARD]; - if (writefds != NULL && FD_ISSET(hooks[BASEBOARD], writefds)) + if (baseboardFd >= 0 && baseboardFd < nfds) { - return baseboardSelect(nfds, readfds, writefds, exceptfds, timeout); + if (readfds != NULL && FD_ISSET(hooks[BASEBOARD], readfds)) + { + return baseboardSelect(nfds, readfds, writefds, exceptfds, timeout); + } + + if (writefds != NULL && FD_ISSET(hooks[BASEBOARD], writefds)) + { + return baseboardSelect(nfds, readfds, writefds, exceptfds, timeout); + } } if ((getConfig()->emulateHW210CardReader == 1 || getConfig()->emulateDriveboard == 1) && @@ -1660,4 +1750,4 @@ struct tm *gmtime_r(const time_t *timep, struct tm *result) return res; } return _gmtime_r(timep, result); -} \ No newline at end of file +} diff --git a/src/lindbergh/iniParser.c b/src/lindbergh/iniParser.c index f01dbbc..a3f1bd6 100644 --- a/src/lindbergh/iniParser.c +++ b/src/lindbergh/iniParser.c @@ -44,7 +44,10 @@ IniConfig *iniLoad(const char *filename) } IniConfig *config = calloc(1, sizeof(IniConfig)); if (!config) + { + fclose(file); return NULL; + } char line[1024]; IniSection *currentSection = NULL; while (fgets(line, sizeof(line), file)) @@ -54,7 +57,14 @@ IniConfig *iniLoad(const char *filename) continue; if (trimmedLine[0] == '[' && trimmedLine[strlen(trimmedLine) - 1] == ']') { - config->sections = realloc(config->sections, (config->numSections + 1) * sizeof(IniSection)); + IniSection *newSections = realloc(config->sections, (config->numSections + 1) * sizeof(IniSection)); + if (!newSections) + { + iniFree(config); + fclose(file); + return NULL; + } + config->sections = newSections; currentSection = &config->sections[config->numSections++]; currentSection->numPairs = 0; currentSection->pairs = NULL; @@ -70,7 +80,14 @@ IniConfig *iniLoad(const char *filename) value++; key = trimString(key); value = trimString(value); - currentSection->pairs = realloc(currentSection->pairs, (currentSection->numPairs + 1) * sizeof(IniKeyValuePair)); + IniKeyValuePair *newPairs = realloc(currentSection->pairs, (currentSection->numPairs + 1) * sizeof(IniKeyValuePair)); + if (!newPairs) + { + iniFree(config); + fclose(file); + return NULL; + } + currentSection->pairs = newPairs; IniKeyValuePair *pair = ¤tSection->pairs[currentSection->numPairs++]; pair->key = strdup(key); pair->value = strdup(value); @@ -142,7 +159,10 @@ bool iniSetValue(IniConfig *config, const char *sectionName, const char *key, co IniSection *section = iniGetSection(config, sectionName); if (!section) { - config->sections = realloc(config->sections, (config->numSections + 1) * sizeof(IniSection)); + IniSection *newSections = realloc(config->sections, (config->numSections + 1) * sizeof(IniSection)); + if (!newSections) + return false; + config->sections = newSections; section = &config->sections[config->numSections++]; section->name = strdup(sectionName); section->pairs = NULL; @@ -162,7 +182,10 @@ bool iniSetValue(IniConfig *config, const char *sectionName, const char *key, co } // Key not found, add a new key-value pair. - section->pairs = realloc(section->pairs, (section->numPairs + 1) * sizeof(IniKeyValuePair)); + IniKeyValuePair *newPairs = realloc(section->pairs, (section->numPairs + 1) * sizeof(IniKeyValuePair)); + if (!newPairs) + return false; + section->pairs = newPairs; IniKeyValuePair *pair = §ion->pairs[section->numPairs++]; pair->key = strdup(key); pair->value = strdup(value); diff --git a/src/lindbergh/jvs.c b/src/lindbergh/jvs.c index 24c6a36..6ffac75 100644 --- a/src/lindbergh/jvs.c +++ b/src/lindbergh/jvs.c @@ -488,7 +488,7 @@ JVSStatus processPacket(int *packetSize) ////printf("CMD_CONVEY_ID\n"); size = 1; outputPacket.data[outputPacket.length++] = REPORT_SUCCESS; - char idData[100]; + char idData[100] = {0}; for (int i = 1; i < 100; i++) { idData[i] = (char)inputPacket.data[index + i]; diff --git a/src/lindbergh/lindbergh.c b/src/lindbergh/lindbergh.c index 9a1b1c5..f41ad98 100644 --- a/src/lindbergh/lindbergh.c +++ b/src/lindbergh/lindbergh.c @@ -345,7 +345,8 @@ void setEnvironmentVariables(const char *ldLibPath, const char *originalDir, con snprintf(newLdLibPath, sizeof(newLdLibPath), "%s", currentLibraryPath); } - char *tmpLibPath = dirname(strdup(ldLibPath)); + char *ldLibPathCopy = strdup(ldLibPath); + char *tmpLibPath = dirname(ldLibPathCopy); if (strcmp(tmpLibPath, ".") != 0) { strcat(newLdLibPath, ":"); @@ -363,6 +364,8 @@ void setEnvironmentVariables(const char *ldLibPath, const char *originalDir, con strcat(newLdLibPath, ":"); strcat(newLdLibPath, gameDir); } + + free(ldLibPathCopy); strcat(newLdLibPath, ":.:lib:../lib"); @@ -869,6 +872,7 @@ int main(int argc, char *argv[]) if (strcmp(libPath, "") == 0) { log_error("Error: %s not found in known locations.\n", PRELOAD_FILE_NAME); + free(libPath); return 1; } @@ -903,6 +907,9 @@ int main(int argc, char *argv[]) setEnvironmentVariables(libPath, originalDir, targetedGameDir, zink, nvidia, extConfigPath, extControlsPath, extControlsDbPath, libOpenal); + free(libPath); + if (libOpenal) free(libOpenal); + if (gdb) { char temp[128]; diff --git a/src/lindbergh/output.c b/src/lindbergh/output.c new file mode 100644 index 0000000..ff3430d --- /dev/null +++ b/src/lindbergh/output.c @@ -0,0 +1,34 @@ + + +// Function to send a command +void send_command(int fd, char command) +{ + // write(fd, &command, 1); +} + +void processGPOpacket(unsigned char data) +{ + for (int i = 7; i >= 4; i--) + { + int bit = (data >> i) & 1; + if (bit != 0) + { + // printf("bit = %d\n", i); + } + // printf("Bit %d: %d (0x%x)\n", i, bit, bit << i); + // if (i == 4) + // { + // printf("%c - bit: %d\n", bit ? 'a' : 'A', bit); + // send_command(fdHW210, bit ? 'a' : 'A'); + // usleep(500); + // } + // else if (i == 6) + // { + // printf("%c - bit: %d\n", bit ? 'b' : 'B', bit); + // send_command(fdHW210, bit ? 'b' : 'B'); + // usleep(500); + // } + } +} + + diff --git a/src/lindbergh/output.h b/src/lindbergh/output.h new file mode 100644 index 0000000..8249f52 --- /dev/null +++ b/src/lindbergh/output.h @@ -0,0 +1,2 @@ +void send_command(int fd, char command); +void processGPOpacket(unsigned char data); \ No newline at end of file diff --git a/src/lindbergh/patch.c b/src/lindbergh/patch.c index d22379e..71d23f4 100644 --- a/src/lindbergh/patch.c +++ b/src/lindbergh/patch.c @@ -11,6 +11,7 @@ #include #include +#include "cardReader.h" #include "config.h" #include "resolution.h" #include "securityBoard.h" @@ -31,6 +32,8 @@ extern void *customCursor; extern void *touchCursor; extern void *blankCursor; +extern void (*idWriteFileHeader)(void *); + int amDongleInit() { return 0; @@ -212,6 +215,21 @@ int patchedPuts(char *s) return ret; } +extern bool gTriggerInsertKey; +int idCardSize = 0; +bool idTwoDigitsCardCount = false; + +bool checkTrgOn(int param_1, long param_2) +{ + if (param_2 == 0x1000 && (gTriggerInsertKey || getConfig()->idCardFileAutoload)) + { + if (idCardFileExists(getConfig()->idCardFolder, idCardSize, idTwoDigitsCardCount)) + return true; + return false; + } + return false; +} + int initPatch() { EmulatorConfig *config = getConfig(); @@ -576,6 +594,10 @@ int initPatch() // Set IPs hummerSetIPs(0x09c2fe90); + + // Forces FFB command output + if (getConfig()->emulateDriveboard) + detourFunction(0x080cffb4, stubRetZero); } break; case HUMMER_EXTREME_MDX: // DVP-0083 @@ -641,6 +663,10 @@ int initPatch() // Set IPs hummerSetIPs(0x09c435b0); + + // Forces FFB command output + if (getConfig()->emulateDriveboard) + detourFunction(0x080d1348, stubRetZero); } break; case HUMMER_SDLX: // DVP-0057 @@ -699,6 +725,10 @@ int initPatch() // Set IPs hummerSetIPs(0x09b30258); + + // Forces FFB command output + if (getConfig()->emulateDriveboard) + detourFunction(0x082cdd1a, stubRetZero); } break; case INITIALD_4_EXP_REVB: @@ -767,6 +797,15 @@ int initPatch() setVariable(0x08235a5d, ipSeat1); setVariable(0x08235a55, ipSeat2); } + + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x0820716e, "48d479"); // RealCard --> FileCard + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1824; + idTwoDigitsCardCount = false; + } } break; case INITIALD_4_EXP_REVC: @@ -835,6 +874,16 @@ int initPatch() setVariable(0x08235b2d, ipSeat1); setVariable(0x08235b25, ipSeat2); } + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x0820722e, "c8d179"); // RealCard --> FileCard + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1824; + idTwoDigitsCardCount = false; + } } break; case INITIALD_4_EXP_REVD: @@ -903,6 +952,16 @@ int initPatch() setVariable(0x08236c5d, ipSeat1); setVariable(0x08236c55, ipSeat2); } + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x082082e8, "88fa79"); // RealCard --> FileCard + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1824; + idTwoDigitsCardCount = false; + } } break; case INITIALD_4_REVA: @@ -959,6 +1018,16 @@ int initPatch() } patchMemoryFromString(0x08548cb3, "31C090"); // cgCreateProgram args argument to 0; detourFunction(0x0825637a, stubRetOne); // isExistNewerSource (forces shader recompilation) + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x081f1e27, "28c777"); // RealCard --> FileCard + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1824; + idTwoDigitsCardCount = true; + } } break; case INITIALD_4_REVB: @@ -1015,6 +1084,16 @@ int initPatch() } patchMemoryFromString(0x08548ef3, "31C090"); // cgCreateProgram args argument to 0; detourFunction(0x0825653a, stubRetOne); // isExistNewerSource (forces shader recompilation) + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x081f1f67, "e8c977"); // RealCard --> FileCard + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1824; + idTwoDigitsCardCount = true; + } } break; case INITIALD_4_REVC: @@ -1071,6 +1150,16 @@ int initPatch() } patchMemoryFromString(0x08551763, "31C090"); // cgCreateProgram args argument to 0; detourFunction(0x08256ff4, stubRetOne); // isExistNewerSource (forces shader recompilation) + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x081f27be, "485278"); // RealCard --> FileCard + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1824; + idTwoDigitsCardCount = false; + } } break; case INITIALD_4_REVD: @@ -1127,6 +1216,16 @@ int initPatch() } patchMemoryFromString(0x0854ee03, "31C090"); // cgCreateProgram args argument to 0; detourFunction(0x08257470, stubRetOne); // isExistNewerSource (forces shader recompilation) + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x081f2c2e, "e82878"); // RealCard --> FileCard + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1824; + idTwoDigitsCardCount = false; + } } break; case INITIALD_4_REVG: @@ -1183,6 +1282,17 @@ int initPatch() } patchMemoryFromString(0x08588f73, "31C090"); // cgCreateProgram args argument to 0; detourFunction(0x08271cec, stubRetOne); // isExistNewerSource (forces shader recompilation) + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x0820a911, "a8a07c"); // RealCard --> FileCard + patchMemoryFromString(0x081909a3, "eb"); // Fixes crash in findCardFiles + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1828; + idTwoDigitsCardCount = false; + } } break; case INITIALD_5_EXP: @@ -1241,6 +1351,17 @@ int initPatch() detourFunction(0x08389544, stubRetOne); // isExistNewerSource detourFunction(0x0807b370, gl_XGetProcAddressARB); patchMemoryFromString(0x08744f2e, "00"); // Fix cutscenes + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x0828573a, "489c9b"); // RealCard --> FileCard + patchMemoryFromString(0x081cf8c3, "eb"); // Fixes crash in findCardFiles + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1354; + idTwoDigitsCardCount = false; + } } break; case INITIALD_5_EXP_20: @@ -1319,6 +1440,17 @@ int initPatch() setVariable(0x0833e2e3, ipSeat1); setVariable(0x0833e2db, ipSeat2); } + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x082923ad, "48519e"); // RealCard --> FileCard 089e5144 + patchMemoryFromString(0x081d6ae3, "eb"); // Fixes crash in findCardFiles + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1354; + idTwoDigitsCardCount = false; + } if (getConfig()->id5ChineseLanguage) { securityBoardSetDipSwitch(1, 1); @@ -1402,6 +1534,17 @@ int initPatch() setVariable(0x0833e473, ipSeat1); setVariable(0x0833e46b, ipSeat2); } + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x0829254d, "88539e"); // RealCard --> FileCard 089e5144 + patchMemoryFromString(0x081d6c83, "eb"); // Fixes crash in findCardFiles + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1354; + idTwoDigitsCardCount = false; + } if (getConfig()->id5ChineseLanguage) { securityBoardSetDipSwitch(1, 1); @@ -1465,6 +1608,17 @@ int initPatch() detourFunction(0x08388cb4, stubRetOne); // isExistNewerSource detourFunction(0x0807b370, gl_XGetProcAddressARB); patchMemoryFromString(0x0874433e, "00"); // Fix cutscenes + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x0828471a, "889b9b"); // RealCard --> FileCard 089e5144 + patchMemoryFromString(0x081ce9a3, "eb"); // Fixes crash in findCardFiles + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1354; + idTwoDigitsCardCount = false; + } } break; case INITIALD_5_JAP_REVC: // ID5 - DVP-0070C @@ -1523,6 +1677,17 @@ int initPatch() detourFunction(0x08389594, stubRetOne); // isExistNewerSource detourFunction(0x0807b370, gl_XGetProcAddressARB); patchMemoryFromString(0x08744d5e, "00"); // Fix cutscenes + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x0828575a, "a8a5"); // RealCard --> FileCard 089e5144 + patchMemoryFromString(0x081cf903, "eb"); // Fixes crash in findCardFiles + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1354; + idTwoDigitsCardCount = false; + } } break; case INITIALD_5_JAP_REVF: // ID5 - DVP-0070F @@ -1588,6 +1753,17 @@ int initPatch() detourFunction(0x08397aa4, stubRetOne); // isExistNewerSource detourFunction(0x0807b3bc, gl_XGetProcAddressARB); patchMemoryFromString(0x0875e1ae, "00"); // Fix cutscenes + + // Patch for Card Reader to use files instead + if (getConfig()->emulateIDCardReader) + { + patchMemoryFromString(0x08292860, "28f29d"); // RealCard --> FileCard 089e5144 + patchMemoryFromString(0x081d7023, "eb"); // Fixes crash in findCardFiles + patchMemoryFromString((size_t)findStaticFnAddr("_ZN11cFileCardIF9emulationEv") + 0x21, "74"); + detourFunction((size_t)findStaticFnAddr("_ZN11_sInterface10checkTrgOnENS_11PORT_NUMBEREm"), checkTrgOn); + idCardSize = 1354; + idTwoDigitsCardCount = false; + } } break; case LETS_GO_JUNGLE: diff --git a/src/lindbergh/resolution.c b/src/lindbergh/resolution.c index a296bf1..e4b13c3 100644 --- a/src/lindbergh/resolution.c +++ b/src/lindbergh/resolution.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "config.h" @@ -68,6 +69,8 @@ static void (*idDrawBallonOri)(void *, void *, float, float, float, float, float float newHeightLGJ, newWidth2LGJ, newWidthHLGJ, newWidthSLGJ; +extern int textureIdIdxAdjust; + bool myEnableScaling = false; int hummerRespatch() @@ -188,6 +191,15 @@ void glViewport(GLint x, GLint y, GLsizei width, GLsizei height) height = gHeight; } } + // Patches the Resolution even tho is not needed to fix patched ELFs. + else if (gWidth == 800 && gHeight == 480) + { + if (width == 1280 && height == 768) + { + width = 800; + height = 480; + } + } } else if (gId == SEGA_RACE_TV) { @@ -284,9 +296,9 @@ void glTexImage2D(unsigned int target, int level, int internalformat, int width, void (*_glTexImage2D)(unsigned int target, int level, int internalformat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels) = dlsym(RTLD_NEXT, "glTexImage2D"); - if (gWidth != 800 || gHeight != 480) + if (gGrp == GROUP_OUTRUN) { - if (gGrp == GROUP_OUTRUN) + if (gWidth != 800 || gHeight != 480) { void *addr = __builtin_return_address(0); if ((width >= 800) && (width != 1024) && (addr != (void *)0x80d78d5) && (addr != (void *)0x080d7941)) @@ -295,6 +307,15 @@ void glTexImage2D(unsigned int target, int level, int internalformat, int width, height = gHeight; } } + // Patches the Resolution even tho is not needed to fix patched ELFs. + else if (gWidth == 800 && gHeight == 480) + { + if (width == 1280 && height == 768) + { + width = 800; + height = 480; + } + } } _glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels); } @@ -347,7 +368,9 @@ void myGlBindTexture(GLenum target, GLuint texture) { curTarget = target; curTextureID = texture; - curTextureID--; + curTextureID--; // We adjust for the added blitting texture + + curTextureID -= textureIdIdxAdjust; // We also adjust for the crosshair textures. void (*_glBindTexture)(GLenum, GLuint) = dlsym(RTLD_NEXT, "glBindTexture"); _glBindTexture(target, texture); @@ -1127,7 +1150,7 @@ void glVertex3fHOD4(GLfloat x, GLfloat y, GLfloat z) else if (z == -1.510000f) { // logo 2d elemtents target - if (curTextureID != 260) // target boss fight // Added 2 because of the crosshairs. + if (curTextureID != 260) // target boss fight { x += OffsetX; y -= OffsetY; @@ -1278,6 +1301,77 @@ void glVertex3f2SP(GLfloat x, GLfloat y, GLfloat z) if (z == -1.000000f) { // 3d + // printf("All x values: %.10f\n", x); + // printf("All y values: %.10f\n", y); + } + + else if (z == -1.3099999428f) + { + + if ((x == 0.1838379353f && y == -0.0376717076f) || (x == -0.1838379353f && y == 0.0376717076f) || + (x == 0.1838379353f && y == 0.0376717076f) || (x == -0.1838379353f && y == -0.0376717076f) || + (x == -0.1819543540f && y == -0.0380484276f) || (x == 0.1819543540f && y == -0.0380484276f) || + (x == 0.1819543540f && y == -0.1126384139f) || (x == -0.1819543540f && y == -0.1126384139f)) + { + // if (curTextureID == 499 || curTextureID == 500){ //500 reload - 499 hide and reload + x *= scaleX; + y *= scaleY; + } + else + { + x += AdjOffsetX; + y -= AdjOffsetY; + x *= scaleX; + y *= scaleY; + } + } + + else if (z == -1.5099999905f) + { + + if ((curTextureID >= 479 && curTextureID < 489) || curTextureID == 504 || curTextureID == 505 || curTextureID == 506) + { // Enemy portraits in the HUD + if (x < 0.0f) + { + x += AdjOffsetX; + y -= AdjOffsetY; + x *= scaleX; + y *= scaleY; + } + else + { + x -= OffsetX; + y -= OffsetY; + x *= scaleX; + y *= scaleY; + } + } + else if (curTextureID == 497) // right + { + x -= OffsetX; + y -= OffsetY; + x *= scaleX; + y *= scaleY; + } + else if (curTextureID == 498) + { // deadend + } + else + { + x += AdjOffsetX; + y -= AdjOffsetY; + x *= scaleX; + y *= scaleY; + } + } + + else if (z == -1.2100000381f) + { + + x += AdjOffsetX; + y -= AdjOffsetY; + x *= scaleX; + y *= scaleY; } else if (z == -3.000000f) { // videos @@ -1291,7 +1385,7 @@ void glVertex3f2SP(GLfloat x, GLfloat y, GLfloat z) y *= scaleY; } else if (z == -1.510000f) - { // logo - coin/start - 2D + { // logo - coin/start - 2D if (curTextureID == 497) // right { x -= OffsetX; @@ -2485,24 +2579,11 @@ int initResolutionPatches() break; case OUTRUN_2_SP_SDX_REVA: { - if (gWidth <= 800 && gHeight <= 480) - break; - // If resolution is not the native of the game this patch kind of fix the Sun when the LensGlare effect is - // shown. - if ((gWidth > 800) && (gHeight > 480)) - { - patchMemoryFromString(0x080e8e06, "9090909090"); // removes a call to a light function - patchMemoryFromString(0x080e8e17, "9090909090"); // removes a call to a light function - if (!getConfig()->outrunLensGlareEnabled) - { - detourFunction(0x080e8ac8, stubReturn); // Completely disables the lens glare effect - } - } setVariable(0x080b913a, 0x44200000); setVariable(0x081dada8, 0x44200000); setVariable(0x081dadb0, 0x44200000); - // Clean patched elf floating around + // Fixes patched ELFs patchMemoryFromString(0x080b912e, "f043"); patchMemoryFromString(0x080f39de, "c0ef43"); patchMemoryFromString(0x080f3aa6, "2048"); @@ -2518,6 +2599,20 @@ int initResolutionPatches() setVariable(0x081dadbc, 0x43f00000); patchMemoryFromString(0x081e7b3e, "7e"); patchMemoryFromString(0x081e7b46, "89"); + + if (gWidth <= 800 && gHeight <= 480) + break; + // If resolution is not the native of the game this patch kind of fix the Sun when the LensGlare effect is + // shown. + if ((gWidth > 800) && (gHeight > 480)) + { + patchMemoryFromString(0x080e8e06, "9090909090"); // removes a call to a light function + patchMemoryFromString(0x080e8e17, "9090909090"); // removes a call to a light function + if (!getConfig()->outrunLensGlareEnabled) + { + detourFunction(0x080e8ac8, stubReturn); // Completely disables the lens glare effect + } + } } break; case OUTRUN_2_SP_SDX_REVA_TEST: @@ -2571,6 +2666,7 @@ int initResolutionPatches() { case 0: { + if (sdlDisplayId) SDL_free(sdlDisplayId); return 0; } case 1: // No touch screen @@ -2629,17 +2725,16 @@ int initResolutionPatches() } break; } - setVariable(0x0805b104, phX); // X 1st screen - setVariable(0x0805b0fd, phY); // Y 1st screen - setVariable(0x0805b0f5, phW); // Width 1st screen - setVariable(0x0805b0ed, phH); // Height 1st screen - setVariable(0x0805afa2, phX2); // X 2nd screen - setVariable(0x0805af9b, phY2); // Y 2st screen - setVariable(0x0805af93, phW2); // Width 2nd screen - setVariable(0x0805af8b, phH2); // Height 2nd screen + setVariable(0x0805b104, phX); // X 1st screen + setVariable(0x0805b0fd, phY); // Y 1st screen + setVariable(0x0805b0f5, phW); // Width 1st screen + setVariable(0x0805b0ed, phH); // Height 1st screen + setVariable(0x0805afa2, phX2); // X 2nd screen + setVariable(0x0805af9b, phY2); // Y 2st screen + setVariable(0x0805af93, phW2); // Width 2nd screen + setVariable(0x0805af8b, phH2); // Height 2nd screen // patchMemoryFromString(0x08052cc4, "0000"); // swap screens detourFunction(0x0804c628, glClearColorPH); - // Test Menu if (isTestMode()) @@ -2756,6 +2851,7 @@ int initResolutionPatches() { if (getConfig()->boostRenderRes) setVariable(0x084c9dbc, 1280); + if (sdlDisplayId) SDL_free(sdlDisplayId); return 0; } else if (gWidth == 640 && gHeight == 480) @@ -2785,6 +2881,7 @@ int initResolutionPatches() { if (getConfig()->boostRenderRes) setVariable(0x084c3a9c, 1280); + if (sdlDisplayId) SDL_free(sdlDisplayId); return 0; } else if (gWidth == 640 && gHeight == 480) @@ -2814,6 +2911,7 @@ int initResolutionPatches() { if (getConfig()->boostRenderRes) setVariable(0x084c3a9c, 1280); + if (sdlDisplayId) SDL_free(sdlDisplayId); return 0; } else if (gWidth == 640 && gHeight == 480) @@ -2927,8 +3025,10 @@ int initResolutionPatches() break; case VIRTUA_FIGHTER_5: { - if ((gWidth == 640 && gHeight == 480) || (gWidth == 1280 && gHeight == 768)) + if ((gWidth == 640 && gHeight == 480) || (gWidth == 1280 && gHeight == 768)) { + if (sdlDisplayId) SDL_free(sdlDisplayId); return 0; + } patchMemoryFromString(0x08054057, "b80a000000"); // Skips resolution set by the Dip Switches. setVariable(0x0847cf58, gWidth); setVariable(0x0847cf5c, gHeight); @@ -3152,5 +3252,7 @@ int initResolutionPatches() default: break; } + if (sdlDisplayId) + SDL_free(sdlDisplayId); return 0; } diff --git a/src/lindbergh/sdlCalls.c b/src/lindbergh/sdlCalls.c index dad6601..2c3f3dd 100644 --- a/src/lindbergh/sdlCalls.c +++ b/src/lindbergh/sdlCalls.c @@ -15,12 +15,14 @@ #include "blitStretching.h" #include "config.h" +#include "crossHair.h" #include "customCursor.h" #include "sdlInput.h" #include "jvs.h" #include "resources/LiberationMono-Regular.h" #include "log.h" #include "sdlCalls.h" +#include "wiimoteEvdev.h" extern uint32_t gId; extern int gGrp; @@ -100,6 +102,7 @@ void startSDL(int *argcp, char **argv) log_warn("More than 1 display detected, will use the first one.\n"); const SDL_DisplayMode *displayMode = SDL_GetCurrentDisplayMode(sdlDisplayId[0]); + SDL_free(sdlDisplayId); if (displayMode == NULL) { log_error("SDL_GetCurrentDisplayMode Error: %s\n", SDL_GetError()); @@ -188,7 +191,7 @@ void startSDL(int *argcp, char **argv) initBlitting(); - if (gId != PRIMEVAL_HUNT) + if (gId != PRIMEVAL_HUNT && gGrp != GROUP_LGJ) SDL_SetWindowResizable(sdlWindow, true); SDL_ShowWindow(sdlWindow); @@ -203,7 +206,6 @@ void startSDL(int *argcp, char **argv) running = 0; glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - SDL_GL_SwapWindow(sdlWindow); } @@ -215,6 +217,11 @@ void startSDL(int *argcp, char **argv) if (customCursor) setCursor(customCursor); + if (getConfig()->enableCrosshairs && + (gGrp == GROUP_HOD4 || gGrp == GROUP_HOD4_TEST || gGrp == GROUP_HOD4_SP || gGrp == GROUP_HOD4_SP_TEST || gGrp == GROUP_RAMBO || + gId == GHOST_SQUAD_EVOLUTION || gId == PRIMEVAL_HUNT)) + initCrossHairs(); + if ((gId == MJ4_REVG || gId == MJ4_EVO || gId == QUIZ_AXA || gId == QUIZ_AXA_LIVE) && strcmp(getConfig()->touchCursor, "") != 0 && getConfig()->emulateTouchscreen) setCursor(touchCursor); @@ -228,7 +235,33 @@ void sdlQuit() usleep(50000); setSwitch(SYSTEM, BUTTON_TEST, 0); usleep(50000); - SDL_DestroyWindow(sdlWindow); + + if (TTF_WasInit()) + { + if (font) + { + TTF_CloseFont(font); + font = NULL; + } + TTF_Quit(); + } + if (fontRenderer) + { + SDL_DestroyRenderer(fontRenderer); + fontRenderer = NULL; + } + + if (sdlContext) + { + SDL_GL_DestroyContext(sdlContext); + sdlContext = NULL; + } + + if (sdlWindow) + { + SDL_DestroyWindow(sdlWindow); + sdlWindow = NULL; + } SDL_Quit(); exit(0); } @@ -238,6 +271,9 @@ void pollEvents() SDL_Event event; while (SDL_PollEvent(&event)) { + if (event.type == SDL_WIIMOTION_EVENT) + processSdlEvent(&event); + switch (event.type) { case SDL_EVENT_KEY_DOWN: diff --git a/src/lindbergh/sdlInput.c b/src/lindbergh/sdlInput.c index 16b4734..bec3df4 100644 --- a/src/lindbergh/sdlInput.c +++ b/src/lindbergh/sdlInput.c @@ -16,12 +16,15 @@ #include "blitStretching.h" #include "config.h" +#include "crossHair.h" +#include "forceFeedback.h" #include "gameControllerMappings.h" #include "iniParser.h" #include "sdlInput.h" #include "jvs.h" #include "log.h" #include "touchScreen.h" +#include "wiimoteEvdev.h" // --- GLOBAL STATE AND MAPPINGS --- ActionState gActionStates[MAX_ENTITIES][NUM_LOGICAL_ACTIONS] = {0}; @@ -51,6 +54,7 @@ ControlBinding gJoyButtonBindings[MAX_JOYSTICKS][MAX_JOY_BUTTONS] = {0}; BindingPair gControllerAxisBindings[MAX_JOYSTICKS][SDL_GAMEPAD_AXIS_COUNT] = {0}; ControlBinding gControllerButtonBindings[MAX_JOYSTICKS][SDL_GAMEPAD_BUTTON_COUNT] = {0}; HatBinding gJoyHatBindings[MAX_JOYSTICKS][MAX_JOY_HATS] = {0}; +bool gTriggerInsertKey = false; extern uint32_t gId; extern int gGrp; @@ -66,8 +70,14 @@ extern Dest dest; extern int phX, phY, phW, phH; int phIsDragging = 0; +extern bool mj4MousePressed; +int mj4MouseX = 0; +int mj4MouseY = 0; extern int drawableW; extern int drawableH; +extern bool mj4TouchedInsideScreen; + +extern bool gTriggerInsertKey; int jvsAnalogueMaxValue; int jvsAnalogueCenterValue; @@ -368,8 +378,17 @@ int initSdlInput(char *controlsPath) } } } + SDL_free(joystics); } + if (sdlJoysticks.joysticksCount > 0) + { + sdlFfbInit(); + } + + findAndOpenWiiMotes(&sdlJoysticks); + startWiimoteThreads(); + // Save any new GUID assignments back to the INI file. if (isProfileLoaded) saveGuidsToIni(); @@ -1230,8 +1249,47 @@ void addActionToDirtyList(JVSPlayer player, LogicalAction action) */ void processSdlEvent(const SDL_Event *e) { - if (!sdlInputInitialized) - return; + // Make sure to use the variable, not the macro name + if (e->type == SDL_WIIMOTION_EVENT) + { + int controllerIndex = e->user.code; + int irX = (intptr_t)e->user.data1; + int irY = (intptr_t)e->user.data2; + + if (irX == 0 || irX == 1023 || irY == 0 || irY == 767) + { + gActionStates[controllerIndex + 1][LA_Reload].isActive = true; + addActionToDirtyList(controllerIndex + 1, LA_Reload); + } + else + { + gActionStates[controllerIndex + 1][LA_Reload].isActive = false; + addActionToDirtyList(controllerIndex + 1, LA_Reload); + } + + // The Wii Remote IR gives 0-1023 for X and 0-767 for Y. + // We can normalize this to 0.0 - 1.0 to behave like our mouse input. + float posX = 1.0f - (irX / 1023.0f); + float posY = irY / 767.0f; + + // printf("idx: %d, x: %f, y: %f\n", controller_index, posX, posY); + + ActionState *stateX = &gActionStates[controllerIndex + 1][LA_GunX]; + if (fabs(stateX->analogValue - posX) > 0.001f) + { + stateX->analogValue = posX; + addActionToDirtyList(controllerIndex + 1, LA_GunX); + } + + ActionState *stateY = &gActionStates[controllerIndex + 1][LA_GunY]; + if (fabs(stateY->analogValue - posY) > 0.001f) + { + stateY->analogValue = posY; + addActionToDirtyList(controllerIndex + 1, LA_GunY); + } + + return; // Event handled + } switch (e->type) { @@ -1523,6 +1581,8 @@ void processSdlEvent(const SDL_Event *e) } } } + if (gameType == MAHJONG && getConfig()->emulateTouchscreen) + handleMahjongTouch(e, drawableW, drawableH); } } break; @@ -1780,6 +1840,14 @@ void processChangedActions() switch (map->call_type) { case JVS_CALL_SWITCH: + if ((gGrp == GROUP_ID4_EXP || gGrp == GROUP_ID4_JAP || gGrp == GROUP_ID5) && actionId == LA_CardInsert) + { + if (state->isActive) + gTriggerInsertKey = true; + else + gTriggerInsertKey = false; + break; + } setSwitch(player, map->jvsInput, state->isActive); break; case JVS_CALL_ANALOGUE: @@ -1808,6 +1876,36 @@ void processChangedActions() (gGrp == GROUP_ID4_EXP || gGrp == GROUP_ID4_JAP || gGrp == GROUP_ID5)) state->analogValue = (state->analogValue - 0.5f) * (getConfig()->idSteeringPercentageReduction / 100.0f) + 0.5f; + if (p1CrossHairInitialized && (map->jvsInput == ANALOGUE_1 || map->jvsInput == ANALOGUE_2)) + { + static float lastAnalogue1 = 0.0f; + static float lastAnalogue2 = 0.0f; + if (map->jvsInput == ANALOGUE_1) + lastAnalogue1 = state->analogValue; + else + lastAnalogue2 = state->analogValue; + + float xPos = map->jvsInput == ANALOGUE_1 ? state->analogValue : lastAnalogue1; + float yPos = map->jvsInput == ANALOGUE_2 ? state->analogValue : lastAnalogue2; + + updateCrosshairPosition(player - 1, xPos, yPos); + } + + if (p2CrossHairInitialized && (map->jvsInput == ANALOGUE_3 || map->jvsInput == ANALOGUE_4)) + { + static float lastAnalogue3 = 0.0f; + static float lastAnalogue4 = 0.0f; + if (map->jvsInput == ANALOGUE_3) + lastAnalogue3 = state->analogValue; + else + lastAnalogue4 = state->analogValue; + + float xPos = map->jvsInput == ANALOGUE_3 ? state->analogValue : lastAnalogue3; + float yPos = map->jvsInput == ANALOGUE_4 ? state->analogValue : lastAnalogue4; + + updateCrosshairPosition(player - 1, xPos, yPos); + } + setAnalogue(map->jvsInput, (int)(state->analogValue * jvsAnalogueMaxValue)); break; case JVS_CALL_COIN: @@ -2055,6 +2153,7 @@ int listSdlControllers(void) } printf("\n"); } + SDL_free(joysticks); } SDL_Quit(); return 0; diff --git a/src/lindbergh/shaderCache.c b/src/lindbergh/shaderCache.c index cacd922..48161b4 100644 --- a/src/lindbergh/shaderCache.c +++ b/src/lindbergh/shaderCache.c @@ -799,6 +799,10 @@ void cacheModedShaderFiles() memcpy((void *)shaderFilesToPatch[x].shaderBuffer, buff, patchedFile.fsize); free(buff); } + + xdl_free_mmfile(&oriFile); + xdl_free_mmfile(&patch); + xdl_free_mmfile(&patchedFile); } if (getConfig()->showDebugMessages) diff --git a/src/lindbergh/shaderPatches.c b/src/lindbergh/shaderPatches.c index bda51cd..1f5e0cf 100644 --- a/src/lindbergh/shaderPatches.c +++ b/src/lindbergh/shaderPatches.c @@ -157,6 +157,10 @@ char *replaceInBlock(char *source, SearchReplace *searchReplace, int searchRepla char *new_result = replaceSubstring(result, start_index, end_index, target, replacement); if (new_result != NULL) { + if (result != source) + { + free(result); + } result = new_result; } } @@ -328,8 +332,15 @@ void gsEvoElfShaderPatcher() newProgram = replaceInBlock(tmpProgram, gsevoElf, gsevoElfCount, "", ""); + char *prevProgram = newProgram; newProgram = replaceInBlock(newProgram, gsevoElfMesa, gsevoElfCountMesa, "", ""); + if (prevProgram != tmpProgram && prevProgram != newProgram) + free(prevProgram); + + prevProgram = newProgram; newProgram = replaceInBlock(newProgram, gsevoSet1, gsevoSetCount1, "", ""); + if (prevProgram != tmpProgram && prevProgram != newProgram) + free(prevProgram); int newProgramLen = strlen(newProgram); @@ -342,6 +353,9 @@ void gsEvoElfShaderPatcher() } memcpy((void *)(size_t)gsevoElfShaderOffsets[i].startOffset, newProgram, newProgramLen); + + if (newProgram != tmpProgram) + free(newProgram); } } @@ -419,13 +433,18 @@ void srtvElfShaderPatcher() cleanShaderString(newProgram); + char *prevProgram = newProgram; newProgram = replaceInBlock(newProgram, srtvElf2, srtvElfCount2, "", ""); + if (prevProgram != tmpProgram && prevProgram != newProgram) + free(prevProgram); int newProgramLen = strlen(newProgram); newProgram[newProgramLen] = '\0'; memcpy((void *)(size_t)srtvElfShaderOffsets[i].startOffset, newProgram, newProgramLen); + if (newProgram != tmpProgram) + free(newProgram); } } @@ -464,28 +483,23 @@ char *cgGetProgramString(char *program, int e) */ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *program) { - char *newProgram; + char *newProgram = program; + char *program_to_free = NULL; int newProgramLen; void *addr = __builtin_return_address(0); - if(gId == GHOST_SQUAD_EVOLUTION) + if (gId == GHOST_SQUAD_EVOLUTION) { if (addr == (void *)0x8179fab) { newProgram = replaceInBlock(program, gsevoSet1, gsevoSetCount1, "", ""); - int newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; } } - else if(gGrp == GROUP_LGJ) + else if (gGrp == GROUP_LGJ) { newProgram = replaceInBlock(program, lgjCompiledShadersPatch, lgjCompiledShadersPatchCount, "", ""); - newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; } - else if(gGrp == GROUP_ABC) + else if (gGrp == GROUP_ABC) { if (target == GL_VERTEX_PROGRAM_ARB) { @@ -495,11 +509,8 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro { newProgram = replaceInBlock(program, abcFsMesa, abcFsMesaCount, "", ""); } - newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; } - else if(gGrp == GROUP_OUTRUN) + else if (gGrp == GROUP_OUTRUN) { if (target == GL_VERTEX_PROGRAM_ARB) { @@ -517,9 +528,6 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro else newProgram = replaceInBlock(program, orFsMesa, orFsMesaCount, "", ""); } - newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; } else if (gGrp == GROUP_VF5) { @@ -531,7 +539,10 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro if (gId == VIRTUA_FIGHTER_5_FINAL_SHOWDOWN_REVA || gId == VIRTUA_FIGHTER_5_FINAL_SHOWDOWN_REVB || gId == VIRTUA_FIGHTER_5_FINAL_SHOWDOWN_REVB_6000) { + char *prevProgram = newProgram; newProgram = replaceInBlock(newProgram, vf5VsAtifs, vf5VsAtifsCount, "", ""); + if (prevProgram != program && prevProgram != newProgram) + free(prevProgram); } } else @@ -546,9 +557,6 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro else newProgram = replaceInBlock(program, vf5FsMesa, vf5FsMesaCount, "", ""); } - newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; } else if (gId == R_TUNED) { @@ -560,9 +568,6 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro { newProgram = replaceInBlock(program, rtunedFsMesa, rtunedFsMesaCount, "", ""); } - newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; } else if (gId == MJ4_REVG || gId == MJ4_EVO) { @@ -574,11 +579,13 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro { newProgram = replaceInBlock(program, mj4FsMesa, mj4FsMesaCount, "", ""); } - newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; } - glProgramStringARB(target, program_fmt, program_len, program); + + if (newProgram != program) + program_to_free = newProgram; + + newProgramLen = strlen(newProgram); + glProgramStringARB(target, program_fmt, newProgramLen, newProgram); if (gGrp == GROUP_VF5) { @@ -587,12 +594,11 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro int err = glGetError(); if (err == 1282) { - char *newProgram; - newProgram = replaceInBlock(program, vf5VsAti2, vf5VsAtiCount2, "", ""); - int newProgramLen = strlen(newProgram); - program = newProgram; - program_len = newProgramLen; - glProgramStringARB(target, program_fmt, program_len, program); + char *newProgram2 = replaceInBlock(newProgram, vf5VsAti2, vf5VsAtiCount2, "", ""); + int newProgramLen2 = strlen(newProgram2); + glProgramStringARB(target, program_fmt, newProgramLen2, newProgram2); + if (newProgram2 != newProgram) + free(newProgram2); } } } @@ -616,9 +622,9 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro FILE *f; sprintf(filename, "%s/Error-%d.prg", dir_only, idxError); // sprintf(filename, "%s/%s", dir_only, fname); - printf("Size: %d\n", program_len); + printf("Size: %d\n", newProgramLen); f = fopen(filename, "w+"); - fwrite(program, sizeof(char), program_len, f); + fwrite(newProgram, sizeof(char), newProgramLen, f); fclose(f); idxError++; } @@ -638,6 +644,8 @@ void gl_ProgramStringARB(int target, int program_fmt, int program_len, char *pro } } } + if (program_to_free) + free(program_to_free); return; } @@ -710,16 +718,20 @@ char *cgCreateProgram(uint32_t context, int program_type, const char *program, i void gl_ShaderSourceARB(GLhandleARB shaderObj, GLsizei count, const GLcharARB **const string, const GLint *length) { - if(gGrp != GROUP_HUMMER && gId != SEGA_RACE_TV) + if (gGrp != GROUP_HUMMER && gId != SEGA_RACE_TV) { glShaderSourceARB(shaderObj, count, string, length); return; } - char *newProgram = malloc(250000); + char *program_to_free = NULL; + const GLcharARB *final_string = *string; + GLint final_length = (length ? *length : 0); - if(gId == SEGA_RACE_TV) + if (gId == SEGA_RACE_TV) { + char *newProgram = NULL; + char tmp[250000]; void *addr = __builtin_return_address(0); if (addr <= (void *)0x82fd823) { @@ -729,35 +741,43 @@ void gl_ShaderSourceARB(GLhandleARB shaderObj, GLsizei count, const GLcharARB ** { if (getConfig()->GPUVendor != NVIDIA_GPU) { - sprintf(newProgram, "#version 120\r\n%s", *string); - newProgram = replaceInBlock(newProgram, srtvVsMesa, srtvVsMesaCount, "", ""); + sprintf(tmp, "#version 120\r\n%s", *string); + newProgram = replaceInBlock(tmp, srtvVsMesa, srtvVsMesaCount, "", ""); } else { - sprintf(newProgram, "%s", *string); - newProgram = replaceInBlock(newProgram, srtvVsNvidia, srtvVsNvidiaCount, "", ""); + sprintf(tmp, "%s", *string); + newProgram = replaceInBlock(tmp, srtvVsNvidia, srtvVsNvidiaCount, "", ""); } } else if (addr == (void *)0x084bc06b) { if (getConfig()->GPUVendor != NVIDIA_GPU) { - sprintf(newProgram, "#version 120\r\n%s", *string); - newProgram = replaceInBlock(newProgram, srtvFsMesa, srtvFsMesaCount, "", ""); + sprintf(tmp, "#version 120\r\n%s", *string); + newProgram = replaceInBlock(tmp, srtvFsMesa, srtvFsMesaCount, "", ""); } else { newProgram = strdup(*string); } } + + if (newProgram) + { + final_string = newProgram; + program_to_free = newProgram; + } } else if (gGrp == GROUP_HUMMER) { void *addr = __builtin_return_address(0); - if ((addr != (void *)0x830a501) && (addr != (void *)0x830a485) && (addr != (void *)0x08319841) && (addr != (void *)0x083197c5) && - (addr != (void *)0x83af939) && (addr != (void *)0x83af9b3) && (addr != (void *)0x83b0515) && (addr != (void *)0x83b058f)) + if ((addr != (void *)0x830a501) && (addr != (void *)0x830a485) && (addr != (void *)0x08319841) && + (addr != (void *)0x083197c5) && (addr != (void *)0x83af939) && (addr != (void *)0x83af9b3) && + (addr != (void *)0x83b0515) && (addr != (void *)0x83b058f)) { - sprintf(newProgram, "%s", hummerShaderFilesToMod[hummerExtremeShaderFileIndex].shaderBuffer); + final_string = strdup(hummerShaderFilesToMod[hummerExtremeShaderFileIndex].shaderBuffer); + program_to_free = (char *)final_string; } else { @@ -765,26 +785,26 @@ void gl_ShaderSourceARB(GLhandleARB shaderObj, GLsizei count, const GLcharARB ** return; } } - else + else { glShaderSourceARB(shaderObj, count, string, length); return; } - const char *newString = malloc(250000); - memset((void *)newString, '\0', 250000); - newString = strdup(newProgram); if (length != NULL) { - const int newLength = hummerShaderFilesToMod[hummerExtremeShaderFileIndex].shaderBufferSize; - glShaderSourceARB(shaderObj, count, &newString, &newLength); + final_length = strlen(final_string); + glShaderSourceARB(shaderObj, count, &final_string, &final_length); } else { - glShaderSourceARB(shaderObj, count, &newString, length); + glShaderSourceARB(shaderObj, count, &final_string, NULL); + } + + if (program_to_free) + { + free(program_to_free); } - free((void *)newString); - free(newProgram); } /** @@ -958,7 +978,9 @@ void glBindTexture(GLenum target, GLuint texture) int parseCgcArgs(const char *input, const char ***compilerArgs, const char **outputFileName) { - char *token = strtok(strdup(input), " "); + char *inputCopy = strdup(input); + char *toFree = inputCopy; + char *token = strtok(inputCopy, " "); char **args = malloc(256 * sizeof(char *)); int count = 0; char *profile; @@ -992,6 +1014,7 @@ int parseCgcArgs(const char *input, const char ***compilerArgs, const char **out } (*compilerArgs)[argIndex] = NULL; free(args); + free(toFree); if (strcmp(profile, "vp40") == 0) { return 6150; @@ -1021,7 +1044,7 @@ char *findLibCg() if (currentDir != NULL) { size_t pathLen = strlen(currentDir) + strlen("/libCg.so") + 1; - pathsToCheck[3] = malloc(pathLen); + pathsToCheck[4] = malloc(pathLen); if (pathsToCheck[4] != NULL) { snprintf(pathsToCheck[4], pathLen, "%s/%s", currentDir, "libCg.so"); @@ -1228,6 +1251,7 @@ int compileWithCGC(char *command) _cgDestroyProgram(program); _cgDestroyContext(ctx); + free((void *)*compilerArgs); return 0; } diff --git a/src/lindbergh/touchScreen.c b/src/lindbergh/touchScreen.c index 4dbee29..5584505 100644 --- a/src/lindbergh/touchScreen.c +++ b/src/lindbergh/touchScreen.c @@ -23,6 +23,14 @@ extern bool phShowCursorInGame; #define TOUCH_TOUCHING 0x82 #define TOUCH_END 0x84 +#define MJ4_PACKET_SIZE 10 +uint8_t mj4ResponseBuf[MJ4_PACKET_SIZE] = {0}; +bool mj4MousePressed = false; +extern int mj4MouseX; +extern int mj4MouseY; +bool mj4ResponseReady = false; +bool mj4TouchedInsideScreen = false; + void phCoordinates(int x, int y, int w, int h, int button) { coordX = (float)x / ((float)w * 0.0002441406); @@ -169,11 +177,190 @@ bool phIsInsideTouchScreen(int mX, int mY, int *x, int *y) } void phTouchClick(int x, int y, int type) -{ +{ if (type == SDL_EVENT_MOUSE_BUTTON_DOWN) phCoordinates(x, y, phW2, phH2, SDL_EVENT_MOUSE_BUTTON_DOWN); else if (type == SDL_EVENT_MOUSE_BUTTON_UP) phCoordinates(x, y, phW2, phH2, SDL_EVENT_MOUSE_BUTTON_UP); else if (type == SDL_EVENT_MOUSE_MOTION) phCoordinates(x, y, phW2, phH2, SDL_EVENT_MOUSE_MOTION); +} + +void handleMahjongTouch(const SDL_Event *e, int drawableW, int drawableH) +{ + + mj4MousePressed = 0; + mj4MouseX = e->button.x; + mj4MouseY = drawableH - e->button.y; + + if (e->type == SDL_EVENT_MOUSE_BUTTON_DOWN) + mj4MousePressed = 1; + + float winAspect = (float)drawableW / drawableH; + float gameAspect = 1024.0f / 768.0f; + + int viewportX, viewportY, viewportW, viewportH; + + if (winAspect > gameAspect) + { + viewportH = drawableH; + viewportW = (int)(drawableH * gameAspect); + viewportX = (drawableW - viewportW) / 2; + viewportY = 0; + } + else + { + viewportW = drawableW; + viewportH = (int)(drawableW / gameAspect); + viewportX = 0; + viewportY = (drawableH - viewportH) / 2; + } + + if (mj4MouseX < viewportX || mj4MouseX >= viewportX + viewportW || mj4MouseY < viewportY || mj4MouseY >= viewportY + viewportH) + { + mj4TouchedInsideScreen = false; + return; + } + else + { + mj4TouchedInsideScreen = true; + float normalizedX = (mj4MouseX - viewportX) / (float)viewportW; + float normalizedY = (mj4MouseY - viewportY) / (float)viewportH; + + mj4MouseX = normalizedX * 1024.0f; + mj4MouseY = normalizedY * 768.0f; + } + mj4BuildResponsePacket(0x54, mj4MouseX, mj4MouseY, mj4MousePressed); +} + +uint8_t mj4CalcChecksum(uint8_t *packet) +{ + uint8_t sum = 0; + for (int i = 0; i < MJ4_PACKET_SIZE - 1; ++i) + sum += packet[i]; + return (sum + 0xAA) & 0xFF; +} + +void mj4BuildResponsePacket(uint8_t cmd, int pckA, int pckB, int pckC) +{ + uint8_t *packet = mj4ResponseBuf; + + packet[0] = 0x55; + packet[1] = cmd; + + if (cmd == 0x54) + { + packet[2] = pckC; // & 0xFF; + packet[3] = pckA & 0xFF; + packet[4] = (pckA >> 8) & 0xFF; + packet[5] = pckB & 0xFF; + packet[6] = (pckB >> 8) & 0xFF; + } + else + { + packet[2] = pckA & 0xFF; + packet[3] = pckB & 0xFF; + packet[4] = pckC & 0xFF; + packet[5] = 0x00; + packet[6] = 0x00; + } + packet[7] = 0x00; + packet[8] = 0x00; + packet[9] = mj4CalcChecksum(packet); + + mj4ResponseReady = true; +} + +int mj4WriteTouchPacket(const void *buf, size_t count) +{ + static uint8_t packetBuf[10]; + static int packetPos = 0; + bool degugMsg = getConfig()->showDebugMessages; + + for (size_t i = 0; i < count; ++i) + { + uint8_t b = ((uint8_t *)buf)[i]; + + if (packetPos == 0 && b != 0x55) + continue; + + packetBuf[packetPos++] = b; + + if (packetPos == MJ4_PACKET_SIZE) + { + if (degugMsg) + { + printf("Serial1 FULL PACKET: "); + for (int j = 0; j < MJ4_PACKET_SIZE; ++j) + printf("%02X ", packetBuf[j]); + printf("\n"); + } + + uint8_t cmd = packetBuf[1]; + + switch (cmd) + { + case 0x52: + if (degugMsg) + printf("MJ4: Init/handshake? (0x52)\n"); + // mj4_send_response(0x52, 0xff, 0xff); + // start_streaming = 1; + break; + case 0x50: + if (degugMsg) + printf("MJ4: Config/setup (baud) (0x50)\n"); + // mj4_send_response(0x50, 0x00, 0x00); + break; + case 0x70: + if (degugMsg) + printf("MJ4: Status check/reply? (0x70)\n"); + mj4BuildResponsePacket(0x50, 0x30, 0x06, 0x04); + break; + case 0x42: + if (degugMsg) + printf(" (0x42)\n"); + mj4BuildResponsePacket(0x41, 0x03, 0x01, 0x00); + break; + case 0x62: + if (degugMsg) + printf(" (0x62)\n"); + mj4BuildResponsePacket(0x42, 0x03, 0x01, 0x00); + break; + case 0x6D: + if (degugMsg) + printf(" (0x6D)\n"); + mj4BuildResponsePacket(0x4D, 0x00, 0x00, 0x00); + break; + case 0x4D: + if (degugMsg) + printf(" (0x6D)\n"); + mj4BuildResponsePacket(0x41, 0x03, 0x01, 0x00); + break; + default: + if (degugMsg) + printf("MJ4: Unknown command %02X\n", cmd); + mj4ResponseReady = false; + break; + } + packetPos = 0; + } + } + return count; +} + +int mj4ReadTouchPacket(uint8_t *buf, size_t count) +{ + if (mj4ResponseReady && count >= MJ4_PACKET_SIZE) + { + memcpy(buf, mj4ResponseBuf, MJ4_PACKET_SIZE); + mj4ResponseReady = false; + return MJ4_PACKET_SIZE; + } + return 0; +} + +void mj4TouchHolding() +{ + if (mj4MousePressed && mj4TouchedInsideScreen) + mj4BuildResponsePacket(0x54, mj4MouseX, mj4MouseY, 2); } \ No newline at end of file diff --git a/src/lindbergh/touchScreen.h b/src/lindbergh/touchScreen.h index 0f4f011..6d7486a 100644 --- a/src/lindbergh/touchScreen.h +++ b/src/lindbergh/touchScreen.h @@ -9,3 +9,8 @@ ssize_t phRead(int fd, void *buf, size_t count); void phTouchScreenCursor(int mX, int mY, int *motX, int *motY); bool phIsInsideTouchScreen(int mX, int mY, int *x, int *y); void phTouchClick(int x, int y, int type); +void handleMahjongTouch(const SDL_Event *e, int drawableW, int drawableH); +void mj4BuildResponsePacket(uint8_t cmd, int pckA, int pckB, int pckC); +int mj4ReadTouchPacket(uint8_t *buf, size_t max_len); +int mj4WriteTouchPacket(const void *buf, size_t count); +void mj4TouchHolding(); \ No newline at end of file diff --git a/src/lindbergh/wiimoteEvdev.c b/src/lindbergh/wiimoteEvdev.c new file mode 100644 index 0000000..b3d8a92 --- /dev/null +++ b/src/lindbergh/wiimoteEvdev.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wiimoteEvdev.h" + +typedef struct +{ + int sdlPlayerIndex; + char mac[18]; + int fd; + pthread_t threadId; + int running; +} WiiMoteDevice; + +static WiiMoteDevice gWiiMotes[MAX_JOYSTICKS]; +static int gWiiMoteCount = 0; +Uint32 SDL_WIIMOTION_EVENT = 0; + +static void *wiimoteThreadFunc(void *arg) +{ + WiiMoteDevice *wiimote = (WiiMoteDevice *)arg; + struct input_event ev; + int irX = 0; + int irY = 0; + + printf("Started IR thread for WiiMote P%d\n", wiimote->sdlPlayerIndex + 1); + + while (wiimote->running) + { + if (read(wiimote->fd, &ev, sizeof(ev)) > 0) + { + if (ev.type == EV_ABS) + { + if (ev.code == ABS_HAT0X) + { + irX = ev.value; + } + else if (ev.code == ABS_HAT0Y) + { + irY = ev.value; + } + } + else if (ev.type == EV_SYN && ev.code == SYN_REPORT) + { + SDL_Event event; + SDL_zero(event); + event.type = SDL_WIIMOTION_EVENT; + event.user.code = wiimote->sdlPlayerIndex; + event.user.data1 = (void *)(intptr_t)irX; + event.user.data2 = (void *)(intptr_t)irY; + SDL_PushEvent(&event); + } + } + usleep(1000); + } + + printf("Exiting IR thread for WiiMote P%d\n", wiimote->sdlPlayerIndex + 1); + close(wiimote->fd); + return NULL; +} + +static int getMacFromSyspath(struct udev *udev, const char *syspath, char *macBuffer, size_t len) +{ + int ret = -1; + struct udev_device *dev = udev_device_new_from_syspath(udev, syspath); + if (!dev) + { + return -1; + } + + struct udev_device *parent = udev_device_get_parent_with_subsystem_devtype(dev, "hid", NULL); + if (parent) + { + const char *uniq = udev_device_get_property_value(parent, "HID_UNIQ"); + if (uniq) + { + strncpy(macBuffer, uniq, len - 1); + macBuffer[len - 1] = '\0'; + ret = 0; + } + } + + udev_device_unref(dev); + return ret; +} + +int findAndOpenWiiMotes(SDLControllers *sdlControllers) +{ + struct udev *udev = udev_new(); + if (!udev) + { + fprintf(stderr, "Failed to create udev context.\n"); + return 0; + } + + for (int i = 0; i < MAX_JOYSTICKS; i++) + { + SDL_Joystick *joy = NULL; + if (sdlControllers->controllers[i]) + joy = SDL_GetGamepadJoystick(sdlControllers->controllers[i]); + else if (sdlControllers->joysticks[i]) + joy = sdlControllers->joysticks[i]; + + if (joy && strstr(SDL_GetJoystickName(joy), "Nintendo Wii Remote")) + { + const char *sdlDevPath = SDL_GetJoystickPathForID(i); + if (!sdlDevPath) + { + fprintf(stderr, "Couldn't get device path for joystick %d\n", i); + continue; + } + + char *devPathCopy = strdup(sdlDevPath); + if (!devPathCopy) + continue; + + struct udev_device *dev = udev_device_new_from_subsystem_sysname(udev, "input", basename(devPathCopy)); + free(devPathCopy); + + if (dev) + { + const char *syspath = udev_device_get_syspath(dev); + if (syspath) + { + if (getMacFromSyspath(udev, syspath, gWiiMotes[gWiiMoteCount].mac, sizeof(gWiiMotes[gWiiMoteCount].mac)) == 0) + { + gWiiMotes[gWiiMoteCount].sdlPlayerIndex = i; + printf("Found WiiMote P%d with MAC: %s\n", i + 1, gWiiMotes[gWiiMoteCount].mac); + gWiiMoteCount++; + } + } + udev_device_unref(dev); + } + } + } + + if (gWiiMoteCount == 0) + { + printf("No Wii Remotes found by SDL.\n"); + udev_unref(udev); + return 0; + } + + struct udev_enumerate *enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_scan_devices(enumerate); + + struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate); + struct udev_list_entry *devListEntry; + + udev_list_entry_foreach(devListEntry, devices) + { + const char *path = udev_list_entry_get_name(devListEntry); + struct udev_device *dev = udev_device_new_from_syspath(udev, path); + if (!dev) + continue; + + const char *devnode = udev_device_get_devnode(dev); + if (devnode && strstr(devnode, "/dev/input/event")) + { + struct udev_device *parent = udev_device_get_parent_with_subsystem_devtype(dev, "input", NULL); + if (parent) + { + const char *name = udev_device_get_sysattr_value(parent, "name"); + if (name && strstr(name, "Nintendo Wii Remote IR")) + { + char macFromHid[18] = {0}; + if (getMacFromSyspath(udev, path, macFromHid, sizeof(macFromHid)) == 0) + { + for (int j = 0; j < gWiiMoteCount; j++) + { + if (strcmp(macFromHid, gWiiMotes[j].mac) == 0) + { + gWiiMotes[j].fd = open(devnode, O_RDONLY | O_NONBLOCK); + int flags = fcntl(gWiiMotes[j].fd, F_GETFL, 0); + fcntl(gWiiMotes[j].fd, F_SETFL, flags | O_NONBLOCK); + if (gWiiMotes[j].fd > 0) + { + printf("Successfully associated WiiMote P%d with IR device %s\n", gWiiMotes[j].sdlPlayerIndex + 1, + devnode); + } + break; + } + } + } + } + } + } + udev_device_unref(dev); + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return gWiiMoteCount; +} + +void startWiimoteThreads() +{ + for (int i = 0; i < gWiiMoteCount; i++) + { + if (gWiiMotes[i].fd > 0) + { + gWiiMotes[i].running = 1; + pthread_create(&gWiiMotes[i].threadId, NULL, wiimoteThreadFunc, &gWiiMotes[i]); + } + } +} + +void cleanupWiiMoteThreads() +{ + for (int i = 0; i < gWiiMoteCount; i++) + { + if (gWiiMotes[i].running) + { + gWiiMotes[i].running = 0; + pthread_join(gWiiMotes[i].threadId, NULL); + } + } +} \ No newline at end of file diff --git a/src/lindbergh/wiimoteEvdev.h b/src/lindbergh/wiimoteEvdev.h new file mode 100644 index 0000000..9cda5f5 --- /dev/null +++ b/src/lindbergh/wiimoteEvdev.h @@ -0,0 +1,9 @@ +#pragma once + +#include "sdlInput.h" + +extern Uint32 SDL_WIIMOTION_EVENT; + +int findAndOpenWiiMotes(SDLControllers *sdl_controllers); +void startWiimoteThreads(); +void cleanupWiiMoteThreads(); \ No newline at end of file