Changeset 1163


Ignore:
Timestamp:
Oct 24, 2021, 4:50:48 AM (3 years ago)
Author:
rossy
Message:

Daodan: Add new local input system based on Raw Input

Location:
Daodan/src
Files:
1 added
13 edited

Legend:

Unmodified
Added
Removed
  • Daodan/src/Daodan.h

    r1017 r1163  
    33
    44#include <windows.h>
     5#include <windowsx.h>
    56#include <assert.h>
    67#include "stdint.h"
  • Daodan/src/Daodan_Config.c

    r1162 r1163  
    252252                        {.intBoolVal = true},
    253253                        {.intBoolVal = true} },
     254                { "daodaninput",
     255                        "New input system that reports input on every frame, supports Raw Input for mice.",
     256                        C_BOOL,
     257                        {.intBoolVal = true},
     258                        {.intBoolVal = true} },
    254259                { "directinput",
    255260                        "Enforces the usage of DirectInput on every system. Should be off for Linux/Wine.",
     
    257262                        {.intBoolVal = true},
    258263                        {.intBoolVal = true} },
     264                { "mousesensitivity",
     265                        "Multiplier for Oni's mouse sensitivity. 1.0 is Oni's default.",
     266                        C_FLOAT,
     267                        {.floatVal = 1.0f},
     268                        {.floatVal = 1.0f} },
    259269                { "disablecmdline",
    260270                        "Disables Oni's existing command line parser as Daodan has its own.",
  • Daodan/src/Oni/GameState.h

    r1161 r1163  
    13531353  int field_FC;
    13541354  int field_100;
    1355   char field_104;
     1355  char SlowMotion;
    13561356  char gap_105[3];
    1357   int SlowMotion;
     1357  int SlowMotionTimer;
    13581358  char gap_10c[4];
    13591359  int field_110;
     
    13621362  char field_138;
    13631363  char gap_139[7];
    1364   int field_140;
     1364  int LastSixtieths;
    13651365  int GameTime;
    1366   int field_148;
     1366  int TargetGameTime;
    13671367  int field_14C;
    13681368  OniLevel* Level;
  • Daodan/src/Oni/Oni.h

    r1017 r1163  
    99#define ONICALL __fastcall
    1010
     11typedef struct {
     12        short x;
     13        short y;
     14} IMtPoint;
    1115
     16// The following headers were written when Daodan used -fpack-struct, and
     17// should be rewritten
     18#pragma pack(push, 1)
    1219#include "stdtypes.h"
    1320#include "BFW_Motoko_Draw.h"
     
    1724#include "GL.h"
    1825#include "OBJt.h"
     26#pragma pack(pop)
     27
     28#include "Input.h"
    1929
    2030#define DDmAssert(expr) assert(expr);
     
    3444} UUtRect;
    3545
    36 typedef struct {
    37         uint32_t input;
    38         float analog;
    39 } LItDeviceInput;
    40 
    4146#include "Symbols_Var.h"
    4247#include "Symbols_Func.h"
  • Daodan/src/Oni/Symbols_Func.h

    r1017 r1163  
    5757
    5858DefFunc(void, ONrGameState_Timer_Start, ONICALL, (char* function, int time), 0x004FD370);
    59 DefFunc(void, ONrGameState_HandleUtilityInput, ONICALL, (const void* inInput), 0x004f6200);
     59DefFunc(void, ONrGameState_HandleUtilityInput, ONICALL, (GameInput *input), 0x004f6200);
    6060DefFunc(uint8_t, ONrDebugKey_WentDown, ONICALL, (uint32_t inKey), 0x005050d0);
    6161
     
    177177DefFunc(void, LIrActionBuffer_Add, ONICALL, (void* unknown, LItDeviceInput* input), 0x00403b30);
    178178
     179// Called during the game loop to run the Windows message loop
     180DefFunc(UUtBool, LIiPlatform_InputEvent_GetEvent, ONICALL, (void), 0x004036d0);
     181
     182// Called by the game loop to get the latest input frames
     183DefFunc(void, LIrActionBuffer_Get, ONICALL, (short* count, LItActionBuffer **buffers), 0x00403be0);
     184
     185// Gets the mouse position
     186DefFunc(void, LIrPlatform_InputEvent_GetMouse, ONICALL, (int active, LItInputEvent *info), 0x00402ca0);
     187
     188// Checks if a keyboard key is pressed
     189DefFunc(UUtBool, LIrPlatform_TestKey, ONICALL, (int key, int active), 0x00403930);
     190
     191// Translates a key name (from key_config.txt) to a key code
     192DefFunc(int, LIrTranslate_InputName, ONICALL, (char *name), 0x00403a90);
     193
     194// Updates TargetGameTime in the game state
     195DefFunc(void, ONrGameState_UpdateServerTime, ONICALL, (GameState *game_state), 0x004fbeb0);
     196
     197// Basically stricmp, but it only ever does ASCII case folding
     198DefFunc(int, UUrString_Compare_NoCase, ONICALL, (const char *str1, const char *str2), 0x004266d0);
     199
     200// A safe strcpy which lets you specify the size of the target buffer and always
     201// NUL-terminates. Kind of like strlcpy. Just don't call it with dest_size = 0.
     202DefFunc(void, UUrString_Copy, ONICALL, (char *dest, const char *src, size_t dest_size), 0x004265f0);
    179203
    180204#undef DefFunc
  • Daodan/src/Oni/Symbols_Var.h

    r994 r1163  
    8484//#define OBJgFlag_DrawNameDistance     (*((void*)0x005ec634))
    8585
     86// Oni's platform input system stores the window HWND in this global
     87#define LIgPlatform_HWND (*((HWND *)0x0055cae8))
     88
     89// Is Oni's input system active? True in gameplay and false in menus.
     90// LIrPlatform_Mode_Set gets called when this changes.
     91#define LIgMode_Internal (*((UUtBool *)0x0055fd40))
     92
     93// List of bindings from Oni's internal key codes to LItActionDescriptions
     94#define LIgBindingArray ((LItBinding *)0x0055fa20)
     95
     96// Is "Invert Mouse" checked in the Options menu? If so, platform specific input
     97// code should invert the cursor's Y offset when building LItActionBuffers.
     98#define LIgMode_InvertMouse (*((UUtBool *)0x0052ecf4))
     99
     100// This is a map from DirectInput scan codes to Oni's internal key codes, the
     101// latter of which mostly match ASCII for a US keyboard layout. DirectInput scan
     102// codes are mostly the same as the PS2 make codes that Windows uses in other
     103// APIs except that extended keys are represented by bit 0x80 being set.
     104#define LIgPlatform_ScanCodeToChar ((uint8_t *)0x005292b8)
     105
     106// Script variable to enable/disable centering the cursor in the window when
     107// processing input. On by default.
     108#define LIgCenterCursor (*((int *)0x0052d88d))
     109
     110// List of bindable input actions
     111#define LIgActionDescriptions ((LItActionDescription*)0x0052db18)
     112
    86113#endif
  • Daodan/src/Patches/Cheater.c

    r1017 r1163  
    297297
    298298
    299 static void BindableCheatCallback (CustomActionCallbackArgument cheatnum) {
     299static void BindableCheatCallback (intptr_t cheatnum) {
    300300        uint8_t res = DDrCheater (cheatnum);
    301301        if (res)
     
    308308        oniCheatCode* cur;
    309309        for (cur = DDr_CheatTable; cur->name != 0; cur++) {
    310 //              char* val = malloc(20);
    311 //              sprintf(val, "cheat_%s", cur->name);
    312                 Input_RegisterCustomAction (cur->name, EVENT_KEYPRESS, 0, BindableCheatCallback, cur->func);
     310                DDrInput_RegisterCustomAction(cur->name, DDcEventType_KeyPress,
     311                                              BindableCheatCallback, cur->func);
    313312        }
    314313}
  • Daodan/src/Patches/Input.c

    r1017 r1163  
    55#include "Input.h"
    66#include "../Oni/Oni.h"
     7#include "../Daodan_Config.h"
    78#include "../Daodan_Patch.h"
    89#include "Utility.h"
    910
    10 #define EVENT_KEYPRESS_SECURETIME 3
    11 
    1211typedef struct {
    13         uint32_t key;
    14         const char* actionname;
    15         ActionEventType_t eventType;
    16         uint32_t keydownTimeoutTicks;
    17         int64_t lastevent;
    18        
    19         CustomActionCallback_t callback;       
    20         CustomActionCallbackArgument callbackArgument;
    21 } DD_CustomAction_t;
    22 
    23 static DD_CustomAction_t customActions[100];
    24 
    25 
    26 void Input_RegisterCustomAction (const char* actionname, ActionEventType_t eventType, uint32_t keydownTimeoutTicks, CustomActionCallback_t callback, CustomActionCallbackArgument callbackArgument) {
    27         uint16_t i = 0;
    28         DD_CustomAction_t* cur = customActions;
    29        
    30         while ( (i < ARRAY_SIZE(customActions)) && (cur->callback != 0)) {
    31                 cur++;
    32                 i++;
    33         }
    34        
    35         if (i < ARRAY_SIZE(customActions)) {
    36                 cur->actionname = actionname;
    37                 cur->eventType = eventType;
    38                 cur->keydownTimeoutTicks = keydownTimeoutTicks;
    39                 cur->callback = callback;
    40                 cur->callbackArgument = callbackArgument;
     12        LItActionDescription descr;
     13        DDtActionEventType eventType;
     14
     15        DDtCustomActionCallback callback;
     16        intptr_t ctx;
     17} DDtCustomAction;
     18
     19static DDtCustomAction DDgCustomActions[100] = { 0 };
     20
     21// Extra keys (make sure these don't collide with Oni's LIc_* keys)
     22enum {
     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
     31static 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
     82static 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
     122bool DDgUseDaodanInput = false;
     123
     124// The Oni key codes that correspond to the togglable keys
     125static uint8_t DDgCapsOniKey = 0;
     126static uint8_t DDgScrollOniKey = 0;
     127static uint8_t DDgNumLockOniKey = 0;
     128
     129// Multiplier for mouse values
     130static 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.
     135static int DDgWheelDelta_V = 0;
     136static 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.
     140static 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.)
     147static 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
     151static char DDgInputState[256] = { 0 };
     152
     153static 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
     207static 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
     243static 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
     275void 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
     310static 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
     319static 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
     342static 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
     358static 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                }
    41401        } else {
    42                 STARTUPMESSAGE("Registering action %s failed, maximum actions reached", actionname);
    43         }
    44 }
    45 
    46 
    47 _LIrBinding_Add Oni_LIrBinding_Add = (_LIrBinding_Add)0;
    48 uint16_t ONICALL DD_LIrBinding_Add(uint32_t key, const char* name) {
    49         DD_CustomAction_t* cur;
    50         for (cur = customActions; cur->callback != 0; cur++) {
    51                 if (!strcmp(name, cur->actionname)) {
    52                         cur->key = key;
    53                         return 0;
    54                 }
    55         }
    56 
    57         return Oni_LIrBinding_Add(key, name);
    58 }
    59 
    60 _LIrActionBuffer_Add Oni_LIrActionBuffer_Add = (_LIrActionBuffer_Add)0;
    61 void ONICALL DD_LIrActionBuffer_Add(void* unknown, LItDeviceInput* input) {
    62         DD_CustomAction_t* cur;
    63         for (cur = customActions; cur->callback != 0; cur++) {
    64                 if (cur->key == input->input) {
    65                         int64_t curTime = UUrMachineTime_Sixtieths();
    66                         if (cur->eventType == EVENT_KEYPRESS) {
    67                                 if (cur->lastevent + EVENT_KEYPRESS_SECURETIME < curTime) {
    68                                         cur->callback(cur->callbackArgument);
    69                                 }
    70                                 cur->lastevent = curTime;
    71                         } else if (cur->eventType == EVENT_KEYDOWN) {
    72                                 if (cur->lastevent + cur->keydownTimeoutTicks < curTime) {
    73                                         cur->callback(cur->callbackArgument);
    74                                         cur->lastevent = curTime;
    75                                 }
     402                ClipCursor(NULL);
     403        }
     404}
     405
     406static 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
     421error:
     422        *event = (LItInputEvent) { 0 };
     423        return;
     424}
     425
     426static 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;
    76442                        }
     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
     464static 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
     480static 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
     574static 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)
    77590                        return;
    78                 }
    79         }
    80         Oni_LIrActionBuffer_Add(unknown, input);
    81 }
    82 
    83 void Input_PatchCode () {
    84         Oni_LIrBinding_Add = DDrPatch_MakeDetour((void*)LIrBinding_Add, (void*)DD_LIrBinding_Add);
    85         Oni_LIrActionBuffer_Add = DDrPatch_MakeDetour((void*)LIrActionBuffer_Add, (void*)DD_LIrActionBuffer_Add);
    86 }
    87 
     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
     607static 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
     635static 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
     658static 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
     669static 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
     703bool 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
     732static 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
     819void 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
     828void 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
     837void 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}
  • Daodan/src/Patches/Input.h

    r1017 r1163  
    44#include "../Daodan.h"
    55
    6 typedef uint32_t CustomActionCallbackArgument;
    7 
    8 typedef void (*CustomActionCallback_t) (CustomActionCallbackArgument argument);
     6typedef void (*DDtCustomActionCallback)(intptr_t ctx);
    97
    108typedef enum {
    11         EVENT_KEYPRESS,
    12         EVENT_KEYDOWN,
    13 } ActionEventType_t;
     9        DDcEventType_KeyPress,
     10        DDcEventType_KeyDown,
     11} DDtActionEventType;
    1412
    15 void Input_PatchCode ();
     13void DDrInput_RegisterCustomAction(const char *name, DDtActionEventType type,
     14                                   DDtCustomActionCallback callback,
     15                                   intptr_t ctx);
     16bool DDrInput_WindowProc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam,
     17                         LRESULT* res);
    1618
    17 void Input_RegisterCustomAction (const char* actionname, ActionEventType_t eventType, uint32_t keydownTimeoutTicks, CustomActionCallback_t callback, CustomActionCallbackArgument callbackArgument);
     19void DDrInput_PatchUtilityInput(void);
     20void DDrInput_PatchCustomActions(void);
     21void DDrInput_PatchDaodanInput(void);
    1822
    1923#endif
  • Daodan/src/Patches/Patches.c

    r1017 r1163  
    183183        return Oni_ONrMechanics_Register(inObjectType, inObjectTypeIndex, inGroupName, inSizeInMemory, inObjectMethods, inFlags, inMechanicsMethods);
    184184}
    185 
    186 _ONrGameState_HandleUtilityInput Oni_ONrGameState_HandleUtilityInput = (_ONrGameState_HandleUtilityInput)0;
    187 void ONICALL DD_ONrGameState_HandleUtilityInput(const void* inInput)
    188 {
    189         Oni_ONrGameState_HandleUtilityInput(inInput);
    190        
    191         if (ONrDebugKey_WentDown(7)) {
    192                 OBJgTriggerVolume_Visible = !OBJgTriggerVolume_Visible;
    193         }
    194 }
    195 
    196185
    197186// Enables d_regen script command. Instead of one global flag to only regenerate player each char has a flag to enable local regeneration
     
    385374                DD_Patch_Chinese();
    386375
    387         // Limit cursor to Oni's window
    388         if (DDrConfig_GetOptOfType("windows.clipcursor", C_BOOL)->value.intBoolVal)
    389         {
    390                 // LIrMode_Set: replace LIrPlatform_Mode_Set call with our hook.
    391                 DDrPatch_MakeCall((void*)(OniExe + 0x00003f9f), (void*) DD_LIrPlatform_Mode_Set);
    392 
    393                 // LIrMode_Set_Internal: replace LIrPlatform_Mode_Set call with our hook.
    394                 DDrPatch_MakeCall((void*)(OniExe + 0x00003fff), (void*) DD_LIrPlatform_Mode_Set);
    395        
    396                 // LIrTerminate: replace LIrPlatform_Terminate call with our hook.
    397                 DDrPatch_MakeCall((void*)(OniExe + 0x000004cb8), (void*) DD_LIrPlatform_Terminate);
    398         }
    399 
    400376        // Disables weapon cooldown exploit
    401377        if (DDrConfig_GetOptOfType("gameplay.cooldowntimer", C_BOOL)->value.intBoolVal)
     
    410386                DDrPatch_MakeJump((void*)gl_enumerate_valid_display_modes, (void*)DD_GLrEnumerateDisplayModes);
    411387        }
    412        
    413         // Forced DirectInput (for Windows NT)
    414         if (DDrConfig_GetOptOfType("windows.directinput", C_BOOL)->value.intBoolVal)
    415         {
    416                 // LIrPlatform_Initialize: replace conditional jump by unconditional
    417                 DDrPatch_Byte((char*)(OniExe + 0x00002e6d), 0xeb);
     388
     389        if (DDrConfig_GetOptOfType("windows.daodaninput", C_BOOL)->value.intBoolVal)
     390        {
     391                DDrInput_PatchDaodanInput();
     392        }
     393        else
     394        {
     395                // The following patches aren't compatible with, or are superseded by
     396                // Daodan input
     397
     398                // Limit cursor to Oni's window
     399                if (DDrConfig_GetOptOfType("windows.clipcursor", C_BOOL)->value.intBoolVal)
     400                {
     401                        // LIrMode_Set: replace LIrPlatform_Mode_Set call with our hook.
     402                        DDrPatch_MakeCall((void*)(OniExe + 0x00003f9f), (void*) DD_LIrPlatform_Mode_Set);
     403
     404                        // LIrMode_Set_Internal: replace LIrPlatform_Mode_Set call with our hook.
     405                        DDrPatch_MakeCall((void*)(OniExe + 0x00003fff), (void*) DD_LIrPlatform_Mode_Set);
     406
     407                        // LIrTerminate: replace LIrPlatform_Terminate call with our hook.
     408                        DDrPatch_MakeCall((void*)(OniExe + 0x000004cb8), (void*) DD_LIrPlatform_Terminate);
     409                }
     410
     411                // Forced DirectInput (for Windows NT)
     412                if (DDrConfig_GetOptOfType("windows.directinput", C_BOOL)->value.intBoolVal)
     413                {
     414                        // LIrPlatform_Initialize: replace conditional jump by unconditional
     415                        DDrPatch_Byte((char*)(OniExe + 0x00002e6d), 0xeb);
     416                }
    418417        }
    419418
     
    449448        // Allow custom actions to be bound through Daodan
    450449        if (DDrConfig_GetOptOfType("gameplay.customactions", C_BOOL)->value.intBoolVal)
    451         {
    452                 Input_PatchCode ();
    453         }
    454        
     450                DDrInput_PatchCustomActions();
     451
    455452        // Hackish fix for Konoko not kicking guns
    456453        // Don't use this, it breaks stairs.
     
    571568        if (DDrConfig_GetOptOfType("devmode.showtriggervolumes", C_BOOL)->value.intBoolVal)
    572569        {
     570                DDrInput_PatchUtilityInput();
    573571                Oni_ONrMechanics_Register = DDrPatch_MakeDetour((void*)ONrMechanics_Register, (void*)DD_ONrMechanics_Register);
    574                 Oni_ONrGameState_HandleUtilityInput = DDrPatch_MakeDetour((void*)ONrGameState_HandleUtilityInput, (void*)DD_ONrGameState_HandleUtilityInput);
    575         }
    576        
     572        }
     573
    577574        // Experiment with allowing enemies to be thrown over railings
    578575        if (DDrConfig_GetOptOfType("gameplay.throwtest", C_BOOL)->value.intBoolVal)
     
    591588                DDrPatch_NOOP((char*) OniExe + 0x0002651c, 6);
    592589                DDrPatch_MakeCall((char*) OniExe + 0x0002651c, (void*) GetClientRect);
     590
     591                // Note: Daodan input makes the following GetCursorPos and SetCursorPos
     592                // patches unnecessary
    593593
    594594                // LIrPlatform_PollInputForAction: fix GetCursorPos call to return client coordinates.
  • Daodan/src/Patches/Utility.c

    r1161 r1163  
    102102                memcpy(output_ptr, default_msg, sizeof(default_msg));
    103103        }
    104        
     104
    105105}
    106106
    107 typedef struct
    108 {
    109         uint16_t x;
    110         uint16_t y;
    111 
    112 } IMtPoint2D;
    113 IMtPoint2D Point = {256, 250};
     107IMtPoint Point = {256, 250};
    114108extern void* TSrTest;
    115109extern void* TSrScores;
  • Daodan/src/Patches/Win32.c

    r1000 r1163  
    33#include "../Daodan.h"
    44#include "../Daodan_Config.h"
     5#include "Input.h"
    56#include "Win32.h"
    67
     
    105106                                return TRUE;
    106107                        }
    107                        
     108
    108109                        break;
    109110        }
    110        
     111
     112        LRESULT res;
     113        if (DDrInput_WindowProc(hWnd, uMsg, wParam, lParam, &res))
     114                return res;
     115
    111116        return ONrPlatform_WindowProc(hWnd, uMsg, wParam, lParam);
    112117}
  • Daodan/src/makefile

    r1045 r1163  
    66DEF = BEA_ENGINE_STATIC
    77INCLUDEPATHS = .
    8 GCCFLAGS = -std=gnu99 -O0 -Wall -fomit-frame-pointer -fpack-struct -Wextra -Wno-pragmas -Wno-unused-variable $(addprefix -I,$(INCLUDEPATHS)) -Wno-unused-parameter $(addprefix -D,$(DEF))
    9 LINKFLAGS = -O0 -Wall -fomit-frame-pointer -fpack-struct -s -mdll
     8GCCFLAGS = -std=gnu99 -ggdb -O0 -Wall -fomit-frame-pointer -Wextra -Wno-pragmas -Wno-unused-variable $(addprefix -I,$(INCLUDEPATHS)) -Wno-unused-parameter $(addprefix -D,$(DEF))
     9LINKFLAGS = -O0 -Wall -fomit-frame-pointer -mdll
    1010LOCALE = LC_MESSAGES=C
    1111
Note: See TracChangeset for help on using the changeset viewer.