#include "mungal4.h" #pragma hdrstop #include "l4padbindings.h" #include #include //######################################################################## // Name tables: .NET Keys names (what vRIO uses) to virtual keys, pad // names to XInput masks. Matching is case-insensitive. //######################################################################## namespace { struct NameValue { const char *name; int value; }; const NameValue kKeyNames[] = { // letters and digits are generated in LookupKey { "F1", VK_F1 }, { "F2", VK_F2 }, { "F3", VK_F3 }, { "F4", VK_F4 }, { "F5", VK_F5 }, { "F6", VK_F6 }, { "F7", VK_F7 }, { "F8", VK_F8 }, { "F9", VK_F9 }, { "F10", VK_F10 }, { "F11", VK_F11 }, { "F12", VK_F12 }, { "NumPad0", VK_NUMPAD0 }, { "NumPad1", VK_NUMPAD1 }, { "NumPad2", VK_NUMPAD2 }, { "NumPad3", VK_NUMPAD3 }, { "NumPad4", VK_NUMPAD4 }, { "NumPad5", VK_NUMPAD5 }, { "NumPad6", VK_NUMPAD6 }, { "NumPad7", VK_NUMPAD7 }, { "NumPad8", VK_NUMPAD8 }, { "NumPad9", VK_NUMPAD9 }, { "Divide", VK_DIVIDE }, { "Multiply", VK_MULTIPLY }, { "Subtract", VK_SUBTRACT }, { "Add", VK_ADD }, { "Decimal", VK_DECIMAL }, { "Up", VK_UP }, { "Down", VK_DOWN }, { "Left", VK_LEFT }, { "Right", VK_RIGHT }, { "Space", VK_SPACE }, { "Return", VK_RETURN }, { "Enter", VK_RETURN }, { "Escape", VK_ESCAPE }, { "Tab", VK_TAB }, { "Back", VK_BACK }, { "Home", VK_HOME }, { "End", VK_END }, { "PageUp", VK_PRIOR }, { "PageDown", VK_NEXT }, { "Prior", VK_PRIOR }, { "Next", VK_NEXT }, { "Insert", VK_INSERT }, { "Delete", VK_DELETE }, { "ShiftKey", VK_SHIFT }, { "Shift", VK_SHIFT }, { "ControlKey", VK_CONTROL }, { "Ctrl", VK_CONTROL }, { "Control", VK_CONTROL }, { "Menu", VK_MENU }, { "Alt", VK_MENU }, { "OemMinus", VK_OEM_MINUS }, { "Oemplus", VK_OEM_PLUS }, { "Oemcomma", VK_OEM_COMMA }, { "OemPeriod", VK_OEM_PERIOD }, { "OemOpenBrackets", VK_OEM_4 }, { "OemCloseBrackets", VK_OEM_6 }, { "OemSemicolon", VK_OEM_1 }, { "OemQuotes", VK_OEM_7 }, { "OemQuestion", VK_OEM_2 }, { "OemPipe", VK_OEM_5 }, { "Oemtilde", VK_OEM_3 }, }; const NameValue kPadButtonNames[] = { { "A", XINPUT_GAMEPAD_A }, { "B", XINPUT_GAMEPAD_B }, { "X", XINPUT_GAMEPAD_X }, { "Y", XINPUT_GAMEPAD_Y }, { "DPadUp", XINPUT_GAMEPAD_DPAD_UP }, { "DPadDown", XINPUT_GAMEPAD_DPAD_DOWN }, { "DPadLeft", XINPUT_GAMEPAD_DPAD_LEFT }, { "DPadRight", XINPUT_GAMEPAD_DPAD_RIGHT }, { "Start", XINPUT_GAMEPAD_START }, { "Back", XINPUT_GAMEPAD_BACK }, { "LeftShoulder", XINPUT_GAMEPAD_LEFT_SHOULDER }, { "RightShoulder", XINPUT_GAMEPAD_RIGHT_SHOULDER }, { "LeftThumb", XINPUT_GAMEPAD_LEFT_THUMB }, { "RightThumb", XINPUT_GAMEPAD_RIGHT_THUMB }, }; const NameValue kPadAxisNames[] = { { "LeftStickX", BindPadLeftStickX }, { "LeftStickY", BindPadLeftStickY }, { "RightStickX", BindPadRightStickX }, { "RightStickY", BindPadRightStickY }, { "LeftTrigger", BindPadLeftTrigger }, { "RightTrigger", BindPadRightTrigger }, }; const NameValue kRioAxisNames[] = { { "Throttle", BindAxisThrottle }, { "LeftPedal", BindAxisLeftPedal }, { "RightPedal", BindAxisRightPedal }, { "JoystickY", BindAxisJoystickY }, { "JoystickX", BindAxisJoystickX }, }; Logical NameEquals(const char *a, const char *b) { return _stricmp(a, b) == 0; } int LookupTable(const NameValue *table, int count, const char *name) { for (int i = 0; i < count; ++i) { if (NameEquals(table[i].name, name)) { return table[i].value; } } return -1; } int LookupKey(const char *name) { // single letter A-Z (VK == uppercase ASCII) if (name[1] == '\0' && ((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z'))) { return toupper(name[0]); } // digit row: D0-D9 (VK == '0'-'9') if ((name[0] == 'D' || name[0] == 'd') && name[1] >= '0' && name[1] <= '9' && name[2] == '\0') { return name[1]; } return LookupTable(kKeyNames, sizeof(kKeyNames) / sizeof(kKeyNames[0]), name); } Logical ParseAddress(const char *text, int *address) { int value = -1; if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) { if (sscanf(text + 2, "%x", &value) != 1) { return False; } } else if (sscanf(text, "%d", &value) != 1) { return False; } if ((value >= 0x00 && value <= 0x47) || (value >= 0x50 && value <= 0x6F)) { *address = value; return True; } return False; } Logical ParseNumber(const char *text, Scalar *value) { float parsed; if (sscanf(text, "%f", &parsed) != 1) { return False; } *value = (Scalar) parsed; return True; } //--------------------------------------------------------------- // Line tokenizer: whitespace separated, '#' starts a comment //--------------------------------------------------------------- int Tokenize(char *line, char *tokens[], int max_tokens) { char *hash = strchr(line, '#'); if (hash != NULL) { *hash = '\0'; } int count = 0; char *cursor = line; while (count < max_tokens) { while (*cursor == ' ' || *cursor == '\t' || *cursor == '\r' || *cursor == '\n') { ++cursor; } if (*cursor == '\0') { break; } tokens[count++] = cursor; while (*cursor != '\0' && *cursor != ' ' && *cursor != '\t' && *cursor != '\r' && *cursor != '\n') { ++cursor; } if (*cursor != '\0') { *cursor++ = '\0'; } } return count; } //--------------------------------------------------------------- // One line of the profile grammar //--------------------------------------------------------------- Logical ParseLine(char *tokens[], int token_count, PadBindingProfile *profile) { if (token_count < 4) { return False; } if (NameEquals(tokens[0], "key") && NameEquals(tokens[2], "button")) { int key = LookupKey(tokens[1]); int address; if (key < 0 || !ParseAddress(tokens[3], &address) || profile->keyButtonCount >= PadBindingProfile::maxKeyButtons) { return False; } Logical toggle = (token_count > 4 && NameEquals(tokens[4], "toggle")); PadKeyButtonBinding *binding = &profile->keyButtons[profile->keyButtonCount++]; memset(binding, 0, sizeof(*binding)); binding->virtualKey = key; binding->address = address; binding->toggle = toggle; return True; } if (NameEquals(tokens[0], "key") && NameEquals(tokens[2], "axis")) { if (token_count < 6) { return False; } int key = LookupKey(tokens[1]); int axis = LookupTable(kRioAxisNames, sizeof(kRioAxisNames) / sizeof(kRioAxisNames[0]), tokens[3]); int mode = NameEquals(tokens[4], "deflect") ? BindKeyDeflect : NameEquals(tokens[4], "rate") ? BindKeyRate : -1; Scalar value; if (key < 0 || axis < 0 || mode < 0 || !ParseNumber(tokens[5], &value) || profile->keyAxisCount >= PadBindingProfile::maxKeyAxes) { return False; } PadKeyAxisBinding *binding = &profile->keyAxes[profile->keyAxisCount++]; binding->virtualKey = key; binding->axis = axis; binding->mode = mode; binding->value = value; return True; } if (NameEquals(tokens[0], "pad") && NameEquals(tokens[2], "button")) { int mask = LookupTable(kPadButtonNames, sizeof(kPadButtonNames) / sizeof(kPadButtonNames[0]), tokens[1]); int address; if (mask < 0 || !ParseAddress(tokens[3], &address) || profile->padButtonCount >= PadBindingProfile::maxPadButtons) { return False; } Logical toggle = (token_count > 4 && NameEquals(tokens[4], "toggle")); PadPadButtonBinding *binding = &profile->padButtons[profile->padButtonCount++]; memset(binding, 0, sizeof(*binding)); binding->padMask = (unsigned short) mask; binding->address = address; binding->toggle = toggle; return True; } if (NameEquals(tokens[0], "padaxis") && NameEquals(tokens[2], "axis")) { int source = LookupTable(kPadAxisNames, sizeof(kPadAxisNames) / sizeof(kPadAxisNames[0]), tokens[1]); int axis = LookupTable(kRioAxisNames, sizeof(kRioAxisNames) / sizeof(kRioAxisNames[0]), tokens[3]); if (source < 0 || axis < 0 || profile->padAxisCount >= PadBindingProfile::maxPadAxes) { return False; } PadPadAxisBinding *binding = &profile->padAxes[profile->padAxisCount++]; memset(binding, 0, sizeof(*binding)); binding->source = source; binding->axis = axis; for (int i = 4; i < token_count; ++i) { if (NameEquals(tokens[i], "invert")) { binding->invert = True; } else if (NameEquals(tokens[i], "deadzone") && i + 1 < token_count) { if (!ParseNumber(tokens[++i], &binding->deadzone)) { return False; } } else if (NameEquals(tokens[i], "rate") && i + 1 < token_count) { if (!ParseNumber(tokens[++i], &binding->rate)) { return False; } } else { return False; } } return True; } return False; } //--------------------------------------------------------------- // The default profile: vRIO's board-complete layout, adapted for // the desktop game - the driving keys (WASD/QE/PgUp/PgDn) stay // axes, so six bank buttons lose their keyboard keys (they are // still clickable on the on-screen cockpit, and rebindable here). //--------------------------------------------------------------- const char kDefaultProfile[] = "# RP412 input bindings - keyboard and Xbox (XInput) controller.\n" "#\n" "# One binding per line, '#' starts a comment, keywords are case-\n" "# insensitive. Loaded at game start; delete this file to restore\n" "# the defaults.\n" "#\n" "# key button [toggle]\n" "# key axis deflect \n" "# key axis rate \n" "# pad