Ported from RP412: RIOBase split out of the serial RIO (L4RIO.h), rioPointer is RIOBase* (L4CTRL.h), PAD token -> new PadRIO() speaking the RIO surface from an XInput pad + keyboard (L4PADRIO/L4PADBINDINGS, vRIO bindings.txt grammar, hot-plug), KeyLight RGB mirror TU (BT412KEYLIGHT, /std:c++17 per-file). BT-side fixes PadRIO forced into the open: - Both keyboard input bridges (mech4.cpp, mechmppr.cpp BT_KEY_BRIDGE) stand down when a RIO device exists -- they overwrote the engine controls push every frame. M/X conveniences stay live. - Mapper attribute chain OFF BY ONE (latent real-pod bug): the DOS chain below MechControlsMapper carried two base attributes, WinTesla carries one, and AttributeIndexSet::Find is positional -- the .CTL stick mapping wrote throttlePosition. Pad slot + binary-locked enum; gotcha ledgered (reconstruction-gotchas #11). Verified: PAD throttle lever ramps + sticks, stick turns with the authentic speed-vs-turn clamp (61.5 -> 22.0 u/s), mech drives; keyboard fallback intact (BT_FORCE_THROTTLE harness). New diags: BT_CTRLMAP_LOG, BT_STICK_LOG. (Phase 2 of docs/BT412-ROADMAP.md) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
543 lines
17 KiB
C++
543 lines
17 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4padbindings.h"
|
|
|
|
#include <XInput.h>
|
|
#include <stdio.h>
|
|
|
|
//########################################################################
|
|
// 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[] =
|
|
"# BT412 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 <name> button <addr> [toggle]\n"
|
|
"# key <name> axis <axis> deflect <n>\n"
|
|
"# key <name> axis <axis> rate <n-per-second>\n"
|
|
"# pad <button> button <addr> [toggle]\n"
|
|
"# padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n-per-second>]\n"
|
|
"#\n"
|
|
"# <addr> RIO input address: lamp buttons 0x00-0x47, internal keypad\n"
|
|
"# 0x50-0x5F, external keypad 0x60-0x6F (hex or decimal).\n"
|
|
"# <axis> Throttle | LeftPedal | RightPedal | JoystickY | JoystickX\n"
|
|
"# <name> Keys name: A-Z, D0-D9 (digit row), F1-F12, NumPad0-NumPad9,\n"
|
|
"# Up, Down, Left, Right, Space, Enter, PageUp, PageDown,\n"
|
|
"# OemMinus, Oemplus, Oemcomma, OemPeriod, ...\n"
|
|
"# <button> A B X Y DPadUp DPadDown DPadLeft DPadRight Start Back\n"
|
|
"# LeftShoulder RightShoulder LeftThumb RightThumb\n"
|
|
"# <src> LeftStickX LeftStickY RightStickX RightStickY\n"
|
|
"# LeftTrigger RightTrigger\n"
|
|
"#\n"
|
|
"# 'deflect' holds the axis there while the key is down and springs\n"
|
|
"# back on release; 'rate' walks the axis by <n> per second and the\n"
|
|
"# position sticks (the throttle). Every lamp button is also clickable\n"
|
|
"# on the on-screen cockpit, so unbound addresses are never stranded.\n"
|
|
"\n"
|
|
"# ---- Driving: number pad + modifiers ------------------------------\n"
|
|
"# The whole letter board stays free for the MFD banks; driving lives\n"
|
|
"# on the numpad with the throttle lever on the modifier keys.\n"
|
|
"key NumPad8 axis JoystickY deflect -1 # stick forward\n"
|
|
"key NumPad2 axis JoystickY deflect 1 # stick back\n"
|
|
"key NumPad4 axis JoystickX deflect 1 # stick left\n"
|
|
"key NumPad6 axis JoystickX deflect -1 # stick right\n"
|
|
"key NumPad7 axis LeftPedal deflect 1\n"
|
|
"key NumPad9 axis RightPedal deflect 1\n"
|
|
"key NumPad0 button 0x40 # Main trigger (Space works too)\n"
|
|
"key Shift axis Throttle rate 0.75 # throttle up\n"
|
|
"key Ctrl axis Throttle rate -0.75 # throttle down\n"
|
|
"key Alt button 0x3F # Throttle-head button\n"
|
|
"\n"
|
|
"# ---- Driving: Xbox controller (signs match the pod convention) ----\n"
|
|
"padaxis LeftStickX axis JoystickX invert deadzone 0.24\n"
|
|
"padaxis LeftStickY axis JoystickY invert deadzone 0.24\n"
|
|
"padaxis RightStickY axis Throttle deadzone 0.24 rate 0.75\n"
|
|
"padaxis LeftTrigger axis LeftPedal deadzone 0.12\n"
|
|
"padaxis RightTrigger axis RightPedal deadzone 0.12\n"
|
|
"\n"
|
|
"# ---- Joystick column: hat on the arrows, Main on Space ------------\n"
|
|
"key Space button 0x40 # Main trigger\n"
|
|
"key Up button 0x42 # Hat Up\n"
|
|
"key Down button 0x41 # Hat Back\n"
|
|
"key Left button 0x44 # Hat Left\n"
|
|
"key Right button 0x43 # Hat Right\n"
|
|
"\n"
|
|
"# ---- Xbox controller: buttons -------------------------------------\n"
|
|
"pad A button 0x40 # Main\n"
|
|
"pad B button 0x46 # Middle\n"
|
|
"pad X button 0x45 # Pinky\n"
|
|
"pad Y button 0x47 # Upper\n"
|
|
"pad DPadUp button 0x42 # Hat Up\n"
|
|
"pad DPadDown button 0x41 # Hat Back\n"
|
|
"pad DPadLeft button 0x44 # Hat Left\n"
|
|
"pad DPadRight button 0x43 # Hat Right\n"
|
|
"pad LeftShoulder button 0x3D # Panic\n"
|
|
"pad RightShoulder button 0x3F # Throttle-head button\n"
|
|
"pad Start button 0x37 # config 1\n"
|
|
"pad Back button 0x36 # config 2\n"
|
|
"\n"
|
|
"# ---- Upper MFD bank: the number row is the top MFD row and the\n"
|
|
"# ---- QWERTY row is the row under it, left to right across the\n"
|
|
"# ---- Left / Middle / Right MFDs.\n"
|
|
"key D1 button 0x2F\n"
|
|
"key D2 button 0x2E\n"
|
|
"key D3 button 0x2D\n"
|
|
"key D4 button 0x2C\n"
|
|
"key D5 button 0x27\n"
|
|
"key D6 button 0x26\n"
|
|
"key D7 button 0x25\n"
|
|
"key D8 button 0x24\n"
|
|
"key D9 button 0x37\n"
|
|
"key D0 button 0x36\n"
|
|
"key OemMinus button 0x35\n"
|
|
"key Oemplus button 0x34\n"
|
|
"key Q button 0x2B\n"
|
|
"key W button 0x2A\n"
|
|
"key E button 0x29\n"
|
|
"key R button 0x28\n"
|
|
"key T button 0x23\n"
|
|
"key Y button 0x22\n"
|
|
"key U button 0x21\n"
|
|
"key I button 0x20\n"
|
|
"key O button 0x33\n"
|
|
"key P button 0x32\n"
|
|
"key OemOpenBrackets button 0x31\n"
|
|
"key OemCloseBrackets button 0x30\n"
|
|
"\n"
|
|
"# ---- Lower MFD bank: home row and the row below it, two 4-key\n"
|
|
"# ---- blocks split by an unbound gap key (G / B), mirroring the\n"
|
|
"# ---- keypad gap between the Lower Left and Lower Right MFDs.\n"
|
|
"key A button 0x0F\n"
|
|
"key S button 0x0E\n"
|
|
"key D button 0x0D\n"
|
|
"key F button 0x0C\n"
|
|
"key H button 0x07\n"
|
|
"key J button 0x06\n"
|
|
"key K button 0x05\n"
|
|
"key L button 0x04\n"
|
|
"key Z button 0x0B\n"
|
|
"key X button 0x0A\n"
|
|
"key C button 0x09\n"
|
|
"key V button 0x08\n"
|
|
"key N button 0x03\n"
|
|
"key M button 0x02\n"
|
|
"key Oemcomma button 0x01\n"
|
|
"key OemPeriod button 0x00\n"
|
|
"\n"
|
|
"# ---- Secondary / Screen columns on the function keys, top to\n"
|
|
"# ---- bottom (0x16/0x17 and 0x1E/0x1F intentionally unmapped).\n"
|
|
"key F1 button 0x10\n"
|
|
"key F2 button 0x11\n"
|
|
"key F3 button 0x12\n"
|
|
"key F4 button 0x13\n"
|
|
"key F5 button 0x14\n"
|
|
"key F6 button 0x15\n"
|
|
"key F7 button 0x18\n"
|
|
"key F8 button 0x19\n"
|
|
"key F9 button 0x1A\n"
|
|
"key F10 button 0x1B\n"
|
|
"key F11 button 0x1C\n"
|
|
"key F12 button 0x1D\n"
|
|
"\n"
|
|
"# The pilot keypad (0x50-0x5F) and external keypad (0x60-0x6F) are\n"
|
|
"# unbound - the game never reads them - but remain bindable here\n"
|
|
"# (they arrive as arcade RIO key events).\n";
|
|
|
|
const char kBindingsFileName[] = "bindings.txt";
|
|
}
|
|
|
|
//########################################################################
|
|
// Loader
|
|
//########################################################################
|
|
|
|
void
|
|
PadBindings_Load(PadBindingProfile *profile)
|
|
{
|
|
memset(profile, 0, sizeof(*profile));
|
|
|
|
//
|
|
// First run: write the default profile so the player has a
|
|
// self-documenting file to edit
|
|
//
|
|
FILE *file = fopen(kBindingsFileName, "rb");
|
|
if (file == NULL)
|
|
{
|
|
FILE *out = fopen(kBindingsFileName, "wb");
|
|
if (out != NULL)
|
|
{
|
|
fwrite(kDefaultProfile, 1, strlen(kDefaultProfile), out);
|
|
fclose(out);
|
|
DEBUG_STREAM << "PadBindings: wrote default " << kBindingsFileName
|
|
<< "\n" << std::flush;
|
|
}
|
|
file = fopen(kBindingsFileName, "rb");
|
|
}
|
|
|
|
//
|
|
// Parse: file if we have one, the built-in default if not
|
|
//
|
|
char *text = NULL;
|
|
long size = 0;
|
|
if (file != NULL)
|
|
{
|
|
fseek(file, 0, SEEK_END);
|
|
size = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
text = new char[size + 1];
|
|
fread(text, 1, size, file);
|
|
text[size] = '\0';
|
|
fclose(file);
|
|
}
|
|
const char *source = (text != NULL) ? text : kDefaultProfile;
|
|
|
|
char line[256];
|
|
int line_number = 0;
|
|
int error_count = 0;
|
|
const char *cursor = source;
|
|
while (*cursor != '\0')
|
|
{
|
|
int length = 0;
|
|
while (cursor[length] != '\0' && cursor[length] != '\n' &&
|
|
length < (int) sizeof(line) - 1)
|
|
{
|
|
line[length] = cursor[length];
|
|
++length;
|
|
}
|
|
line[length] = '\0';
|
|
cursor += length;
|
|
if (*cursor == '\n')
|
|
{
|
|
++cursor;
|
|
}
|
|
++line_number;
|
|
|
|
char *tokens[12];
|
|
int token_count = Tokenize(line, tokens, 12);
|
|
if (token_count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (!ParseLine(tokens, token_count, profile))
|
|
{
|
|
++error_count;
|
|
DEBUG_STREAM << "PadBindings: " << kBindingsFileName << " line "
|
|
<< line_number << " rejected\n" << std::flush;
|
|
}
|
|
}
|
|
|
|
if (text != NULL)
|
|
{
|
|
delete[] text;
|
|
}
|
|
|
|
DEBUG_STREAM << "PadBindings: " << profile->keyButtonCount << " key buttons, "
|
|
<< profile->keyAxisCount << " key axes, "
|
|
<< profile->padButtonCount << " pad buttons, "
|
|
<< profile->padAxisCount << " pad axes"
|
|
<< (error_count ? " (with rejected lines)" : "")
|
|
<< "\n" << std::flush;
|
|
}
|