diff --git a/.gitignore b/.gitignore index beb171c..308b7ef 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ build-*/ # Visual Studio .vs/ + +# Geode resources +!resources/* \ No newline at end of file diff --git a/changelog.md b/changelog.md index 6dccf27..9c70d67 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,10 @@ -# v1.1.22 +# v1.2.0 +* Potentially fix slope bugs * Potentially improve performance +* Fix keyboard input not working on Linux +* Significantly improve input precision on Linux +* Don't submit CBF completions to leaderboards anymore # v1.1.21 diff --git a/mod.json b/mod.json index ac9c0d1..9d5a0b1 100644 --- a/mod.json +++ b/mod.json @@ -1,9 +1,9 @@ { - "geode": "3.4.0", + "geode": "3.5.0", "gd": { "win": "2.206" }, - "version": "v1.1.22", + "version": "v1.2.0", "id": "syzzi.click_between_frames", "name": "Click Between Frames", "developer": "syzzi", @@ -59,5 +59,11 @@ "default": true } }, - "repository": "https://github.com/theyareonit/Click-Between-Frames" + "repository": "https://github.com/theyareonit/Click-Between-Frames", + "resources": { + "files": [ + "resources/linux-input.exe", + "resources/linux-input.exe.so" + ] + } } \ No newline at end of file diff --git a/resources/linux-input.exe b/resources/linux-input.exe new file mode 100644 index 0000000..4846cb0 --- /dev/null +++ b/resources/linux-input.exe @@ -0,0 +1,36 @@ +#!/bin/sh + +appname="linux-input.exe.so" +# determine the application directory +appdir='' +case "$0" in + */*) + # $0 contains a path, use it + appdir=`dirname "$0"` + ;; + *) + # no directory in $0, search in PATH + saved_ifs=$IFS + IFS=: + for d in $PATH + do + IFS=$saved_ifs + if [ -x "$d/$appname" ]; then appdir="$d"; break; fi + done + ;; +esac + +# figure out the full app path +if [ -n "$appdir" ]; then + apppath="$appdir/$appname" + WINEDLLPATH="$appdir:$WINEDLLPATH" + export WINEDLLPATH +else + apppath="$appname" +fi + +# determine the WINELOADER +if [ ! -x "$WINELOADER" ]; then WINELOADER="wine"; fi + +# and try to start the app +exec "$WINELOADER" "$apppath" "$@" diff --git a/resources/linux-input.exe.so b/resources/linux-input.exe.so new file mode 100644 index 0000000..1023de7 Binary files /dev/null and b/resources/linux-input.exe.so differ diff --git a/src/includes.hpp b/src/includes.hpp index 11147a1..2595d70 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -17,21 +17,23 @@ enum GameAction : int { p2Right = 5 }; -enum Player : bool { - Player1 = 0, - Player2 = 1 -}; - enum State : bool { Press = 0, Release = 1 }; +struct __attribute__((packed)) LinuxInputEvent { + LARGE_INTEGER time; + USHORT type; + USHORT code; + int value; +}; + struct InputEvent { LARGE_INTEGER time; PlayerButton inputType; bool inputState; - bool player; + bool isPlayer1; }; struct Step { @@ -40,7 +42,12 @@ struct Step { bool endStep; }; +extern HANDLE hSharedMem; +extern HANDLE hMutex; +extern LPVOID pBuf; + extern std::queue inputQueue; +extern std::queue inputQueueCopy; extern std::array, 6> inputBinds; extern std::unordered_set heldInputs; @@ -50,5 +57,9 @@ extern std::mutex keybindsLock; extern std::atomic enableRightClick; extern bool threadPriority; +extern bool isLinux; + +constexpr size_t BUFFER_SIZE = 20; +void linuxCheckInputs(); void inputThread(); \ No newline at end of file diff --git a/src/input.cpp b/src/input.cpp index 1369a42..d010ef0 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -15,7 +15,7 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) LARGE_INTEGER time; PlayerButton inputType; bool inputState; - bool player; + bool player1; QueryPerformanceCounter(&time); @@ -48,23 +48,21 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } bool shouldEmplace = true; - player = Player1; + player1 = true; - std::array, 6> binds; { std::lock_guard lock(keybindsLock); - binds = inputBinds; - } - if (binds[p1Jump].contains(vkey)) inputType = PlayerButton::Jump; - else if (binds[p1Left].contains(vkey)) inputType = PlayerButton::Left; - else if (binds[p1Right].contains(vkey)) inputType = PlayerButton::Right; - else { - player = Player2; - if (binds[p2Jump].contains(vkey)) inputType = PlayerButton::Jump; - else if (binds[p2Left].contains(vkey)) inputType = PlayerButton::Left; - else if (binds[p2Right].contains(vkey)) inputType = PlayerButton::Right; - else shouldEmplace = false; + if (inputBinds[p1Jump].contains(vkey)) inputType = PlayerButton::Jump; + else if (inputBinds[p1Left].contains(vkey)) inputType = PlayerButton::Left; + else if (inputBinds[p1Right].contains(vkey)) inputType = PlayerButton::Right; + else { + player1 = false; + if (inputBinds[p2Jump].contains(vkey)) inputType = PlayerButton::Jump; + else if (inputBinds[p2Left].contains(vkey)) inputType = PlayerButton::Left; + else if (inputBinds[p2Right].contains(vkey)) inputType = PlayerButton::Right; + else shouldEmplace = false; + } } if (!inputState) heldInputs.emplace(vkey); @@ -76,13 +74,13 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) USHORT flags = raw->data.mouse.usButtonFlags; bool shouldEmplace = true; - player = Player1; + player1 = true; inputType = PlayerButton::Jump; if (flags & RI_MOUSE_BUTTON_1_DOWN) inputState = Press; else if (flags & RI_MOUSE_BUTTON_1_UP) inputState = Release; else { - player = Player2; + player1 = false; if (!enableRightClick.load()) return 0; if (flags & RI_MOUSE_BUTTON_2_DOWN) inputState = Press; else if (flags & RI_MOUSE_BUTTON_2_UP) inputState = Release; @@ -101,14 +99,13 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { std::lock_guard lock(inputQueueLock); - inputQueue.emplace(InputEvent{ time, inputType, inputState, player }); + inputQueue.emplace(InputEvent{ time, inputType, inputState, player1 }); } return 0; } void inputThread() { - WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; wc.hInstance = GetModuleHandleA(NULL); @@ -144,4 +141,51 @@ void inputThread() { while (GetMessage(&msg, hwnd, 0, 0)) { DispatchMessage(&msg); } +} + +void linuxCheckInputs() { + DWORD waitResult = WaitForSingleObject(hMutex, 1); + if (waitResult == WAIT_OBJECT_0) { + LinuxInputEvent* events = static_cast(pBuf); + for (int i = 0; i < BUFFER_SIZE; i++) { + if (events[i].type == 0) break; + + InputEvent input; + bool player1 = true; + + USHORT scanCode = events[i].code; + if (scanCode == 0x3110) { // left click + input.inputType = PlayerButton::Jump; + } + else if (scanCode == 0x3111) { // right click + if (!enableRightClick.load()) continue; + input.inputType = PlayerButton::Jump; + player1 = false; + } + else { + USHORT keyCode = MapVirtualKeyExA(scanCode, MAPVK_VSC_TO_VK, GetKeyboardLayout(0)); + if (inputBinds[p1Jump].contains(keyCode)) input.inputType = PlayerButton::Jump; + else if (inputBinds[p1Left].contains(keyCode)) input.inputType = PlayerButton::Left; + else if (inputBinds[p1Right].contains(keyCode)) input.inputType = PlayerButton::Right; + else { + player1 = false; + if (inputBinds[p2Jump].contains(keyCode)) input.inputType = PlayerButton::Jump; + else if (inputBinds[p2Left].contains(keyCode)) input.inputType = PlayerButton::Left; + else if (inputBinds[p2Right].contains(keyCode)) input.inputType = PlayerButton::Right; + else continue; + } + } + + input.inputState = !events[i].value; + input.time = events[i].time; + input.isPlayer1 = player1; + + inputQueueCopy.emplace(input); + } + ZeroMemory(events, sizeof(LinuxInputEvent[BUFFER_SIZE])); + ReleaseMutex(hMutex); + } + else if (waitResult != WAIT_TIMEOUT) { + log::error("WaitForSingleObject failed: {}", GetLastError()); + } } \ No newline at end of file diff --git a/src/linux/Makefile b/src/linux/Makefile new file mode 100644 index 0000000..941e588 --- /dev/null +++ b/src/linux/Makefile @@ -0,0 +1,112 @@ +### Generated by Winemaker 0.8.4 +### +### Invocation command line was +### /usr/bin/winemaker -iws2_32 -ievdev . --single-target linux-input + + +SRCDIR = . +SUBDIRS = +DLLS = +LIBS = +EXES = linux-input + + + +### Common settings + +CEXTRA = +CXXEXTRA = +RCEXTRA = +DEFINES = +INCLUDE_PATH = +DLL_PATH = +DLL_IMPORTS = ws2_32 \ + evdev +LIBRARY_PATH = +LIBRARIES = + + +### linux-input sources and settings + +linux_input_MODULE = linux-input +linux_input_C_SRCS = +linux_input_CXX_SRCS = linux-input.cpp +linux_input_RC_SRCS = +linux_input_LDFLAGS = +linux_input_ARFLAGS = +linux_input_DLL_PATH = +linux_input_DLLS = +linux_input_LIBRARY_PATH= +linux_input_LIBRARIES = + +linux_input_OBJS = $(linux_input_C_SRCS:.c=.o) \ + $(linux_input_CXX_SRCS:.cpp=.o) \ + $(linux_input_RC_SRCS:.rc=.res) + + + +### Global source lists + +C_SRCS = $(linux_input_C_SRCS) +CXX_SRCS = $(linux_input_CXX_SRCS) +RC_SRCS = $(linux_input_RC_SRCS) + + +### Tools + +CC = winegcc +CXX = wineg++ +RC = wrc +AR = ar + + +### Generic targets + +all: $(SUBDIRS) $(DLLS:%=%.so) $(LIBS) $(EXES) + +### Build rules + +.PHONY: all clean dummy + +$(SUBDIRS): dummy + @cd $@ && $(MAKE) + +# Implicit rules + +.SUFFIXES: .cpp .cxx .rc .res +DEFINCL = $(INCLUDE_PATH) $(DEFINES) $(OPTIONS) + +.c.o: + $(CC) -c $(CFLAGS) $(CEXTRA) $(DEFINCL) -o $@ $< + +.cpp.o: + $(CXX) -c $(CXXFLAGS) $(CXXEXTRA) $(DEFINCL) -o $@ $< + +.cxx.o: + $(CXX) -c $(CXXFLAGS) $(CXXEXTRA) $(DEFINCL) -o $@ $< + +.rc.res: + $(RC) $(RCFLAGS) $(RCEXTRA) $(DEFINCL) -fo$@ $< + +# Rules for cleaning + +CLEAN_FILES = y.tab.c y.tab.h lex.yy.c core *.orig *.rej \ + \\\#*\\\# *~ *% .\\\#* + +clean:: $(SUBDIRS:%=%/__clean__) $(EXTRASUBDIRS:%=%/__clean__) + $(RM) $(CLEAN_FILES) $(RC_SRCS:.rc=.res) $(C_SRCS:.c=.o) $(CXX_SRCS:.cpp=.o) + $(RM) $(DLLS:%=%.so) $(LIBS) $(EXES) $(EXES:%=%.so) + +$(SUBDIRS:%=%/__clean__): dummy + cd `dirname $@` && $(MAKE) clean + +$(EXTRASUBDIRS:%=%/__clean__): dummy + -cd `dirname $@` && $(RM) $(CLEAN_FILES) + +### Target specific build rules +DEFLIB = $(LIBRARY_PATH) $(LIBRARIES) $(DLL_PATH) $(DLL_IMPORTS:%=-l%) + +$(linux_input_MODULE): $(linux_input_OBJS) + $(CXX) $(linux_input_LDFLAGS) -o $@ $(linux_input_OBJS) $(linux_input_LIBRARY_PATH) $(linux_input_DLL_PATH) $(DEFLIB) $(linux_input_DLLS:%=-l%) $(linux_input_LIBRARIES:%=-l%) + + diff --git a/src/linux/build.sh b/src/linux/build.sh new file mode 100644 index 0000000..cf511f7 --- /dev/null +++ b/src/linux/build.sh @@ -0,0 +1,5 @@ +# too lazy to make this into a github workflow +winemaker -iws2_32 -ievdev . --single-target linux-input +make +cp linux-input.exe ../../resources/linux-input.exe +cp linux-input.exe.so ../../resources/linux-input.exe.so diff --git a/src/linux/linux-input.cpp b/src/linux/linux-input.cpp new file mode 100644 index 0000000..6672c78 --- /dev/null +++ b/src/linux/linux-input.cpp @@ -0,0 +1,225 @@ +#include // thankfully this can be put before windows.h, it bugs otherwise +#include // winelib lets you use both windows and linux apis + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct __attribute__((packed)) LinuxInputEvent { + LARGE_INTEGER time; + USHORT type; + USHORT code; + int value; +}; + +constexpr size_t BUFFER_SIZE = 20; +constexpr int MAX_EVENTS = 10; + +std::atomic should_quit{false}; + +void stop(int i) { + should_quit.store(true); +} + +LARGE_INTEGER convert_time(timeval t) { // convert timeval to windows file time + LARGE_INTEGER wft; + wft.QuadPart = ((static_cast(t.tv_sec) + 11644473600) * 10000000) + (t.tv_usec * 10); // hopefully wine doesnt change how this is calculated + return wft; +} + +USHORT convert_scan_code(USHORT code) { + static const std::array special_codes = []() { + std::array map{}; + map[96 - 96] = 0xE01C; // KPENTER + map[97 - 96] = 0xE01D; // RIGHTCTRL + map[98 - 96] = 0xE035; // KPSLASH + map[100 - 96] = 0xE038; // RIGHTALT + map[102 - 96] = 0xE047; // HOME + map[103 - 96] = 0xE048; // UP + map[104 - 96] = 0xE049; // PAGEUP + map[105 - 96] = 0xE04B; // LEFT + map[106 - 96] = 0xE04D; // RIGHT + map[107 - 96] = 0xE04F; // END + map[108 - 96] = 0xE050; // DOWN + map[109 - 96] = 0xE051; // PAGEDOWN + map[110 - 96] = 0xE052; // INSERT + map[111 - 96] = 0xE053; // DELETE + map[113 - 96] = 0xE020; // MUTE + map[114 - 96] = 0xE02E; // VOLUMEDOWN + map[115 - 96] = 0xE030; // VOLUMEUP + return map; + }(); + + return (code > 96) && (code < 116) ? special_codes[code - 96] : code; +} + +DWORD WINAPI gd_watchdog(LPVOID) { // CreateProcess doesn't return a handle for linux-input.exe, so a job object to auto-close linux-input.exe isn't an option + HANDLE gdMutex = OpenMutex(SYNCHRONIZE, FALSE, "CBFWatchdogMutex"); + if (gdMutex == NULL) { + std::cerr << "[CBF] Failed to open mutex: " << GetLastError() << std::endl; + should_quit.store(true); + return 1; + } + WaitForSingleObject(gdMutex, INFINITE); + ReleaseMutex(gdMutex); + should_quit.store(true); + return 0; +} + +int main() { + std::cerr << "[CBF] Linux input program started" << std::endl; + std::vector devices; + + int epoll_fd = epoll_create1(0); + if (epoll_fd == -1) { + std::cerr << "[CBF] Failed to create epoll instance: " << strerror(errno) << std::endl; + return 1; + } + + for (int i = 0;; i++) { + std::string path = "/dev/input/event" + std::to_string(i); + int fd = open(path.c_str(), O_RDONLY); + if (fd == -1) break; + + libevdev* dev = nullptr; + int rc = libevdev_new_from_fd(fd, &dev); + if (rc < 0) { + std::cerr << "[CBF] Failed to create evdev device: " << strerror(-rc) << std::endl; + close(fd); + continue; + } + + if (libevdev_get_id_bustype(dev) == BUS_USB || libevdev_get_id_bustype(dev) == BUS_BLUETOOTH) { + devices.push_back(dev); + + epoll_event ev; + ev.events = EPOLLIN; + ev.data.ptr = dev; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + std::cerr << "[CBF] Failed to add fd to epoll: " << strerror(errno) << std::endl; + libevdev_free(dev); + close(fd); + continue; + } + } else { + libevdev_free(dev); + close(fd); + } + } + + signal(SIGINT, stop); + signal(SIGTERM, stop); + + HANDLE hSharedMem = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "LinuxSharedMemory"); + if (hSharedMem == NULL) { + std::cerr << "[CBF] Failed to open file mapping: " << GetLastError() << std::endl; + return 1; + } + + LPVOID pBuf = MapViewOfFile(hSharedMem, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinuxInputEvent) * BUFFER_SIZE); + if (pBuf == NULL) { + std::cerr << "[CBF] Failed to map view of file: " << GetLastError() << std::endl; + CloseHandle(hSharedMem); + return 1; + } + + LinuxInputEvent* shared_events = static_cast(pBuf); + + HANDLE hMutex = OpenMutex(SYNCHRONIZE, FALSE, "CBFLinuxMutex"); + if (hMutex == NULL) { + std::cerr << "[CBF] Failed to open mutex: " << GetLastError() << std::endl; + UnmapViewOfFile(pBuf); + CloseHandle(hSharedMem); + return 1; + } + + if (devices.empty()) { + std::cerr << "[CBF] No input devices" << std::endl; + close(epoll_fd); + + DWORD waitResult = WaitForSingleObject(hMutex, 1000); + if (waitResult == WAIT_OBJECT_0) { + shared_events[0].type = 3; // cant access input devices + ReleaseMutex(hMutex); + } + else if (waitResult != WAIT_TIMEOUT) { + std::cerr << "[CBF] Failed to acquire mutex: " << GetLastError() << std::endl; + } + + return 1; + } + + std::cerr << "[CBF] Waiting for input events" << std::endl; + CreateThread(NULL, 0, gd_watchdog, NULL, 0, NULL); + + epoll_event events[MAX_EVENTS]; + + while (!should_quit.load()) { + int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, 100); + if (nfds == -1) { + if (errno == EINTR) continue; // timeout + std::cerr << "[CBF] Failed to epoll_wait: " << strerror(errno) << std::endl; + break; + } + + for (int n = 0; n < nfds; ++n) { + struct libevdev* dev = static_cast(events[n].data.ptr); + struct input_event ev; + + while (libevdev_has_event_pending(dev)) { + int rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (rc != -EAGAIN && rc != 0) { + std::cerr << "[CBF] Error reading event: " << strerror(-rc) << std::endl; + break; + } + if (ev.type == EV_KEY && ev.value != 2) { // Exclude autorepeat + LARGE_INTEGER time = convert_time(ev.time); + USHORT code; + + if (libevdev_has_event_type(dev, EV_REL)) code = ev.code + 0x3000; // if mouse + else code = convert_scan_code(ev.code); + + DWORD waitResult = WaitForSingleObject(hMutex, 1000); + if (waitResult == WAIT_OBJECT_0) { + for (int i = 0; i < BUFFER_SIZE; i++) { + if (shared_events[i].type == 0) { // if there is room in the buffer + shared_events[i].time = time; + shared_events[i].type = ev.type; + shared_events[i].code = code; + shared_events[i].value = ev.value; + break; + } + } + ReleaseMutex(hMutex); + } + else if (waitResult != WAIT_TIMEOUT) { + std::cerr << "[CBF] Failed to acquire mutex: " << GetLastError() << std::endl; + } + } + } + } + } + + for (auto dev : devices) { + int fd = libevdev_get_fd(dev); + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, nullptr); + libevdev_free(dev); + close(fd); + } + + close(epoll_fd); + + UnmapViewOfFile(pBuf); + CloseHandle(hSharedMem); + CloseHandle(hMutex); + + std::cerr << "[CBF] Linux input program exiting" << std::endl; + return 0; +} diff --git a/src/main.cpp b/src/main.cpp index c86e43a..b46ee91 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,9 +10,12 @@ #include #include #include +#include #include +typedef void (*wine_get_host_version)(const char **sysname, const char **release); + constexpr double smallestFloat = std::numeric_limits::min(); const InputEvent emptyInput = InputEvent{ 0, 0, PlayerButton::Jump, 0, 0 }; @@ -27,9 +30,13 @@ LARGE_INTEGER lastFrameTime; LARGE_INTEGER lastPhysicsFrameTime; LARGE_INTEGER currentFrameTime; +HANDLE hSharedMem = NULL; +HANDLE hMutex = NULL; + bool firstFrame = true; bool skipUpdate = true; bool enableInput = false; +bool isLinux = false; bool lateCutoff; void updateInputQueueAndTime(int stepCount) { @@ -48,7 +55,11 @@ void updateInputQueueAndTime(int stepCount) { lastFrameTime = lastPhysicsFrameTime; stepQueue = {}; // just in case - { + if (isLinux) { + GetSystemTimePreciseAsFileTime((FILETIME*)¤tFrameTime); + linuxCheckInputs(); + } + else { std::lock_guard lock(inputQueueLock); if (lateCutoff) { @@ -113,7 +124,7 @@ Step updateDeltaFactorAndInput() { PlayLayer* playLayer = PlayLayer::get(); enableInput = true; - playLayer->handleButton(!nextInput.inputState, (int)nextInput.inputType, !nextInput.player); + playLayer->handleButton(!nextInput.inputState, (int)nextInput.inputType, nextInput.isPlayer1); enableInput = false; } @@ -189,7 +200,7 @@ class $modify(CCDirector) { inputQueueCopy = {}; - { + if (!isLinux) { std::lock_guard lock(inputQueueLock); inputQueue = {}; } @@ -280,8 +291,9 @@ class $modify(PlayerObject) { if (p1NotBuffering) { PlayerObject::update(newTimeFactor); if (!step.endStep) { - if (firstLoop && (this->m_yVelocity < 0 ^ this->m_isUpsideDown)) this->m_isOnGround = p1StartedOnGround; // this fixes delayed inputs on platforms moving down for some reason + if (firstLoop && ((this->m_yVelocity < 0) ^ this->m_isUpsideDown)) this->m_isOnGround = p1StartedOnGround; // this fixes delayed inputs on platforms moving down for some reason if (!this->m_isOnSlope || this->m_isDart) pl->checkCollisions(this, 0.0f, true); + else pl->checkCollisions(this, 0.25f, true); PlayerObject::updateRotation(newTimeFactor); newResetCollisionLog(this); } @@ -294,8 +306,9 @@ class $modify(PlayerObject) { if (p2NotBuffering) { p2->update(newTimeFactor); if (!step.endStep) { - if (firstLoop && (p2->m_yVelocity < 0 ^ p2->m_isUpsideDown)) p2->m_isOnGround = p2StartedOnGround; + if (firstLoop && ((p2->m_yVelocity < 0) ^ p2->m_isUpsideDown)) p2->m_isOnGround = p2StartedOnGround; if (!p2->m_isOnSlope || p2->m_isDart) pl->checkCollisions(p2, 0.0f, true); + else pl->checkCollisions(p2, 0.25f, true); p2->updateRotation(newTimeFactor); newResetCollisionLog(p2); } @@ -357,6 +370,36 @@ class $modify(EndLevelLayer) { } }; +LPVOID pBuf; + +class $modify(CreatorLayer) { + bool init() { + if (!CreatorLayer::init()) return false; + + DWORD waitResult = WaitForSingleObject(hMutex, 5); + if (waitResult == WAIT_OBJECT_0) { + if (static_cast(pBuf)[0].type == 3 && !softToggle) { + log::error("Linux input failed"); + FLAlertLayer* popup = FLAlertLayer::create( + "CBF Linux", + "Failed to read input devices.\nOn most distributions, this can be resolved by adding yourself to the input group (this will make your system slightly less secure).\nIf the issue persists, please contact the mod developer.", + "OK" + ); + popup->m_scene = this; + popup->show(); + } + ReleaseMutex(hMutex); + } + else if (waitResult == WAIT_TIMEOUT) { + log::error("Mutex stalling"); + } + else { + log::error("CreatorLayer WaitForSingleObject failed: {}", GetLastError()); + } + return true; + } +}; + Patch* patch; void toggleMod(bool disable) { @@ -376,6 +419,8 @@ void toggleMod(bool disable) { softToggle = disable; } +HANDLE gdMutex; + $on_mod(Loaded) { toggleMod(Mod::get()->getSettingValue("soft-toggle")); listenForSettingChanges("soft-toggle", toggleMod); @@ -392,5 +437,66 @@ void toggleMod(bool disable) { threadPriority = Mod::get()->getSettingValue("thread-priority"); - std::thread(inputThread).detach(); + HMODULE ntdll = GetModuleHandle("ntdll.dll"); + wine_get_host_version wghv = (wine_get_host_version)GetProcAddress(ntdll, "wine_get_host_version"); + if (wghv) { + const char* sysname; + const char* release; + wghv(&sysname, &release); + + std::string sys = sysname; + log::info("Wine {}", sys); + if (sys == "Linux") { // background raw keyboard input doesn't work in Wine + isLinux = true; + + hSharedMem = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(LinuxInputEvent[BUFFER_SIZE]), "LinuxSharedMemory"); + if (hSharedMem == NULL) { + log::error("Failed to create file mapping: {}", GetLastError()); + return; + } + + pBuf = MapViewOfFile(hSharedMem, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinuxInputEvent[BUFFER_SIZE])); + if (pBuf == NULL) { + log::error("Failed to map view of file: {}", GetLastError()); + CloseHandle(hSharedMem); + return; + } + + hMutex = CreateMutex(NULL, FALSE, "CBFLinuxMutex"); + if (hMutex == NULL) { + log::error("Failed to create shared memory mutex: {}", GetLastError()); + CloseHandle(hSharedMem); + return; + } + + gdMutex = CreateMutex(NULL, TRUE, "CBFWatchdogMutex"); // will be released when gd closes + if (gdMutex == NULL) { + log::error("Failed to create watchdog mutex: {}", GetLastError()); + CloseHandle(hMutex); + CloseHandle(hSharedMem); + return; + } + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + if (!CreateProcess(CCFileUtils::get()->fullPathForFilename("linux-input.exe"_spr, true).c_str(), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { + log::error("Failed to launch Linux input program: {}", GetLastError()); + CloseHandle(hMutex); + CloseHandle(gdMutex); + CloseHandle(hSharedMem); + return; + } + } + } + + if (!isLinux) std::thread(inputThread).detach(); }