| 1 |
#include <stdlib.h> |
| 2 |
#include <stdarg.h> |
| 3 |
|
| 4 |
#include "../Daodan.h" |
| 5 |
#include "Input.h" |
| 6 |
#include "../Oni/Oni.h" |
| 7 |
#include "../Daodan_Config.h" |
| 8 |
#include "../Daodan_Patch.h" |
| 9 |
#include "Utility.h" |
| 10 |
|
| 11 |
typedef struct { |
| 12 |
LItActionDescription descr; |
| 13 |
DDtActionEventType eventType; |
| 14 |
|
| 15 |
DDtCustomActionCallback callback; |
| 16 |
intptr_t ctx; |
| 17 |
} DDtCustomAction; |
| 18 |
|
| 19 |
static DDtCustomAction DDgCustomActions[100] = { 0 }; |
| 20 |
|
| 21 |
// Extra keys (make sure these don't collide with Oni's LIc_* keys) |
| 22 |
enum { |
| 23 |
DDcKey_MouseButton5 = LIcKey_Max, |
| 24 |
DDcKey_ScrollUp, |
| 25 |
DDcKey_ScrollDown, |
| 26 |
DDcKey_ScrollLeft, |
| 27 |
DDcKey_ScrollRight, |
| 28 |
}; |
| 29 |
|
| 30 |
// Enhanced version of LIgInputNames from Oni with some extra keys |
| 31 |
static const LItInputName DDgInputNames[] = { |
| 32 |
// The following key names are mapped in Oni |
| 33 |
{"fkey1", LIcKey_FKey1}, {"fkey2", LIcKey_FKey2}, {"fkey3", LIcKey_FKey3}, |
| 34 |
{"fkey4", LIcKey_FKey4}, {"fkey5", LIcKey_FKey5}, {"fkey6", LIcKey_FKey6}, |
| 35 |
{"fkey7", LIcKey_FKey7}, {"fkey8", LIcKey_FKey8}, {"fkey9", LIcKey_FKey9}, |
| 36 |
{"fkey10", LIcKey_FKey10}, {"fkey11", LIcKey_FKey11}, |
| 37 |
{"fkey12", LIcKey_FKey12}, {"fkey13", LIcKey_FKey13}, |
| 38 |
{"fkey14", LIcKey_FKey14}, {"fkey15", LIcKey_FKey15}, |
| 39 |
{"backspace", LIcKey_Backspace}, {"tab", LIcKey_Tab}, |
| 40 |
{"capslock", LIcKey_CapsLock}, {"enter", LIcKey_Enter}, |
| 41 |
{"leftshift", LIcKey_LeftShift}, {"rightshift", LIcKey_RightShift}, |
| 42 |
{"leftcontrol", LIcKey_LeftControl}, |
| 43 |
{"leftwindows", 0x94}, {"leftoption", 0x94}, // Does nothing in Oni |
| 44 |
{"leftalt", LIcKey_LeftAlt}, {"space", ' '}, {"rightalt", LIcKey_RightAlt}, |
| 45 |
{"rightoption", 0x97}, {"rightwindows", 0x97}, // Does nothing in Oni |
| 46 |
{"rightcontrol", LIcKey_RightControl}, {"printscreen", LIcKey_PrintScreen}, |
| 47 |
{"scrolllock", LIcKey_ScrollLock}, {"pause", LIcKey_Pause}, |
| 48 |
{"insert", LIcKey_Insert}, {"home", LIcKey_Home}, {"pageup", LIcKey_PageUp}, |
| 49 |
{"delete", LIcKey_Delete}, {"end", LIcKey_End}, |
| 50 |
{"pagedown", LIcKey_PageDown}, {"uparrow", LIcKey_UpArrow}, |
| 51 |
{"leftarrow", LIcKey_LeftArrow}, {"downarrow", LIcKey_DownArrow}, |
| 52 |
{"rightarrow", LIcKey_RightArrow}, {"numlock", LIcKey_NumLock}, |
| 53 |
{"divide", LIcKey_Divide}, {"multiply", LIcKey_Multiply}, |
| 54 |
{"subtract", LIcKey_Subtract}, {"add", LIcKey_Add}, |
| 55 |
{"numpadequals", LIcKey_NumpadEquals}, {"numpadenter", LIcKey_NumpadEnter}, |
| 56 |
{"decimal", LIcKey_Decimal}, {"numpad0", LIcKey_Numpad0}, |
| 57 |
{"numpad1", LIcKey_Numpad1}, {"numpad2", LIcKey_Numpad2}, |
| 58 |
{"numpad3", LIcKey_Numpad3}, {"numpad4", LIcKey_Numpad4}, |
| 59 |
{"numpad5", LIcKey_Numpad5}, {"numpad6", LIcKey_Numpad6}, |
| 60 |
{"numpad7", LIcKey_Numpad7}, {"numpad8", LIcKey_Numpad8}, |
| 61 |
{"numpad9", LIcKey_Numpad9}, {"backslash", '\\'}, {"semicolon", ';'}, |
| 62 |
{"period", '.'}, {"apostrophe", '\''}, {"slash", '/'}, {"leftbracket", '['}, |
| 63 |
{"rightbracket", ']'}, {"comma", ','}, |
| 64 |
{"mousebutton1", LIcKey_MouseButton1}, |
| 65 |
{"mousebutton2", LIcKey_MouseButton2}, |
| 66 |
{"mousebutton3", LIcKey_MouseButton3}, |
| 67 |
{"mousebutton4", LIcKey_MouseButton4}, |
| 68 |
{"mousexaxis", LIcKey_MouseXAxis}, {"mouseyaxis", LIcKey_MouseYAxis}, |
| 69 |
{"mousezaxis", LIcKey_MouseZAxis}, |
| 70 |
|
| 71 |
// Extra keys for Daodan Input |
| 72 |
{"mousebutton5", DDcKey_MouseButton5}, |
| 73 |
{"scrollup", DDcKey_ScrollUp}, |
| 74 |
{"scrolldown", DDcKey_ScrollDown}, |
| 75 |
{"scrollleft", DDcKey_ScrollLeft}, |
| 76 |
{"scrollright", DDcKey_ScrollRight}, |
| 77 |
|
| 78 |
{"", 0} |
| 79 |
}; |
| 80 |
|
| 81 |
// Enhanced version of LIgPlatform_ScanCodeToChar from Oni |
| 82 |
static const uint8_t DDgPlatform_ScanCodeToChar[256] = { |
| 83 |
// The following scan codes are mapped in Oni |
| 84 |
[0x01] = LIcKey_Escape, [0x02] = '1', [0x03] = '2', [0x04] = '3', |
| 85 |
[0x05] = '4', [0x06] = '5', [0x07] = '6', [0x08] = '7', [0x09] = '8', |
| 86 |
[0x0a] = '9', [0x0b] = '0', [0x0c] = '-', [0x0d] = '=', |
| 87 |
[0x0e] = LIcKey_Backspace, [0x0f] = LIcKey_Tab, [0x10] = 'q', [0x11] = 'w', |
| 88 |
[0x12] = 'e', [0x13] = 'r', [0x14] = 't', [0x15] = 'y', [0x16] = 'u', |
| 89 |
[0x17] = 'i', [0x18] = 'o', [0x19] = 'p', [0x1a] = '[', [0x1b] = ']', |
| 90 |
[0x1c] = LIcKey_Enter, [0x1d] = LIcKey_LeftControl, [0x1e] = 'a', |
| 91 |
[0x1f] = 's', [0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h', |
| 92 |
[0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x27] = ';', [0x28] = '\'', |
| 93 |
[0x29] = '`', [0x2a] = LIcKey_LeftShift, [0x2b] = '\\', [0x2c] = 'z', |
| 94 |
[0x2d] = 'x', [0x2e] = 'c', [0x2f] = 'v', [0x30] = 'b', [0x31] = 'n', |
| 95 |
[0x32] = 'm', [0x33] = ',', [0x34] = '.', [0x35] = '/', |
| 96 |
[0x36] = LIcKey_RightShift, [0x37] = LIcKey_Multiply, |
| 97 |
[0x38] = LIcKey_LeftAlt, [0x39] = ' ', [0x3a] = LIcKey_CapsLock, |
| 98 |
[0x3b] = LIcKey_FKey1, [0x3c] = LIcKey_FKey2, [0x3d] = LIcKey_FKey3, |
| 99 |
[0x3e] = LIcKey_FKey4, [0x3f] = LIcKey_FKey5, [0x40] = LIcKey_FKey6, |
| 100 |
[0x41] = LIcKey_FKey7, [0x42] = LIcKey_FKey8, [0x43] = LIcKey_FKey9, |
| 101 |
[0x44] = LIcKey_FKey10, [0x45] = LIcKey_NumLock, [0x46] = LIcKey_ScrollLock, |
| 102 |
[0x47] = LIcKey_Numpad7, [0x48] = LIcKey_Numpad8, [0x49] = LIcKey_Numpad9, |
| 103 |
[0x4a] = LIcKey_Subtract, [0x4b] = LIcKey_Numpad4, [0x4c] = LIcKey_Numpad5, |
| 104 |
[0x4d] = LIcKey_Numpad6, [0x4e] = LIcKey_Add, [0x4f] = LIcKey_Numpad1, |
| 105 |
[0x50] = LIcKey_Numpad2, [0x51] = LIcKey_Numpad3, [0x52] = LIcKey_Numpad0, |
| 106 |
[0x53] = LIcKey_Decimal, [0x57] = LIcKey_FKey11, [0x58] = LIcKey_FKey12, |
| 107 |
[0x64] = LIcKey_FKey13, [0x65] = LIcKey_FKey14, [0x66] = LIcKey_FKey15, |
| 108 |
[0x8d] = LIcKey_NumpadEquals, [0x9c] = LIcKey_NumpadEnter, |
| 109 |
[0x9d] = LIcKey_RightControl, [0xb3] = LIcKey_NumpadComma, |
| 110 |
[0xb5] = LIcKey_Divide, [0xb8] = LIcKey_RightAlt, [0xc7] = LIcKey_Home, |
| 111 |
[0xc8] = LIcKey_UpArrow, [0xc9] = LIcKey_PageUp, [0xcb] = LIcKey_LeftArrow, |
| 112 |
[0xcd] = LIcKey_RightArrow, [0xcf] = LIcKey_End, [0xd0] = LIcKey_DownArrow, |
| 113 |
[0xd2] = LIcKey_Insert, [0xd3] = LIcKey_Delete, [0xdb] = LIcKey_LeftWindows, |
| 114 |
[0xdc] = LIcKey_RightWindows, [0xdd] = LIcKey_Apps, |
| 115 |
|
| 116 |
// Missing in Oni |
| 117 |
[0xd1] = LIcKey_PageDown, |
| 118 |
}; |
| 119 |
|
| 120 |
// Set in Patches.c if the Daodan input patches are applied. This just enables |
| 121 |
// the windows message handling for now |
| 122 |
bool DDgUseDaodanInput = false; |
| 123 |
|
| 124 |
// The Oni key codes that correspond to the togglable keys |
| 125 |
static uint8_t DDgCapsOniKey = 0; |
| 126 |
static uint8_t DDgScrollOniKey = 0; |
| 127 |
static uint8_t DDgNumLockOniKey = 0; |
| 128 |
|
| 129 |
// Multiplier for mouse values |
| 130 |
static float DDgMouseSensitivity = 1.0; |
| 131 |
|
| 132 |
// Accumulators for mouse scrolling. These are needed because some mice have |
| 133 |
// continuous scroll wheels (not to mention touchpads.) We should only add an |
| 134 |
// action to Oni's input if one of these accumulators exceeds +/-WHEEL_DELTA. |
| 135 |
static int DDgWheelDelta_V = 0; |
| 136 |
static int DDgWheelDelta_H = 0; |
| 137 |
|
| 138 |
// UUrMachineTime_High for the last update of the accumulators. Used so they can |
| 139 |
// be reset after a period of no scroll events. |
| 140 |
static int64_t DDgWheelDelta_Updated = 0; |
| 141 |
|
| 142 |
// Temporary action buffer that we build over the duration of a frame with the |
| 143 |
// input we're going to send to the engine. This includes the accumulated |
| 144 |
// movement of the mouse cursor and all actions (keyboard and mouse buttons) |
| 145 |
// that were pressed this frame (but not held down from previous frames - that |
| 146 |
// gets added later from DDgInputState.) |
| 147 |
static LItActionBuffer DDgActionBuffer = { 0 }; |
| 148 |
|
| 149 |
// Temporary buffer containing the current state of the keyboard and mouse |
| 150 |
// buttons, that is, if they're being held now |
| 151 |
static char DDgInputState[256] = { 0 }; |
| 152 |
|
| 153 |
static short ONICALL DDrBinding_Add(int key, const char *name) |
| 154 |
{ |
| 155 |
// First try to replace an existing binding for the same key |
| 156 |
LItBinding *binding = NULL; |
| 157 |
for (int i = 0; i < 100; i++) { |
| 158 |
if (LIgBindingArray[i].key == key) { |
| 159 |
binding = &LIgBindingArray[i]; |
| 160 |
break; |
| 161 |
} |
| 162 |
} |
| 163 |
|
| 164 |
// If there are no existing bindings for this key, find a free entry |
| 165 |
if (!binding) { |
| 166 |
for (int i = 0; i < 100; i++) { |
| 167 |
if (!LIgBindingArray[i].key) { |
| 168 |
binding = &LIgBindingArray[i]; |
| 169 |
break; |
| 170 |
} |
| 171 |
} |
| 172 |
} |
| 173 |
// No free entries, so give up |
| 174 |
if (!binding) |
| 175 |
return 2; |
| 176 |
|
| 177 |
// Now try to find the action to bind to. First check Oni's built-in list |
| 178 |
// of actions. |
| 179 |
LItActionDescription *descr = NULL; |
| 180 |
for (int i = 0; LIgActionDescriptions[i].name[0]; i++) { |
| 181 |
if (!UUrString_Compare_NoCase(name, LIgActionDescriptions[i].name)) { |
| 182 |
descr = &LIgActionDescriptions[i]; |
| 183 |
break; |
| 184 |
} |
| 185 |
} |
| 186 |
|
| 187 |
// Next, try Daodan's list of custom actions |
| 188 |
if (!descr) { |
| 189 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
| 190 |
if (!DDgCustomActions[i].descr.name[0]) |
| 191 |
break; |
| 192 |
|
| 193 |
if (!UUrString_Compare_NoCase(name, DDgCustomActions[i].descr.name)) { |
| 194 |
descr = &DDgCustomActions[i].descr; |
| 195 |
break; |
| 196 |
} |
| 197 |
} |
| 198 |
} |
| 199 |
if (!descr) |
| 200 |
return 0; |
| 201 |
|
| 202 |
binding->key = key; |
| 203 |
binding->descr = descr; |
| 204 |
return 0; |
| 205 |
} |
| 206 |
|
| 207 |
static void ONICALL DDrGameState_HandleUtilityInput(GameInput *input) |
| 208 |
{ |
| 209 |
// Mac Oni 1.2.1 checks the cheat binds here, so we should too. Note that |
| 210 |
// unlike Mac Oni, which hardcodes each cheat here, we use our flexible |
| 211 |
// custom action system. |
| 212 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
| 213 |
if (!DDgCustomActions[i].descr.name[0]) |
| 214 |
break; |
| 215 |
|
| 216 |
uint64_t action = 1ull << DDgCustomActions[i].descr.index; |
| 217 |
bool active = false; |
| 218 |
|
| 219 |
switch (DDgCustomActions[i].eventType) { |
| 220 |
case DDcEventType_KeyPress: |
| 221 |
if (input->ActionsPressed & action) |
| 222 |
active = true; |
| 223 |
break; |
| 224 |
case DDcEventType_KeyDown: |
| 225 |
if (input->ActionsDown & action) |
| 226 |
active = true; |
| 227 |
break; |
| 228 |
} |
| 229 |
|
| 230 |
if (active) |
| 231 |
DDgCustomActions[i].callback(DDgCustomActions[i].ctx); |
| 232 |
} |
| 233 |
|
| 234 |
// Now do everything Oni does in this function |
| 235 |
ONrGameState_HandleUtilityInput(input); |
| 236 |
|
| 237 |
// This is for show_triggervolumes. Mac Oni does this at the end of |
| 238 |
// HandleUtilityInput too. |
| 239 |
if (ONrDebugKey_WentDown(7)) |
| 240 |
OBJgTriggerVolume_Visible = !OBJgTriggerVolume_Visible; |
| 241 |
} |
| 242 |
|
| 243 |
static int GetLowestFreeDigitalAction(void) |
| 244 |
{ |
| 245 |
// Get the digital action indexes that Oni is using right now, plus any in |
| 246 |
// use by our custom actions |
| 247 |
uint64_t used = 0; |
| 248 |
for (int i = 0; LIgActionDescriptions[i].name[0]; i++) { |
| 249 |
if (LIgActionDescriptions[i].type != LIcActionType_Digital) |
| 250 |
continue; |
| 251 |
used |= 1ull << LIgActionDescriptions[i].index; |
| 252 |
} |
| 253 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
| 254 |
if (!DDgCustomActions[i].descr.name[0]) |
| 255 |
break; |
| 256 |
|
| 257 |
if (DDgCustomActions[i].descr.type != LIcActionType_Digital) |
| 258 |
continue; |
| 259 |
used |= 1ull << DDgCustomActions[i].descr.index; |
| 260 |
} |
| 261 |
|
| 262 |
// Get the lowest unused action index and use it. This isn't totally safe |
| 263 |
// since Oni _might_ have "orphaned" actions that are checked in the code |
| 264 |
// but not bindable, but Mac Oni 1.2.1 seems to have allocated its new |
| 265 |
// bindings this way, including filling the gaps between eg. f12 and |
| 266 |
// lookmode, so we're probably fine to do the same thing. |
| 267 |
unsigned long lowest; |
| 268 |
if (BitScanForward(&lowest, ~(unsigned long)used)) |
| 269 |
return lowest; |
| 270 |
if (BitScanForward(&lowest, ~(unsigned long)(used >> 32))) |
| 271 |
return lowest + 32; |
| 272 |
return -1; |
| 273 |
} |
| 274 |
|
| 275 |
void DDrInput_RegisterCustomAction(const char *name, DDtActionEventType type, |
| 276 |
DDtCustomActionCallback callback, |
| 277 |
intptr_t ctx) |
| 278 |
{ |
| 279 |
int index = GetLowestFreeDigitalAction(); |
| 280 |
if (index < 0) { |
| 281 |
STARTUPMESSAGE("Registering action %s failed, maximum actions reached", |
| 282 |
name); |
| 283 |
return; |
| 284 |
} |
| 285 |
|
| 286 |
DDtCustomAction *action; |
| 287 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
| 288 |
if (!DDgCustomActions[i].descr.name[0]) { |
| 289 |
action = &DDgCustomActions[i]; |
| 290 |
break; |
| 291 |
} |
| 292 |
} |
| 293 |
if (!action) { |
| 294 |
STARTUPMESSAGE("Registering action %s failed, maximum actions reached", |
| 295 |
name); |
| 296 |
return; |
| 297 |
} |
| 298 |
|
| 299 |
*action = (DDtCustomAction) { |
| 300 |
.descr = { |
| 301 |
.type = 1, |
| 302 |
.index = index, |
| 303 |
}, |
| 304 |
.callback = callback, |
| 305 |
.ctx = ctx, |
| 306 |
}; |
| 307 |
UUrString_Copy(action->descr.name, name, sizeof(action->descr.name)); |
| 308 |
} |
| 309 |
|
| 310 |
static uint8_t VKeyToChar(UINT vkey) |
| 311 |
{ |
| 312 |
int sc = MapVirtualKeyA(vkey, MAPVK_VK_TO_VSC_EX); |
| 313 |
if ((sc & 0xff00) == 0xe000) |
| 314 |
sc |= 0x80; |
| 315 |
sc &= 0xff; |
| 316 |
return DDgPlatform_ScanCodeToChar[sc]; |
| 317 |
} |
| 318 |
|
| 319 |
static int ONICALL DDrTranslate_InputName(char *name) |
| 320 |
{ |
| 321 |
// Mutate the source argument to convert to lowercase. It's ugly but Oni |
| 322 |
// does this too. Unlike Oni, we don't use tolower, since passing |
| 323 |
// potentially out-of-range values to tolower is undefined. |
| 324 |
for (char *c = name; *c; c++) { |
| 325 |
if (*c >= 'A' && *c <= 'Z') |
| 326 |
*c = *c - 0x20; |
| 327 |
} |
| 328 |
|
| 329 |
// Single character names just use that character as the key code. Unlike |
| 330 |
// Oni, we restrict this to printable ASCII. |
| 331 |
if (strlen(name) == 1 && name[0] >= ' ' && name[0] <= '~') |
| 332 |
return name[0]; |
| 333 |
|
| 334 |
// Otherwise, look up the name in DDgInputNames |
| 335 |
for (int i = 0; DDgInputNames[i].name[0]; i++) { |
| 336 |
if (!strcmp(name, DDgInputNames[i].name)) |
| 337 |
return DDgInputNames[i].key; |
| 338 |
} |
| 339 |
return 0; |
| 340 |
} |
| 341 |
|
| 342 |
static void CenterCursor(void) |
| 343 |
{ |
| 344 |
// This can be set to false by script. Not sure why you'd turn it off, but |
| 345 |
// let's respect it. |
| 346 |
if (!LIgCenterCursor) |
| 347 |
return; |
| 348 |
|
| 349 |
RECT rc; |
| 350 |
if (!GetClientRect(LIgPlatform_HWND, &rc)) |
| 351 |
return; |
| 352 |
POINT mid = { rc.right / 2, rc.bottom / 2 }; |
| 353 |
if (!ClientToScreen(LIgPlatform_HWND, &mid)) |
| 354 |
return; |
| 355 |
SetCursorPos(mid.x, mid.y); |
| 356 |
} |
| 357 |
|
| 358 |
static void ONICALL DDrPlatform_Mode_Set(int active) |
| 359 |
{ |
| 360 |
// Oni's input system uses LIgPlatform_HWND instead of |
| 361 |
// ONgPlatformData.Window, but they should both have the same value |
| 362 |
DDmAssert(LIgPlatform_HWND); |
| 363 |
|
| 364 |
// Clear the input state when switching input modes |
| 365 |
for (int i = 0; i < ARRAY_SIZE(DDgInputState); i++) |
| 366 |
DDgInputState[i] = 0; |
| 367 |
DDgActionBuffer = (LItActionBuffer) { 0 }; |
| 368 |
|
| 369 |
// Center the cursor before switching modes. Raw Input doesn't need the |
| 370 |
// cursor to be centered, but when switching modes, centering the cursor |
| 371 |
// means it will be in a predictable position for using the pause or F1 |
| 372 |
// menu, which are centered on the screen. Also, the cursor must be inside |
| 373 |
// the clip region when we call ClipCursor, otherwise it doesn't work. |
| 374 |
CenterCursor(); |
| 375 |
|
| 376 |
// If leaving input mode (switching from gameplay to menus,) unregister the |
| 377 |
// input device. Otherwise, register it. |
| 378 |
RegisterRawInputDevices(&(RAWINPUTDEVICE) { |
| 379 |
.usUsagePage = 0x01, // HID_USAGE_PAGE_GENERIC |
| 380 |
.usUsage = 0x02, // HID_USAGE_GENERIC_MOUSE |
| 381 |
.hwndTarget = LIgPlatform_HWND, |
| 382 |
.dwFlags = active ? 0 : RIDEV_REMOVE, |
| 383 |
}, 1, sizeof(RAWINPUTDEVICE)); |
| 384 |
|
| 385 |
if (active) { |
| 386 |
DDgMouseSensitivity = |
| 387 |
DDrConfig_GetOptOfType("windows.mousesensitivity", C_FLOAT)->value.floatVal; |
| 388 |
|
| 389 |
// Get the Oni key codes corresponding to the togglable keys |
| 390 |
DDgCapsOniKey = VKeyToChar(VK_CAPITAL); |
| 391 |
DDgScrollOniKey = VKeyToChar(VK_SCROLL); |
| 392 |
DDgNumLockOniKey = VKeyToChar(VK_NUMLOCK); |
| 393 |
|
| 394 |
// Clip the cursor to the window bounds when entering input mode to |
| 395 |
// prevent other programs being clicked in windowed mode |
| 396 |
RECT rc; |
| 397 |
if (GetClientRect(LIgPlatform_HWND, &rc)) { |
| 398 |
if (MapWindowRect(LIgPlatform_HWND, NULL, &rc)) |
| 399 |
ClipCursor(&rc); |
| 400 |
} |
| 401 |
} else { |
| 402 |
ClipCursor(NULL); |
| 403 |
} |
| 404 |
} |
| 405 |
|
| 406 |
static void ONICALL DDrPlatform_InputEvent_GetMouse(int active, |
| 407 |
LItInputEvent *event) |
| 408 |
{ |
| 409 |
POINT pt; |
| 410 |
if (!GetCursorPos(&pt)) |
| 411 |
goto error; |
| 412 |
|
| 413 |
// Unlike Oni's version of this function, we support windowed mode by |
| 414 |
// mapping the cursor coordinates to the window's client area |
| 415 |
if (!ScreenToClient(LIgPlatform_HWND, &pt)) |
| 416 |
goto error; |
| 417 |
|
| 418 |
*event = (LItInputEvent) { .mouse_pos = { pt.x, pt.y } }; |
| 419 |
return; |
| 420 |
|
| 421 |
error: |
| 422 |
*event = (LItInputEvent) { 0 }; |
| 423 |
return; |
| 424 |
} |
| 425 |
|
| 426 |
static UUtBool ONICALL DDrPlatform_TestKey(int ch, int active) |
| 427 |
{ |
| 428 |
// DDrPlatform_TestKey is always called with active = LIgMode_Internal |
| 429 |
|
| 430 |
if (active) { |
| 431 |
// The input system is running, which means DDgInputState will be up to |
| 432 |
// date, so just use that |
| 433 |
return DDgInputState[ch]; |
| 434 |
} else { |
| 435 |
// Use Oni's map from key codes to DirectInput scan codes to get the |
| 436 |
// scan code we want to test for |
| 437 |
int sc = 0; |
| 438 |
for (int i = 0; i < 256; i++) { |
| 439 |
if (DDgPlatform_ScanCodeToChar[i] == ch) { |
| 440 |
sc = i; |
| 441 |
break; |
| 442 |
} |
| 443 |
} |
| 444 |
if (!sc) |
| 445 |
return UUcFalse; |
| 446 |
|
| 447 |
// DirectInput scan codes have 0x80 set for extended keys. Replace this |
| 448 |
// with an 0xe0 prefix for MapVirtualKey. |
| 449 |
if (sc & 0x80) { |
| 450 |
sc &= 0x7f; |
| 451 |
sc |= 0xe000; |
| 452 |
} |
| 453 |
int vkey = MapVirtualKeyA(sc, MAPVK_VSC_TO_VK_EX); |
| 454 |
|
| 455 |
// Now check if the key is down. We must use GetAsyncKeyState here |
| 456 |
// because DDrPlatform_TestKey can be called from anywhere, even before |
| 457 |
// we have a message loop or game loop. For example, it's called from |
| 458 |
// ONiMain to test the state of the shift key on startup. |
| 459 |
return (GetAsyncKeyState(vkey) & 0x8000) ? UUcTrue : UUcFalse; |
| 460 |
} |
| 461 |
} |
| 462 |
|
| 463 |
// Update DDgInputState and DDgActionBuffer with a new key state |
| 464 |
static void SetKeyState(int key, bool down) |
| 465 |
{ |
| 466 |
// Keep track of held keys. Held keys are added to every buffer and they're |
| 467 |
// also checked in DDrPlatform_TestKey. |
| 468 |
DDgInputState[key] = down; |
| 469 |
|
| 470 |
if (down) { |
| 471 |
// Add the key to the next buffer. This is so key presses are never |
| 472 |
// dropped, even if the key is released before Oni checks the buffer. |
| 473 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 474 |
.input = key, |
| 475 |
.analog = 1.0, |
| 476 |
}); |
| 477 |
} |
| 478 |
} |
| 479 |
|
| 480 |
static void ProcessRawInputPacket(RAWINPUT *ri) |
| 481 |
{ |
| 482 |
if (ri->header.dwType != RIM_TYPEMOUSE) |
| 483 |
return; |
| 484 |
|
| 485 |
// We don't handle MOUSE_MOVE_ABSOLUTE at all yet |
| 486 |
if (!(ri->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)) { |
| 487 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 488 |
.input = LIcKey_MouseXAxis, |
| 489 |
.analog = (float)ri->data.mouse.lLastX * 0.25 * DDgMouseSensitivity, |
| 490 |
}); |
| 491 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 492 |
.input = LIcKey_MouseYAxis, |
| 493 |
.analog = (float)ri->data.mouse.lLastY * |
| 494 |
(LIgMode_InvertMouse ? -0.25 : 0.25) * DDgMouseSensitivity, |
| 495 |
}); |
| 496 |
} |
| 497 |
|
| 498 |
// Oni supports using the mouse wheel to look up and down or left and right |
| 499 |
// by binding mousezaxis to aim_lr or aim_ud. We don't support this |
| 500 |
// incredibly useful feature, but if you need it, let me know. Instead, we |
| 501 |
// allow scrolling to be bound to digital actions. |
| 502 |
if (ri->data.mouse.usButtonFlags & (RI_MOUSE_WHEEL | RI_MOUSE_HWHEEL)) { |
| 503 |
int64_t now = UUrMachineTime_High(); |
| 504 |
int64_t last_updated = now - DDgWheelDelta_Updated; |
| 505 |
DDgWheelDelta_Updated = now; |
| 506 |
|
| 507 |
// Reset the accumulators if too much time has passed since the last |
| 508 |
// scroll event. The player is assumed to have finished scrolling. |
| 509 |
if (last_updated / UUrMachineTime_High_Frequency() > 0.3) { |
| 510 |
DDgWheelDelta_V = 0; |
| 511 |
DDgWheelDelta_H = 0; |
| 512 |
} |
| 513 |
|
| 514 |
int neg_key, pos_key; |
| 515 |
int *delta; |
| 516 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) { |
| 517 |
neg_key = DDcKey_ScrollUp; |
| 518 |
pos_key = DDcKey_ScrollDown; |
| 519 |
delta = &DDgWheelDelta_V; |
| 520 |
} else { |
| 521 |
neg_key = DDcKey_ScrollLeft; |
| 522 |
pos_key = DDcKey_ScrollRight; |
| 523 |
delta = &DDgWheelDelta_H; |
| 524 |
} |
| 525 |
|
| 526 |
// To support touchpad scrolling and mice with continuous scroll wheels, |
| 527 |
// accumulate the wheel delta and only generate an input event once it |
| 528 |
// crosses the WHEEL_DELTA threshold |
| 529 |
*delta += (short)ri->data.mouse.usButtonData; |
| 530 |
if (*delta >= WHEEL_DELTA) { |
| 531 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 532 |
.input = neg_key, |
| 533 |
.analog = 1.0, |
| 534 |
}); |
| 535 |
|
| 536 |
*delta -= (*delta / WHEEL_DELTA) * WHEEL_DELTA; |
| 537 |
} else if (*delta <= -WHEEL_DELTA) { |
| 538 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 539 |
.input = pos_key, |
| 540 |
.analog = 1.0, |
| 541 |
}); |
| 542 |
|
| 543 |
*delta -= (*delta / -WHEEL_DELTA) * -WHEEL_DELTA; |
| 544 |
} |
| 545 |
} |
| 546 |
|
| 547 |
// This probably doesn't obey SM_SWAPBUTTON... should it? |
| 548 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) |
| 549 |
SetKeyState(LIcKey_MouseButton1, true); |
| 550 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) |
| 551 |
SetKeyState(LIcKey_MouseButton1, false); |
| 552 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) |
| 553 |
SetKeyState(LIcKey_MouseButton2, true); |
| 554 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) |
| 555 |
SetKeyState(LIcKey_MouseButton2, false); |
| 556 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) |
| 557 |
SetKeyState(LIcKey_MouseButton3, true); |
| 558 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP) |
| 559 |
SetKeyState(LIcKey_MouseButton3, false); |
| 560 |
|
| 561 |
// Oni supports binding this button too. It's the back button on most mice. |
| 562 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) |
| 563 |
SetKeyState(LIcKey_MouseButton4, true); |
| 564 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) |
| 565 |
SetKeyState(LIcKey_MouseButton4, false); |
| 566 |
|
| 567 |
// Daodan supports binding the forward button too |
| 568 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) |
| 569 |
SetKeyState(DDcKey_MouseButton5, true); |
| 570 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) |
| 571 |
SetKeyState(DDcKey_MouseButton5, false); |
| 572 |
} |
| 573 |
|
| 574 |
static void DrainRawInput(void) |
| 575 |
{ |
| 576 |
if (!LIgMode_Internal) |
| 577 |
return; |
| 578 |
|
| 579 |
UINT ri_size = 10240; |
| 580 |
static RAWINPUT *ri_buf = NULL; |
| 581 |
if (!ri_buf) |
| 582 |
ri_buf = calloc(1, ri_size); |
| 583 |
|
| 584 |
BOOL wow_hack; |
| 585 |
IsWow64Process(GetCurrentProcess(), &wow_hack); |
| 586 |
|
| 587 |
for (;;) { |
| 588 |
UINT count = GetRawInputBuffer(ri_buf, &ri_size, sizeof ri_buf->header); |
| 589 |
if (count == 0 || count == (UINT)-1) |
| 590 |
return; |
| 591 |
|
| 592 |
RAWINPUT *ri = ri_buf; |
| 593 |
for (UINT i = 0; i < count; i++) { |
| 594 |
// In WOW64, these structures are aligned like in Win64 and they |
| 595 |
// have to be fixed to use from 32-bit code. Yes, really. |
| 596 |
if (wow_hack) { |
| 597 |
memmove(&ri->data, ((char *)&ri->data) + 8, |
| 598 |
ri->header.dwSize - offsetof(RAWINPUT, data) - 8); |
| 599 |
} |
| 600 |
|
| 601 |
ProcessRawInputPacket(ri); |
| 602 |
ri = NEXTRAWINPUTBLOCK(ri); |
| 603 |
} |
| 604 |
} |
| 605 |
} |
| 606 |
|
| 607 |
static UUtBool ONICALL DDiPlatform_InputEvent_GetEvent(void) |
| 608 |
{ |
| 609 |
// Center the cursor just in case. Raw Input doesn't need it, but sometimes |
| 610 |
// ClipCursor doesn't work for some reason and in that case we should still |
| 611 |
// prevent the user from accidentally clicking on other windows. |
| 612 |
if (LIgMode_Internal) |
| 613 |
CenterCursor(); |
| 614 |
|
| 615 |
// Do a buffered read of raw input. Apparently this is faster for high-res |
| 616 |
// mice. Note that we still have to handle WM_INPUT in our wndproc in case |
| 617 |
// a WM_INPUT message arrives during the message loop. |
| 618 |
DrainRawInput(); |
| 619 |
|
| 620 |
// Oni only processes a maximum of three messages here (for performance |
| 621 |
// reasons?) We're actually using Windows messages for input so we need to |
| 622 |
// process all of them. |
| 623 |
MSG msg; |
| 624 |
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { |
| 625 |
TranslateMessage(&msg); |
| 626 |
DispatchMessageA(&msg); |
| 627 |
} |
| 628 |
|
| 629 |
// Oni returns true here if there are still messages to process, so that |
| 630 |
// LIrMode_Set and LIrMode_Set_Internal can call this repeatedly to drain |
| 631 |
// all messages. We always drain all messages so return false. |
| 632 |
return UUcFalse; |
| 633 |
} |
| 634 |
|
| 635 |
static bool HandleWmInput(HRAWINPUT hri, WPARAM wparam) |
| 636 |
{ |
| 637 |
if (!LIgMode_Internal) |
| 638 |
return false; |
| 639 |
|
| 640 |
static RAWINPUT* ri = NULL; |
| 641 |
static UINT ri_size = 0; |
| 642 |
UINT minsize = 0; |
| 643 |
|
| 644 |
GetRawInputData(hri, RID_INPUT, NULL, &minsize, sizeof ri->header); |
| 645 |
if (ri_size < minsize) { |
| 646 |
if (ri) |
| 647 |
free(ri); |
| 648 |
ri_size = minsize; |
| 649 |
ri = calloc(1, ri_size); |
| 650 |
} |
| 651 |
if (GetRawInputData(hri, RID_INPUT, ri, &ri_size, sizeof ri->header) == (UINT)-1) |
| 652 |
return false; |
| 653 |
|
| 654 |
ProcessRawInputPacket(ri); |
| 655 |
return true; |
| 656 |
} |
| 657 |
|
| 658 |
static void HandleWmWindowPosChanged(WINDOWPOS *pos) |
| 659 |
{ |
| 660 |
if (!LIgMode_Internal) |
| 661 |
return; |
| 662 |
|
| 663 |
CenterCursor(); |
| 664 |
|
| 665 |
RECT rc = { pos->x, pos->y, pos->x + pos->cx, pos->y + pos->cy }; |
| 666 |
ClipCursor(&rc); |
| 667 |
} |
| 668 |
|
| 669 |
static void HandleWmKeyboard(int vkey, WORD repeat_count, WORD flags) |
| 670 |
{ |
| 671 |
if (!LIgMode_Internal) |
| 672 |
return; |
| 673 |
|
| 674 |
bool is_extended = flags & KF_EXTENDED; |
| 675 |
bool is_repeat = flags & KF_REPEAT; |
| 676 |
bool is_up = flags & KF_UP; |
| 677 |
BYTE sc = LOBYTE(flags); |
| 678 |
|
| 679 |
// Ignore togglable keys since we handle them specially |
| 680 |
if (vkey == VK_CAPITAL || vkey == VK_SCROLL || vkey == VK_NUMLOCK) |
| 681 |
return; |
| 682 |
|
| 683 |
// Ignore key down messages sent because of key-repeat |
| 684 |
if (!is_up && is_repeat) |
| 685 |
return; |
| 686 |
|
| 687 |
// Apparently some synthetic keyboard messages can be missing the scancode, |
| 688 |
// so get it from the vkey |
| 689 |
if (!sc) |
| 690 |
sc = MapVirtualKeyA(vkey, MAPVK_VK_TO_VSC); |
| 691 |
|
| 692 |
// DirectInput scan codes have 0x80 set for extended keys, and we're using |
| 693 |
// a map based on Oni's DirectInput map to convert to key codes |
| 694 |
if (is_extended) |
| 695 |
sc |= 0x80; |
| 696 |
uint8_t ch = DDgPlatform_ScanCodeToChar[sc]; |
| 697 |
if (!ch) |
| 698 |
return; |
| 699 |
|
| 700 |
SetKeyState(ch, !is_up); |
| 701 |
} |
| 702 |
|
| 703 |
bool DDrInput_WindowProc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam, |
| 704 |
LRESULT* res) |
| 705 |
{ |
| 706 |
// This is called from our own window proc for now, so we only want to use |
| 707 |
// it when Daodan input is enabled |
| 708 |
if (!DDgUseDaodanInput) |
| 709 |
return false; |
| 710 |
|
| 711 |
switch (msg) { |
| 712 |
case WM_INPUT: |
| 713 |
if (HandleWmInput((HRAWINPUT)lparam, wparam)) { |
| 714 |
*res = 0; |
| 715 |
return true; |
| 716 |
} |
| 717 |
break; |
| 718 |
case WM_WINDOWPOSCHANGED: |
| 719 |
HandleWmWindowPosChanged((WINDOWPOS *)lparam); |
| 720 |
break; |
| 721 |
case WM_KEYDOWN: |
| 722 |
case WM_SYSKEYDOWN: |
| 723 |
case WM_KEYUP: |
| 724 |
case WM_SYSKEYUP: |
| 725 |
HandleWmKeyboard(LOWORD(wparam), LOWORD(lparam), HIWORD(lparam)); |
| 726 |
break; |
| 727 |
} |
| 728 |
|
| 729 |
return false; |
| 730 |
} |
| 731 |
|
| 732 |
static void ONICALL DDrActionBuffer_Get(short* count, LItActionBuffer** buffers) |
| 733 |
{ |
| 734 |
// So, Oni's version of this function was totally different. In unpatched |
| 735 |
// Oni, action buffers were produced at 60Hz by a separate high-priority |
| 736 |
// input thread, LIiInterruptHandleProc, and consumed by |
| 737 |
// LIrActionBuffer_Get, which was called from the game loop and could |
| 738 |
// provide multiple outstanding action buffers to the engine at once. |
| 739 |
// |
| 740 |
// That was a problem for a couple of reasons. Firstly, the resolution of |
| 741 |
// Windows timers is limited by the timer frequency, which can be as low as |
| 742 |
// 15.6ms and in the worst case would cause a delay of 31.2ms between action |
| 743 |
// buffers. That meant that even if Oni was running at a steady 60 fps, the |
| 744 |
// input thread would provide no action buffers on a lot of frames. |
| 745 |
// |
| 746 |
// Secondly, even though Oni drained the list of pending action buffers by |
| 747 |
// calling DDrActionBuffer_Get on every frame, the engine only uses them |
| 748 |
// when the internal game time advances, and that happens on a separate 60Hz |
| 749 |
// timer which was totally unsynchronized with the 60Hz timer on the input |
| 750 |
// thread. That wasn't too much of a problem when the game loop was running |
| 751 |
// at less than 60 fps, but when it ran faster, the only action buffers that |
| 752 |
// got processed were the ones produced when the game timer and the input |
| 753 |
// thread timer happened to tick at the same time, meaning potentially a lot |
| 754 |
// of dropped input. |
| 755 |
// |
| 756 |
// Oni's input system was probably designed that way so that input would |
| 757 |
// still run at 60Hz even on PCs that weren't powerful enough to render at |
| 758 |
// 60 fps. It was a well-meaning design, but due to the aforementioned |
| 759 |
// flaws, we do something much different and simpler here. On the frames |
| 760 |
// that Oni will consume input, we generate a single action buffer inside |
| 761 |
// DDrActionBuffer_Get based on most up-to-date input. |
| 762 |
|
| 763 |
// Update ONgGameState->TargetGameTime. We use TargetGameTime to determine |
| 764 |
// if Oni is going to consume input on this frame. Unfortunately, in |
| 765 |
// unpatched Oni, the call to ONrGameState_UpdateServerTime happened after |
| 766 |
// LIrActionBuffer_Get. In Daodan, we NOOP out the original call and call it |
| 767 |
// here instead, so it runs before our code. |
| 768 |
ONrGameState_UpdateServerTime(ONgGameState); |
| 769 |
bool time_updated = ONgGameState->GameTime != ONgGameState->TargetGameTime; |
| 770 |
|
| 771 |
// Only produce input buffers when input is enabled. LIrActionBuffer_Get |
| 772 |
// does the same thing. Also only produce them when Oni will consume them. |
| 773 |
if (!LIgMode_Internal || !time_updated) { |
| 774 |
*count = 0; |
| 775 |
*buffers = NULL; |
| 776 |
return; |
| 777 |
} |
| 778 |
|
| 779 |
// Add held keys to the action buffer |
| 780 |
for (int i = 0; i < ARRAY_SIZE(DDgInputState); i++) { |
| 781 |
if (DDgInputState[i]) { |
| 782 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 783 |
.input = i, |
| 784 |
.analog = 1.0, |
| 785 |
}); |
| 786 |
} |
| 787 |
} |
| 788 |
|
| 789 |
// Add togglable keys to the action buffer |
| 790 |
if (DDgCapsOniKey && (GetKeyState(VK_CAPITAL) & 0x01)) { |
| 791 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 792 |
.input = DDgCapsOniKey, |
| 793 |
.analog = 1.0, |
| 794 |
}); |
| 795 |
} |
| 796 |
if (DDgScrollOniKey && (GetKeyState(VK_SCROLL) & 0x01)) { |
| 797 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 798 |
.input = DDgScrollOniKey, |
| 799 |
.analog = 1.0, |
| 800 |
}); |
| 801 |
} |
| 802 |
if (DDgNumLockOniKey && (GetKeyState(VK_NUMLOCK) & 0x01)) { |
| 803 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
| 804 |
.input = DDgNumLockOniKey, |
| 805 |
.analog = 1.0, |
| 806 |
}); |
| 807 |
} |
| 808 |
|
| 809 |
// Make a copy of our temporary action buffer with all the input we've |
| 810 |
// gathered this frame. This is the copy that Oni's engine will see. |
| 811 |
static LItActionBuffer buf = { 0 }; |
| 812 |
buf = DDgActionBuffer; |
| 813 |
DDgActionBuffer = (LItActionBuffer) { 0 }; |
| 814 |
|
| 815 |
*count = 1; |
| 816 |
*buffers = &buf; |
| 817 |
} |
| 818 |
|
| 819 |
void DDrInput_PatchUtilityInput(void) |
| 820 |
{ |
| 821 |
// Patch the call to ONrGameState_HandleUtilityInput in |
| 822 |
// ONrGameState_ProcessHeartbeat. This is where Oni checks a bunch of |
| 823 |
// miscellaneous inputs, and where Mac Oni 1.2.1 checks the cheat bindings. |
| 824 |
// It's also where Mac Oni toggles the show_triggervolumes flag. |
| 825 |
DDrPatch_MakeCall((void *)0x004fa91c, (void *)DDrGameState_HandleUtilityInput); |
| 826 |
} |
| 827 |
|
| 828 |
void DDrInput_PatchCustomActions(void) |
| 829 |
{ |
| 830 |
DDrInput_PatchUtilityInput(); |
| 831 |
|
| 832 |
// Replace the function which adds bindings with ours, which checks the list |
| 833 |
// of custom bindings as well |
| 834 |
DDrPatch_MakeJump((void *)LIrBinding_Add, (void *)DDrBinding_Add); |
| 835 |
} |
| 836 |
|
| 837 |
void DDrInput_PatchDaodanInput(void) |
| 838 |
{ |
| 839 |
// In LIrInitialize, NOOP the call to UUrInterruptProc_Install and |
| 840 |
// associated error checking |
| 841 |
DDrPatch_NOOP((char *)(OniExe + 0x421f), 106); |
| 842 |
|
| 843 |
// In LIrPlatform_Initialize, NOOP the Windows version checks so we never |
| 844 |
// use DirectInput |
| 845 |
DDrPatch_NOOP((char *)(OniExe + 0x2e64), 11); |
| 846 |
|
| 847 |
// Replace Oni's Windows message loop with one that does buffered raw input |
| 848 |
// reads and processes all messages |
| 849 |
DDrPatch_MakeJump((void *)LIiPlatform_InputEvent_GetEvent, (void *)DDiPlatform_InputEvent_GetEvent); |
| 850 |
|
| 851 |
// Replace the function that gets the latest input frames |
| 852 |
DDrPatch_MakeJump((void *)LIrActionBuffer_Get, (void *)DDrActionBuffer_Get); |
| 853 |
|
| 854 |
// Replace the function that gets the mouse cursor position |
| 855 |
DDrPatch_MakeJump((void *)LIrPlatform_InputEvent_GetMouse, (void *)DDrPlatform_InputEvent_GetMouse); |
| 856 |
|
| 857 |
// Replace the function that performs platform-specific actions when the |
| 858 |
// input mode changes |
| 859 |
DDrPatch_MakeJump((void *)LIrPlatform_Mode_Set, (void *)DDrPlatform_Mode_Set); |
| 860 |
|
| 861 |
// Replaces the function that tests the state of keyboard keys |
| 862 |
DDrPatch_MakeJump((void *)LIrPlatform_TestKey, (void *)DDrPlatform_TestKey); |
| 863 |
|
| 864 |
// Enable extra key names in key_config.txt |
| 865 |
DDrPatch_MakeJump((void *)LIrTranslate_InputName, (void *)DDrTranslate_InputName); |
| 866 |
|
| 867 |
// Patch out the call to ONrGameState_UpdateServerTime in ONiRunGame because |
| 868 |
// we want to do it earlier, in DDrActionBuffer_Get |
| 869 |
DDrPatch_NOOP((char *)(OniExe + 0xd4708), 11); |
| 870 |
|
| 871 |
DDgUseDaodanInput = true; |
| 872 |
} |