From 22ec726494376ea68551cbc0dc4b8eac1692ad39 Mon Sep 17 00:00:00 2001 From: Vegz78 <> Date: Sat, 18 Dec 2021 04:52:34 +0100 Subject: [PATCH] Recalbox 8 support&fixes -Fixed bug where launCharc did not recognize certain Recalbox installations where hostname was not just RECALBOX -Fixed bug where fopen() together with fork() made launCharc not write all its intended output lines to the log file -Finally fixed the argument parser, so that launCharc can now be run with any combination of the current arguments nomap, keybswap and verbose, not only one at a time, like earlier -Tighter removal of certain control character key combinations that could interfere with the game(pad) controls during gameplay -Moved termfix' ioctl calls into its own variadic function, where different combinations of various ioctl calls can be chosen dynamically according to needs -General refactoring and tightening of code flow and logic, pluss various other minor bugfixes -Tested on Recalbox version 8.0 and RetroPie 4.7.3, Linux kernels v5.x and 4.x, respectively -Next up: Automatic installations scripts, with and without HDMI sound, for Recalbox v8.0 -STAY TUNED! --- McAirpos/launCharc/launCharc | Bin 21480 -> 21656 bytes McAirpos/launCharc/launCharc.c | 384 +++++++++++++++++++-------------- 2 files changed, 219 insertions(+), 165 deletions(-) diff --git a/McAirpos/launCharc/launCharc b/McAirpos/launCharc/launCharc index 421455c53c8fb6f20ffc316bd2dc734995a5c1cd..8d0016d97799f2450474100558438e98400b4f5e 100755 GIT binary patch literal 21656 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 #include +#include #include #include #include @@ -51,6 +52,8 @@ // Global variables FILE *fl; +int fd; +int kbdg; // Function which returns results from shell commands run through system() @@ -61,7 +64,7 @@ char* getSystemOutput(char* inputCommand) { memset (systemOutput, 0, sizeof(systemOutput)); fp = popen(inputCommand, "r"); if (fp == NULL) { - fprintf(fl, "Failed to run command\n"); + fprintf(fl, "Failed to run system() command\n"); exit(1); } @@ -73,84 +76,191 @@ char* getSystemOutput(char* inputCommand) { } +// The code in this function is borrowed from https://github.com/hobbitalistair/termfix: +// Terminal Fixer's cleanup part, which returns control of the +// framebuffer and input mode to calling process after game's exit +int setConsoleGraphicsMode(char *path, int noOfArguments, ...) { + + // Open the tty for ioctl + if (strcmp("not a tty", getSystemOutput("tty | tr -d [:cntrl:]"))) { + fd = open(path, O_RDWR, 0); + if (fd < 0) { + fprintf(fl, "Unable to open s% for ioctl...\n", path); + return 1; + } + // Declaring pointer to the argument list + va_list ptr; + // Initializing argument to the list pointer + va_start(ptr, noOfArguments); + for (int i = 1; i < (noOfArguments + 1); i++) { + // Accessing current variable and pointing to next one in switch + switch(va_arg(ptr, int)) { + case 1: + // Sets the Linux console to graphics mode + if (ioctl(fd, KDSETMODE, KD_GRAPHICS) < 0) { + fprintf(fl, "Warn: ioctl KDSETMODE failed...\n"); + fprintf(fl, "Are you on a terminal emulator(X-Windows) instead of the Linux console?\n"); + } + break; + case 2: + // Sets the Linux console back to text mode + if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) { + fprintf(fl, "Warn: ioctl KDSETMODE failed...\n"); + fprintf(fl, "Are you on a terminal emulator(X-Windows) instead of the Linux console?\n"); + } + break; + case 3: + // Get the current keyboard mode + if (ioctl(fd, KDGKBMODE, &kbdg) < 0) { + fprintf(fl, "Warn: ioctl KBSKBMODE failed...\n"); + fprintf(fl, "Unable to get your keyboard mode.\n"); + } + break; + case 4: + // Set the current keyboard mode + if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) { + fprintf(fl, "Warn: ioctl KBSKBMODE failed...\n"); + fprintf(fl, "Unable to set your keyboard mode.\n"); + } + break; + case 5: + // Unlocks switching between virtual terminals with CTRL+ALT+FX + // This one fails without sudo, but doesn't seem needed for + // MakeCode Arcade games(comment out or leave as and option?). + if (ioctl(fd, VT_UNLOCKSWITCH, 1) < 0) { + fprintf(fl, "Warn: ioctl VT_UNLOCKSWITCH failed...\n"); + fprintf(fl, "Are you not a super-user?\n"); + } + break; + default: + fprintf(fl, "Not a valid choice for setConsoleGraphicsMode()\n"); + return 1; + } + } + // Ending argument list traversal + va_end(ptr); + close(fd); + } + + return 0; +} + + // Main function int main(int argc, char** argv) { // Variables for whole main function scope char* path = "/dev/tty"; - int fd; - char basename[200]; - memset (basename, 0, sizeof(basename)); - char copyCmd[300]; - memset (copyCmd, 0, sizeof(copyCmd)); + char logfile[20]; + memset (logfile, 0, sizeof(logfile)); + strcat(logfile, "/tmp/McAirpos.log"); + char sudoer[6]; + memset (sudoer, 0, sizeof(sudoer)); + strcat(sudoer, "sudo "); + char gameString[200]; - // Read game file argument to execute - // Note: Should be updated to allow for combinations of more than one argument, e.g. "nomap verbose" and future additions + // Read game file arguments to execute char game[200]; memset (game, 0, sizeof(game)); - char options[10]; - memset (options, 0, sizeof(options)); - if (argc == 2) { - strcat(game, argv[1]); - } else if (argc == 3) { - strcat(game, argv[2]); - strcat(options, argv[1]); - } else if ((argc > 3) || (argc < 2)) { + int nomap = 0, keybswap = 0, verbose = 0; + struct stat gameFileBuffer; + if ((argc < 2) || !(strstr(argv[argc - 1], ".elf") != NULL)) { printf("usage: launCharc [nomap / keybswap / verbose] [/path/to/arcadegame.elf]\n"); return 1; } + for (int i = 1; i < argc; i++) { + if (!strcmp("nomap", argv[i])) nomap = 1; + if (!strcmp("keybswap", argv[i])) keybswap = 1; + if (!strcmp("verbose", argv[i])) verbose = 1; + if ((argv[i] == argv[argc - 1]) && (strstr(argv[i], ".elf") != NULL)) strcat(game, argv[argc - 1]); + } - // Find rom's basename - snprintf(copyCmd, 300, "basename %s", game); - strcat(basename, getSystemOutput(copyCmd)); - - - // Clear Linux console screen - system("clear"); + // Ready game files and terminal + // Check if game file exists, exit error message otherwise + if (0 != stat(game, &gameFileBuffer)) { + system("clear"); + snprintf(gameString, sizeof(gameString), "The game file %s was not found...\n\n", game); + fprintf(stderr, gameString); + snprintf(game, sizeof(game), "echo \"%s\" >%s 2<&1", gameString, logfile); + system(game); + sleep(1); + return 1; + } + // Check presence of and clear old pid from /tmp/pxt if present + int foundPxtFile = 0; + if (0 == system("head -1 /tmp/pxt-pid >>/dev/null 2>&1")) { + system("sed -i \"1s&.*&\"\"&\" /tmp/pxt-pid"); + foundPxtFile = 1; + } + //Disable pause(CTRL+S), suspend(CTRL+Z), eof(CTRL+D) and interrupt(CTRL+C) in terminal + system("stty -ixon -isig -icanon -iexten intr undef susp undef eof undef stop undef&&set -o ignoreeof"); + //Kill PulseAudio if running and kernel < 5, Pulseaudio can sometimes halt game looking for ALSA + if ((atoi(getSystemOutput("uname -r | grep -o -e '^[0-9]*' | tr -d [:cntrl:]")) < 5) && (strcmp("", getSystemOutput("ps -A | grep pulseaudio")))) { + system("sudo killall pulseaudio >>/dev/null 2>&1"); + // Note: Pulseaudio used to restart automatically on kernels below 5, keep an eye on how this is handled > 5 on RPi OS/RetroPie + } - // Check if run on Recalbox + // Check if and cater for running on Recalbox + // Run copy of game to circumvent Recalbox' read-only(/) and/or non-executablel(.../share/roms exFAT) file systems int isRecalbox = 0; - if (!strcmp("RECALBOX", getSystemOutput("uname -a | tr ' ' '\\n' | grep RECALBOX | tr -d [:cntrl:]"))) { + char copyCmd[300]; + memset (copyCmd, 0, sizeof(copyCmd)); + char basename[200]; + memset (basename, 0, sizeof(basename)); + snprintf(copyCmd, sizeof(copyCmd), "basename %s", game); + strcat(basename, getSystemOutput(copyCmd)); + if (strstr(getSystemOutput("uname -n | tr -d [:cntrl:]"), "RECALBOX") != NULL) { isRecalbox = 1; + memset (sudoer, 0, sizeof(sudoer)); // Copy game_file.elf to /tmp(.../roms folder mount exFAT and cannot execute files) memset (copyCmd, 0, sizeof(copyCmd)); - snprintf(copyCmd, 300, "rsync %s /recalbox/share/bootvideos/makecode/&&chmod +x /recalbox/share/bootvideos/makecode/%s", game, basename); + snprintf(copyCmd, sizeof(copyCmd), "rsync %s /recalbox/share/bootvideos/makecode/&&chmod +x /recalbox/share/bootvideos/makecode/%s", game, basename); system(copyCmd); + // Change game's execution path accordingly + memset (game, 0, sizeof(game)); + snprintf(game, sizeof(game), "/recalbox/share/bootvideos/makecode/%s", basename); //New location instead of /tmp allows for saving game states in settings and DB extensions etc. + // Show MakeCode Arcade splash screen on game loading system("/usr/bin/fbv2 /home/pi/McAirpos/McAirpos/MakeCode/MakeCode_Arcade.png >>/dev/null 2>&1"); } - // Check for verbose option - if (!strcmp("verbose", options)) { + // Check and cater for verbose option + // Silence the game launch information to Linux console if verbose option is not given + memcpy(gameString, game, strlen(game)+1); + system("clear"); + if (verbose) { fl = stdout; }else { - fl = fopen("/tmp/McAirpos.log", "w+"); - // Switch console to graphics mode to avoid disturbing text output in borders - if (strcmp("not a tty", getSystemOutput("tty | tr -d [:cntrl:]"))) { - fd = open(path, O_RDWR, 0); - if (fd < 0) { - perror("unable to open tty"); - return 1; - } - if (ioctl(fd, KDSETMODE, KD_GRAPHICS) < 0) { - perror("warn: ioctl KDSETMODE failed"); - } - close(fd); + // Create new or truncate existing file + fl = fopen(logfile, "w+"); + if (fl < 0) { + fprintf(fl, "Unable to open %s\n", logfile); + }else {fclose(fl);} + // Reopen log file in append mode to allow for writing across fork + fl = fopen(logfile, "a"); + if (fl < 0) { + fprintf(fl, "Unable to open %s\n", logfile); } + // Switch console to graphics mode to avoid disturbing text output in borders + setConsoleGraphicsMode(path, 1, 1); + // Change game's execution path accordingly + memset (game, 0, sizeof(game)); + snprintf(game, sizeof(game), "%s >>%s 2>&1", gameString, logfile); } - // Check for nomap option - if (!strcmp(options, "nomap")) { - fprintf(fl, "%s argument detected,\nlaunCharc starting %s with no automatic gamepad mappings...\n", options, game); + // Check and cater for nomap option + // Run with gamepad settings in /sd/arcade.cfg or find and configure gamepads automatically + if (nomap) { + fprintf(fl, "nomap argument detected,\nlaunCharc starting %s with no automatic gamepad mappings...\n", gameString); sleep(1); } else { // Determine the number of connected gamepads - fprintf(fl, "launCharc starting %s with automatic gamepad mappings...\n", game); + fprintf(fl, "launCharc starting %s with automatic gamepad mappings...\n", gameString); char eventPaths[100]; memset (eventPaths, 0, sizeof(eventPaths)); int numberOfPads = 0; @@ -162,11 +272,7 @@ int main(int argc, char** argv) { if (numberOfPads < 2) { char padCommand[150]; memset (padCommand, 0, sizeof(padCommand)); - if (isRecalbox == 1) { - snprintf(padCommand, 150, "/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -vp /dev/input/event%d 2>&1 | grep -e BTN_START -e BTN_SOUTH -e BTN_PINKIE", i); - }else { - snprintf(padCommand, 150, "/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -vp /dev/input/event%d 2>&1 | grep -e BTN_START -e BTN_SOUTH -e BTN_PINKIE", i); - } + snprintf(padCommand, sizeof(padCommand), "/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -vp /dev/input/event%d 2>&1 | grep -e BTN_START -e BTN_SOUTH -e BTN_PINKIE", i); char* event = getSystemOutput(padCommand); if (strcmp(event, "")) { if (numberOfPads == 0) { @@ -188,7 +294,9 @@ int main(int argc, char** argv) { // Determine if keyboard is connected char keybCommand[300]; memset (keybCommand, 0, sizeof(keybCommand)); - if (!strcmp(options, "keybswap")) { + // Check and cater for keybswap option + // Chooses last keyboard found, first found otherwise + if (keybswap) { strcat(keybCommand, "cat /proc/bus/input/devices | grep -B 5 -A 5 Handlers=sysrq | grep -B 7 -A 3 -e EV=12001 -e EV=10001 | grep -B 2 -A 8 -E 'Phys=(usb\\S+\\/input1:1|usb\\S+\\/input0|[a-zA-Z0-9]{2}(:[a-zA-Z0-9]{2}){5}.*)' | tr ' ' '\\n' | grep event | tail -1 | tr -d [:cntrl:]"); } else { strcat(keybCommand, "cat /proc/bus/input/devices | grep -B 5 -A 5 Handlers=sysrq | grep -B 7 -A 3 -e EV=12001 -e EV=10001 | grep -B 2 -A 8 -E 'Phys=(usb\\S+\\/input1:1|usb\\S+\\/input0|[a-zA-Z0-9]{2}(:[a-zA-Z0-9]{2}){5}.*)' | tr ' ' '\\n' | grep event | head -1 | tr -d [:cntrl:]"); @@ -200,11 +308,9 @@ int main(int argc, char** argv) { } - // Automatically set up uinput-mapper with keyboard, 1 or 2 gamepads + // Automatically set up uinput-mapper with keyboard and/or 1 or 2 gamepads char stringNumberOfPads[20]; memset (stringNumberOfPads, 0, sizeof(stringNumberOfPads)); - char uiMapCommand[400]; - memset (uiMapCommand, 0, sizeof(uiMapCommand)); char uinputMapperOrKeyboard[20]; memset (uinputMapperOrKeyboard, 0, sizeof(uinputMapperOrKeyboard)); char defaultEvent[67]; @@ -246,12 +352,10 @@ int main(int argc, char** argv) { // Launching uinput-mapper + char uiMapCommand[400]; + memset (uiMapCommand, 0, sizeof(uiMapCommand)); if (numberOfPads > 0) { - if (isRecalbox == 1) { - strcat(strcat(strcat(strcat(uiMapCommand, "(/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -C -D "), eventPaths), "| /home/pi/McAirpos/McAirpos/uinput-mapper/input-create -C -S /home/pi/McAirpos/McAirpos/uinput-mapper/configs/arcade"), stringNumberOfPads); - } else { - strcat(strcat(strcat(strcat(uiMapCommand, "(/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -C -D "), eventPaths), "| sudo /home/pi/McAirpos/McAirpos/uinput-mapper/input-create -C -S /home/pi/McAirpos/McAirpos/uinput-mapper/configs/arcade"), stringNumberOfPads); - } + snprintf(uiMapCommand, sizeof(uiMapCommand), "(/home/pi/McAirpos/McAirpos/uinput-mapper/input-read -C -D %s| %s/home/pi/McAirpos/McAirpos/uinput-mapper/input-create -C -S /home/pi/McAirpos/McAirpos/uinput-mapper/configs/arcade%s", eventPaths, sudoer, stringNumberOfPads); if (system(uiMapCommand) == 0) { fprintf(fl, "Starting UInput-Mapper with command:\n%s\n", uiMapCommand); int whileCount = 0; @@ -279,77 +383,38 @@ int main(int argc, char** argv) { } - // Set default input event for MakeCode Arcade elf game file + // Set default input event for MakeCode Arcade elf game file in /sd/arcade.cfg char sedCommand[100]; memset (sedCommand, 0, sizeof(sedCommand)); - snprintf(sedCommand, 100, "sed -i \"1s&.*&\"%s\"&\" /sd/arcade.cfg", defaultEvent); - if (isRecalbox == 1) { - system("mount -o remount,rw /"); - } + snprintf(sedCommand, sizeof(sedCommand), "sed -i \"1s&.*&\"%s\"&\" /sd/arcade.cfg", defaultEvent); + if (isRecalbox) system("mount -o remount,rw /"); if (system(sedCommand) == 0) { fprintf(fl, "Setting up %s in MakeCode Arcade game's /sd/arcade.cfg with:\n%s\n\n", uinputMapperOrKeyboard, defaultEvent); + if (isRecalbox) system("mount -o remount,ro /"); } else { fprintf(fl, "Please check path or write permissions for /sd/arcade.cfg and try again.\n\n"); - if (isRecalbox == 1) { - system("mount -o remount,ro /"); - } goto cleanup; } - if (isRecalbox == 1) { - system("mount -o remount,ro /"); - } } - // Ready terminal and game files - int foundPxtFile = 0; - system("stty -ixon intr undef susp undef"); //Disable pause(CTRL+S), suspend(CTRL+Z) and interrupt(CTRL+C) in terminal - if (0 == system("head -1 /tmp/pxt-pid >>/dev/null 2>&1")) { - system("sed -i \"1s&.*&\"\"&\" /tmp/pxt-pid"); //Clear old pid from /tmp/pxt if present - foundPxtFile = 1; - } - if (strcmp("", getSystemOutput("ps -A | grep pulse"))) { - if (isRecalbox == 1) { - if (strcmp("RECALBOX 5", getSystemOutput("uname -a | tr ' ' '\\n' | grep RECALBOX | tr -d [:cntrl:]"))) { - system("killall pulseaudio >>/dev/null 2>&1"); //Kill PulseAudio if running below kernel 5, can sometimes halt game looking for ALSA - } - } else { - system("sudo killall pulseaudio >>/dev/null 2>&1"); //Kill PulseAudio if running on RPi OS/RetroPie, can sometimes halt game looking for ALSA - // Note: Pulseaudio used to restart automatically on kernels below 5, keep an eye on how this is handled > 5 on RPi OS/RetroPie - } - } + // Flush buffers before fork fflush(stdout); + fflush(fl); // Fork game execution on launch, so that it is executed // the same way it's done in-game on reset if (!fork()) { - // Switch console to graphics mode to avoid disturbing text output in borders - if (strcmp("not a tty", getSystemOutput("tty | tr -d [:cntrl:]"))) { - fd = open(path, O_RDWR, 0); - if (fd < 0) { - perror("unable to open tty"); - return 1; - } - if (ioctl(fd, KDSETMODE, KD_GRAPHICS) < 0) { - perror("warn: ioctl KDSETMODE failed"); - } - close(fd); - } - - // Run copy of game to circumvent Recalbox' read-only(/) and/or non-executablel(.../share/roms exFAT) file systems - if (isRecalbox == 1) { - memset (game, 0, sizeof(game)); - snprintf(game, 200, "/recalbox/share/bootvideos/makecode/%s", basename); //New location instead of /tmp allows for saving game states in settings and DB extensions etc. - } + // Switch console to graphics mode to avoid disturbing text output in borders and save original keyboard mode + setConsoleGraphicsMode(path, 2, 1, 3); - // Silence the game launch information to Linux console if verbose option is not given - char gameString[200]; - memcpy(gameString, game, strlen(game)+1); - if (strcmp("verbose", options)) { - fclose(fl); - strcat(game, " >>/tmp/McAirpos.log 2>&1"); + // Close and reopen FILE fl inside fork to disconnect from pre fork state + if (fl != NULL) fclose(fl); + fl = fopen(logfile, "a"); + if (fl < 0) { + fprintf(fl, "Unable to open %s\n", logfile); } // Launch the game @@ -357,11 +422,17 @@ int main(int argc, char** argv) { if ((launchInt == 36608) || (launchInt == 15)) { fprintf(fl, "%s was exited by the user or reset in-game\n\n", gameString); } else { - fprintf(fl, "Please check path to and executable permissions for game_file.elf and try again.\n\n"); + fprintf(fl, "Please check path to and executable permissions for %s and try again.\n\n", gameString); } //Alternative way to launch game, but I need to spawn new process in fork, not replace fork process //execl(game, game, NULL); + // Flush buffers and close FILE fl before child process ends + fflush(stdout); + fflush(fl); + if (fl != NULL) fclose(fl); + + // Main thread continues }else { @@ -373,10 +444,11 @@ int main(int argc, char** argv) { memset (processCommand, 0, sizeof(processCommand)); int whileCount = 0; int maxCount = 500; + do { - sleep(0.1); + sleep(0); //Sleep OS arbitrary short time and yield open for other threads whileCount++; - snprintf(processCommand, 100, "head -1 /proc/%s/comm >>/dev/null 2>&1", processID); + snprintf(processCommand, sizeof(processCommand), "head -1 /proc/%s/comm >>/dev/null 2>&1", processID); if (whileCount > maxCount) { fprintf(fl, "\nTimed out trying to find game's process ID...\n"); fprintf(fl, "If stuck, please read or open a related issue at https://github.com/Vegz78/McAirpos.\n"); @@ -385,7 +457,7 @@ int main(int argc, char** argv) { // Check until found if /tmp/pxt-pid file exists without opening it, time out otherwise if (0 == foundPxtFile) { if (0 != stat("/tmp/pxt-pid", &pxtFileBuffer)) { - if (whileCount > maxCount -1) { + if (whileCount > (maxCount - 1)) { fprintf(fl, "\nDid not find the file /tmp/pxt-pid...\n"); } continue; @@ -395,16 +467,16 @@ int main(int argc, char** argv) { } } // Set new game's processID from file /tmp/pxt-pid - snprintf(processID, 20, "%s", getSystemOutput("head -1 /tmp/pxt-pid | tr -d [:cntrl:]")); + snprintf(processID, sizeof(processID), "%s", getSystemOutput("head -1 /tmp/pxt-pid | tr -d [:cntrl:]")); } // Check whether processID from /tmp/pxt-pid is running/registered on system, time out otherwise while (0 != system(processCommand)); // Get name of game process - snprintf(processCommand, 100, "head -1 /proc/%s/comm 2>&1 | tr -d [:cntrl:]", processID); + snprintf(processCommand, sizeof(processCommand), "head -1 /proc/%s/comm 2>&1 | tr -d [:cntrl:]", processID); char processName[50]; memset (processName, 0, sizeof(processName)); - snprintf(processName, 50, "%s", getSystemOutput(processCommand)); + snprintf(processName, sizeof(processName), "%s", getSystemOutput(processCommand)); fprintf(fl, "\nFound running game process \"%s\" on PID=%s\n\n", processName, processID); @@ -413,87 +485,69 @@ int main(int argc, char** argv) { // Why does this not work without the printf?!? char processCheckCmd[100]; memset (processCheckCmd, 0, sizeof(processCheckCmd)); - snprintf(processCheckCmd, 100, "pgrep -n %s 2>&1 | tr -d [:cntrl:]", processName); + snprintf(processCheckCmd, sizeof(processCheckCmd), "pgrep -n %s 2>&1 | tr -d [:cntrl:]", processName); char newProcessID[20]; memset (newProcessID, 0, sizeof(newProcessID)); besure: while (strcmp(processID, "")) { //fprintf(fl, "%s@PID=%s is keeping launCharc alive...\n", processName, processID); sleep(2); - snprintf(newProcessID, 20, getSystemOutput(processCheckCmd)); + snprintf(newProcessID, sizeof(newProcessID), getSystemOutput(processCheckCmd)); if (strcmp(processID, newProcessID)) { if (strcmp(newProcessID, "")) { fprintf(fl, "\"%s\" restarted on PID=%s\n", processName, newProcessID); } else { fprintf(fl, "PID not found for \"%s\", trying again...\n", processName); } - snprintf(processID, 20, "%s", newProcessID); + snprintf(processID, sizeof(processID), "%s", newProcessID); } } // Doublecheck that the game really has exited sleep(3); - snprintf(processID, 20, getSystemOutput(processCheckCmd)); + snprintf(processID, sizeof(processID), getSystemOutput(processCheckCmd)); if (strcmp(processID, "")) {goto besure;} else {fprintf(fl, "PID still not found for \"%s\", game exited?\nTerminating launCharc...\n", processName);} // Kill any remaining/orphaned game processes before exit - char killAllCmd[100]; + char killAllCmd[200]; memset (killAllCmd, 0, sizeof(killAllCmd)); - if (strcmp("verbose", options)) { - snprintf(killAllCmd, 100, "killall -q %s >>/tmp/McAirpos.log 2>&1", processName); + if (!verbose) { + snprintf(killAllCmd, sizeof(killAllCmd), "killall -q %s >>%s 2>&1", processName, logfile); }else{ - snprintf(killAllCmd, 100, "killall %s 2>&1", processName); + snprintf(killAllCmd, sizeof(killAllCmd), "killall %s 2>&1", processName); } system(killAllCmd); + cleanup: - if (strcmp("verbose", options)) { - if (isRecalbox == 1) { - system("killall -q input-create >>/tmp/McAirpos.log 2>&1 && killall -q input-read >>/tmp/McAirpos.log 2>&1"); - }else { - system("sudo killall -q input-create >>/tmp/McAirpos.log 2>&1 && sudo killall -q input-read >>/tmp/McAirpos.log 2>&1"); - } + ;// Kill uinput-mapper processes + char killUInputCmd[200]; + memset (killUInputCmd, 0, sizeof(killUInputCmd)); + if (verbose) { + snprintf(killUInputCmd, sizeof(killUInputCmd), "%skillall input-create 2>&1 && %skillall input-read 2>&1", sudoer, sudoer); }else { - if (isRecalbox == 1) { - system("killall input-create 2>&1 && killall input-read 2>&1"); - }else { - system("sudo killall input-create 2>&1 && sudo killall input-read 2>&1"); - } + snprintf(killUInputCmd, sizeof(killUInputCmd), "%skillall -q input-create >>%s 2>&1 && %skillall -q input-read >>%s 2>&1", sudoer, logfile, sudoer, logfile); } + system(killUInputCmd); - // The following code is borrowed from https://github.com/hobbitalistair/termfix: - // Terminal Fixer's cleanup part, which returns control of the - // framebuffer and input mode to calling process after game's exit - if (strcmp("not a tty", getSystemOutput("tty | tr -d [:cntrl:]"))) { - fd = open(path, O_RDWR, 0); - if (fd < 0) { - perror("unable to open tty"); - return 1; - } - - // This one fails without sudo, but doesn't seem needed for - // MakeCode Arcade games(comment out or leave as and option?). - if (ioctl(fd, VT_UNLOCKSWITCH, 1) < 0) { - //perror("warn: ioctl VT_UNLOCKSWITCH failed"); - } - - if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) { - perror("warn: ioctl KDSETMODE failed"); - } - - if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) { - perror("warn: ioctl KBSKBMODE failed"); - } - }else if (isRecalbox == 1) { + // Flush buffers, clean up and restore terminal to original state before exit + if (isRecalbox) { + system("mount -o remount,ro /"); + // Show Recalbox loading screen while returning to EmulationStation system("/usr/bin/fbv2 /recalbox/system/resources/splash/logo-version.png >>/dev/null 2>&1"); + // Set terminal to text mode, keyboard to original mode and allow switching virtual terminals + setConsoleGraphicsMode(path, 3, 2, 4, 5); + }else { + // Set terminal to text mode and keyboard to original mode + setConsoleGraphicsMode(path, 2, 2, 4); } - system("stty sane"); - fclose(fl); - if (strcmp("verbose", options)) { - system("clear"); - } + system("stty sane&&unset ignoreeof"); + fflush(stdout); + fflush(fl); + if (fl != NULL) fclose(fl); + if (!verbose) system("clear"); } return 0;