diff --git a/MUNGA_L4/L4PADBINDINGS.cpp b/MUNGA_L4/L4PADBINDINGS.cpp new file mode 100644 index 0000000..48838d4 --- /dev/null +++ b/MUNGA_L4/L4PADBINDINGS.cpp @@ -0,0 +1,547 @@ +#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 }, { "ControlKey", VK_CONTROL }, + { "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