ViewVC Help
View File | Revision Log | View Changeset | Root Listing
root/Oni2/Daodan/src/Patches/Input.c
(Generate patch)

Comparing Daodan/src/Patches/Input.c (file contents):
Revision 1017 by alloc, Mon Mar 23 23:29:19 2015 UTC vs.
Revision 1163 by rossy, Sun Oct 24 02:50:48 2021 UTC

# Line 4 | Line 4
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  
10 #define EVENT_KEYPRESS_SECURETIME 3
11
11   typedef struct {
12 <        uint32_t key;
13 <        const char* actionname;
14 <        ActionEventType_t eventType;
15 <        uint32_t keydownTimeoutTicks;
16 <        int64_t lastevent;
17 <        
18 <        CustomActionCallback_t callback;        
19 <        CustomActionCallbackArgument callbackArgument;
20 < } DD_CustomAction_t;
21 <
22 < static DD_CustomAction_t customActions[100];
23 <
24 <
25 < void Input_RegisterCustomAction (const char* actionname, ActionEventType_t eventType, uint32_t keydownTimeoutTicks, CustomActionCallback_t callback, CustomActionCallbackArgument callbackArgument) {
26 <        uint16_t i = 0;
27 <        DD_CustomAction_t* cur = customActions;
28 <        
29 <        while ( (i < ARRAY_SIZE(customActions)) && (cur->callback != 0)) {
30 <                cur++;
31 <                i++;
32 <        }
33 <        
34 <        if (i < ARRAY_SIZE(customActions)) {
35 <                cur->actionname = actionname;
36 <                cur->eventType = eventType;
37 <                cur->keydownTimeoutTicks = keydownTimeoutTicks;
38 <                cur->callback = callback;
39 <                cur->callbackArgument = callbackArgument;
40 <        } else {
41 <                STARTUPMESSAGE("Registering action %s failed, maximum actions reached", actionname);
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 < _LIrBinding_Add Oni_LIrBinding_Add = (_LIrBinding_Add)0;
320 < uint16_t ONICALL DD_LIrBinding_Add(uint32_t key, const char* name) {
321 <        DD_CustomAction_t* cur;
322 <        for (cur = customActions; cur->callback != 0; cur++) {
323 <                if (!strcmp(name, cur->actionname)) {
324 <                        cur->key = key;
325 <                        return 0;
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 <        return Oni_LIrBinding_Add(key, name);
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 < _LIrActionBuffer_Add Oni_LIrActionBuffer_Add = (_LIrActionBuffer_Add)0;
427 < void ONICALL DD_LIrActionBuffer_Add(void* unknown, LItDeviceInput* input) {
428 <        DD_CustomAction_t* cur;
429 <        for (cur = customActions; cur->callback != 0; cur++) {
430 <                if (cur->key == input->input) {
431 <                        int64_t curTime = UUrMachineTime_Sixtieths();
432 <                        if (cur->eventType == EVENT_KEYPRESS) {
433 <                                if (cur->lastevent + EVENT_KEYPRESS_SECURETIME < curTime) {
434 <                                        cur->callback(cur->callbackArgument);
435 <                                }
436 <                                cur->lastevent = curTime;
437 <                        } else if (cur->eventType == EVENT_KEYDOWN) {
438 <                                if (cur->lastevent + cur->keydownTimeoutTicks < curTime) {
439 <                                        cur->callback(cur->callbackArgument);
440 <                                        cur->lastevent = curTime;
441 <                                }
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          }
80        Oni_LIrActionBuffer_Add(unknown, input);
605   }
606  
607 < void Input_PatchCode () {
608 <        Oni_LIrBinding_Add = DDrPatch_MakeDetour((void*)LIrBinding_Add, (void*)DD_LIrBinding_Add);
609 <        Oni_LIrActionBuffer_Add = DDrPatch_MakeDetour((void*)LIrActionBuffer_Add, (void*)DD_LIrActionBuffer_Add);
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 + }

Diff Legend

Removed lines
+ Added lines
< Changed lines (old)
> Changed lines (new)