├── LGS extension ├── lua51.dll ├── luajit.exe ├── wluajit.exe ├── D_SAVER.lua └── LGS_extension.lua ├── LICENSE ├── README.md └── LGS_script_template.lua /LGS extension/lua51.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Egor-Skriptunoff/LGS_extension/HEAD/LGS extension/lua51.dll -------------------------------------------------------------------------------- /LGS extension/luajit.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Egor-Skriptunoff/LGS_extension/HEAD/LGS extension/luajit.exe -------------------------------------------------------------------------------- /LGS extension/wluajit.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Egor-Skriptunoff/LGS_extension/HEAD/LGS extension/wluajit.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Egor Skriptunoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LGS_extension 2 | `LGS_script_template.lua` is a template for writing your own Lua scripts in the Logitech Gaming Software programming environment. 3 | Both **LGS** and **GHUB** are supported. 4 | Five additional useful features are implemented here: 5 | 6 | 1. Function `print()` now displays messages in the bottom window of the script editor, you can use it the same way as in original Lua; 7 | 2. `random()` is an improved drop-in replacement for `math.random()`: better random numbers quality, no need to explicitly set the seed; 8 | 3. LGS standard functions `PressMouseButton()`, `IsMouseButtonPressed()`, ... now accept strings `"L"`, `"R"`, `"M"` instead of numbers for the first 3 mouse buttons; 9 | 4. You can get and set mouse coordinates in pixels: `GetMousePositionInPixels()`, `SetMousePositionInPixels()`; 10 | 5. Global variable `D` in your Lua script is now a persistent Lua table: it is automatically saved to disk on profile exit and is automatically loaded from disk on profile start. 11 | 12 | Prior to using this template for writing your own LGS scripts, you have to copy some additional files to your disk. 13 | See details in **How to install** section at the end of this README. 14 | 15 | ---- 16 | 17 | #### FEATURE #1: You can see the output of `print()` in the LGS script editor 18 | ```lua 19 | print(...) 20 | ``` 21 | This function is reimplemented to display messages in the bottom window of the script editor. 22 | You can use `print()` just like you do in standard Lua! 23 | When using `print()` instead of `OutputLogMessage()`, don't append `"\n"` to a message. 24 | 25 | ---- 26 | 27 | #### FEATURE #2: Random numbers of very high quality 28 | ```lua 29 | random() -- float 0 <= x < 1 30 | random(n) -- integer 1 <= x <= n 31 | random(m, n) -- integer m <= x <= n 32 | ``` 33 | This new function is a drop-in replacement for standard Lua function `math.random()`. 34 | It generates different sequences of random numbers on every profile load, so you don't need to set the seed explicitly. 35 | The random number generator absorbs entropy from every event processed by `OnEvent()`. 36 | It takes into account everything: event type, button index, mouse position on the screen, current date and running time. 37 | This entropy is converted by SHAKE128 (SHA3 hash function) into a stream of pseudo-random bits. 38 | That's why function `random()` returns random numbers having excellent statistical properties. 39 | Actually, after user clicked mouse buttons 100-200 times (no hurry please), these pseudo-random numbers might be considered cryptographically strong. 40 | 41 | The code example #1 at the end of `LGS_script_template.lua` shows how you could simulate typing random alphanumeric string in Lua script. 42 | A user should open a text editor and press-and-hold middle mouse button until the string is long enough. 43 | This is the easiest way to generate a strong password. 44 | 45 | ```lua 46 | GetEntropyCounter() 47 | ``` 48 | This function returns estimation of lower bound of number of random bits consumed by random numbers mixer. 49 | Wait until it reaches 256 bits prior to generating crypto keys. 50 | 51 | ```lua 52 | SHA3_224(message) 53 | SHA3_256(message) 54 | SHA3_384(message) 55 | SHA3_512(message) 56 | SHAKE128(digest_size_in_bytes, message) 57 | SHAKE256(digest_size_in_bytes, message) 58 | ``` 59 | I don't know why you might need them, but SHA3 hash functions are available :-) 60 | The first four `SHA3_224`, `SHA3_256`, `SHA3_384`, `SHA3_512` generate message digest of fixed length. 61 | The last two `SHAKE128`, `SHAKE256` generate message digest of potentially infinite length. 62 | 63 | Example: How to get SHA3-digest of your message: 64 | ```lua 65 | SHA3_224("The quick brown fox jumps over the lazy dog") == "d15dadceaa4d5d7bb3b48f446421d542e08ad8887305e28d58335795" 66 | SHAKE128(5, "The quick brown fox jumps over the lazy dog") == "f4202e3c58" 67 | ``` 68 | Example: How to convert your short password into infinite sequence of very high quality pseudo-random bytes: 69 | ```lua 70 | -- start the sequence, initialize it with your password 71 | local get_hex_byte = SHAKE128(-1, "your password") 72 | while .... do 73 | -- get next byte from the inifinite sequence of pseudo-random bytes 74 | local next_random_byte = tonumber(get_hex_byte(), 16) -- integer 0 <= n <= 255 75 | -- get next dword 76 | local next_random_dword = tonumber(get_hex_byte(4), 16) -- integer 0 <= n <= 4294967295 77 | -- get next floating point number 0 <= x < 1 78 | local next_random_double = (tonumber(get_hex_byte(3), 16) % 2^21 * 2^32 + tonumber(get_hex_byte(4), 16)) / 2^53 79 | .... 80 | end 81 | ``` 82 | ---- 83 | 84 | #### FEATURE #3: Handy names for the first three mouse buttons 85 | `"L"`, `"R"`, `"M"` are new names for the first three mouse buttons. 86 | As you might have noticed, there is an unpleasant feature in LGS: Logitech and Microsoft enumerate mouse buttons differently. 87 | In `OnEvent("MOUSE_BUTTON_PRESSED", arg, "mouse")` the parameter `arg` uses Logitech order: 88 | ``` 89 | 1=Left, 2=Right, 3=Middle, 4=Backward(X1), 5=Forward(X2), 6, 7, 8,... 90 | ``` 91 | In `PressMouseButton(button)` and `IsMouseButtonPressed(button)` the parameter `button` uses Microsoft order: 92 | ``` 93 | 1=Left, 2=Middle, 3=Right, 4=X1(Backward), 5=X2(Forward) 94 | ``` 95 | As you see, Right and Middle buttons are swapped; this is very confusing. 96 | To make your code more clear and less error-prone, try to avoid using numbers `1`, `2` and `3`. 97 | Now you can use strings `"L"`, `"R"`, `"M"` for the first three mouse buttons in all the functions. 98 | Two modifications have been made: 99 | 1. The following standard LGS functions now accept strings `"L"`, `"R"`, `"M"` as its argument: `PressMouseButton()`, `ReleaseMouseButton()`, `PressAndReleaseMouseButton()`, `IsMouseButtonPressed()` 100 | 2. `mouse_button` variable was defined inside `OnEvent()` function body, it contains: 101 | - either string `"L"`, `"R"`, `"M"` (for the first three mouse buttons) 102 | - or number `4`, `5`, `6`, `7`, `8`,... (for other mouse buttons). 103 | 104 | These modifications don't break compatibility with your old code. 105 | You can still use numbers if you want: 106 | ```lua 107 | if event == "MOUSE_BUTTON_PRESSED" and arg == 2 then -- 2 = RMB in Logitech order 108 | repeat 109 | ... 110 | Sleep(50) 111 | until not IsMouseButtonPressed(3) -- 3 = RMB in Microsoft order 112 | ``` 113 | But using `"L"`/`"M"`/`"R"` allows you to avoid inconsistent numbers: 114 | ```lua 115 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == "R" then 116 | repeat 117 | ... 118 | Sleep(50) 119 | until not IsMouseButtonPressed("R") 120 | ``` 121 | 122 | ---- 123 | 124 | #### FEATURE #4: Pixel-oriented functions for mouse coordinates 125 | ```lua 126 | GetMousePositionInPixels() 127 | SetMousePositionInPixels(x,y) 128 | ``` 129 | You can now get and set mouse cursor position **in pixels**. 130 | `GetMousePositionInPixels()` returns 6 values (you would probably need only the first two): 131 | ``` 132 | x_in_pixels, -- integer from 0 to (screen_width-1) 133 | y_in_pixels, -- integer from 0 to (screen_height-1) 134 | screen_width_in_pixels, -- for example, 1920 135 | screen_height_in_pixels, -- for example, 1080 136 | x_64K, -- normalized x coordinate 0..65535, this is the first value returned by 'GetMousePosition()' 137 | y_64K -- normalized y coordinate 0..65535, this is the second value returned by 'GetMousePosition()' 138 | ``` 139 | We already have standard LGS function `MoveMouseRelative()` which operates with distance in pixels, but it has two problems. 140 | The first problem: `MoveMouseRelative` is limited to narrow distance range: from -127 to +127 pixels from the current position. 141 | ``` 142 | MoveMouseRelative(300, 300) -- This invocation will work incorrectly because 300 is greater than 127 143 | ``` 144 | Now you can move mouse cursor farther than 127 pixels away using the new functions: 145 | ```lua 146 | local current_x, current_y = GetMousePositionInPixels() 147 | SetMousePositionInPixels(current_x + 300, current_y + 300) 148 | ``` 149 | The second problem with `MoveMouseRelative` is that it works incorrectly when **Acceleration (Enhance Pointer Precision)** flag is checked in **Pointer settings** tab (this is the third icon from the left at the bottom of the LGS application window): the real distance (how far the mouse pointer moves after you have invoked the function) does not equal to the number of pixels requested in the arguments of `MoveMouseRelative`. 150 | The **Acceleration** flag is set by default, so this problem hits every user who tries to use `MoveMouseRelative` in his scripts. 151 | Meanwhile the new functions `GetMousePositionInPixels` and `SetMousePositionInPixels` work fine independently of **Acceleration** flag. 152 | 153 | *Important note:* 154 | Don't forget that you must wait a bit, for example `Sleep(10)`, after simulating any of the following actions: 155 | 156 | - mouse move, 157 | - button press, 158 | - button release. 159 | 160 | In other words, if you read `GetMousePositionInPixels` right after invocation of `SetMousePositionInPixels` without a `Sleep` in between, you will get the old mouse coordinates instead of the new ones. 161 | This is because Windows needs some time to perform your simulation request. 162 | Windows messaging system works slowly, there is nothing you can do to make the simulations instant. 163 | 164 | *Important note:* 165 | The script `LGS_script_template.lua` requires **one second** for initialization. 166 | In other words, when this LGS profile is started, you will have to wait for 1 second before you're able to play. 167 | Explanation: 168 | Every time this profile is activated (and every time when your game changes the screen resolution) the process of automatic determination of screen resolution is restarted. This is necessary for correct working of pixel-oriented mouse functions. 169 | This process takes about one second. During this second, mouse cursor will be programmatically moved some distance away from its current location. This cursor movement might be a hindrance to use your mouse, so just wait until the cursor stops moving. 170 | 171 | --- 172 | 173 | #### FEATURE #5: Persistent table D 174 | Now you have special global variable `D` which contains a Lua table; you can store your own data inside this table. 175 | The variable `D` is persistent: it is automatically saved to disk on profile exit and is automatically loaded from disk on profile start. 176 | So, **D** means **Disk**. 177 | You can accumulate some information in table `D` across years of playing (e.g the total number of times you run this game). 178 | Table `D` is allowed to contain only simple types: strings, numbers, booleans and nested tables. 179 | Circular table refrences (non-tree tables) are allowed, for example: `D.a={}; D.b={}; D.a.next=D.b; D.b.prev=D.a` 180 | Functions, userdatum and metatables will not be saved to disk (they will be silently replaced with nils), so don't store functions inside `D`. 181 | 182 | The table `D` data will be stored in a file; you should give it a name. 183 | By default the line #187 in `LGS_script_template.lua` looks like the following: 184 | ```lua 185 | D_filename = "D_for_profile_1.lua" 186 | ``` 187 | Replace `profile_1` with current profile name (use only English letters and digits). 188 | This file will be located in the `C:\LGS extension` folder and will contain human-readable data. 189 | If two profiles have the same `D_filename` then they share the same `D` table, that's why you might want to make `D_filename` values different for every profile. 190 | 191 | To avoid using my .EXE and .DLL files on your computer, you can turn **feature #5** off: 192 | 1. Remove the assignment `D_filename = "..."` from `LGS_script_template.lua` line #187 193 | 2. (optional) Delete all the files from the folder `C:\LGS extension` except the main module `LGS_extension.lua` 194 | 3. (optional) Delete the command **RUN_D_SAVER** from LGS/GHUB application. 195 | 196 | ---- 197 | 198 | # How to install: 199 | 200 | #### Step 1. Create folder `C:\LGS extension` 201 | 202 | #### Step 2. Copy the following 5 files into the folder `C:\LGS extension` 203 | ``` 204 | Filename Description SHA256 sums of binary files 205 | -------- ----------- --------------------------- 206 | LGS_extension.lua the main module 207 | D_SAVER.lua external script which actually writes table D to the file 208 | wluajit.exe windowless LuaJIT 2.1 x64 (doesn't create a console window) E9C320E67020C2D85208AD449638BF1566C3ACE4CDA8024079B97C26833BF483 209 | lua51.dll LuaJIT DLL 112CB858E8448B0E2A6A6EA5CF9A7C25CFD45AC8A8C1A4BA85ECB04B20C2DE88 210 | luajit.exe LuaJIT 2.1 x64 (does create a console window) 0F593458024EB62035EC41342FC12DAA26108639E68D6236DCF3048E527AE6E5 211 | ``` 212 | #### Step 3. Create new command 213 | The instructions are different for LGS and GHUB: 214 | * In **LGS**: 215 | - Run **Logitech Gaming Software** application 216 | - Open **Customise buttons** tab 217 | - Select profile 218 | - In the left side you will see the **Commands** pane (list of bindable actions such as keyboard keys, macros, etc), press the big plus sign to add new command 219 | - In the **Command Editor**, select the **Shortcut** in the left pane 220 | - Set the 1st text field **Name** to `RUN_D_SAVER` 221 | - Set the 2nd text field **Enter a shortcut** to `wluajit.exe D_SAVER.lua` 222 | - Set the 3rd text field **Working Directory** to `C:\LGS extension` 223 | - Press **OK** button to close the **Command Editor** 224 | - *Important note:* 225 | DO NOT bind the **RUN_D_SAVER** command to any button, this command must not be invoked by a human. 226 | * In **GHUB**: 227 | - Run **G HUB** application 228 | - Click on the mouse picture to open **Gear page** 229 | - Select **Assignments** icon (plus-inside-square) at the left edge 230 | - Select **SYSTEM** tab (it's the last one in the row of tabs: _COMMANDS-KEYS-ACTIONS-MACROS-SYSTEM_) 231 | - Click **ADD APPLICATION** under the **Launch Application** list, a file selection dialogue window will appear 232 | - Find the file `C:\LGS extension\luajit.exe` and click it 233 | - Click **ADD ARGUMENTS** and replace `New argument` with `D_SAVER.lua` 234 | - Click **SAVE** 235 | - Select **MACROS** tab (in the row of tabs: _COMMANDS-KEYS-ACTIONS-MACROS-SYSTEM_) 236 | - Click **CREATE NEW MACRO** 237 | - Set `RUN_D_SAVER` as macro name 238 | - Select **NO REPEAT** type of macro 239 | - Click **START NOW** 240 | - Click **LAUNCH APPLICATION** 241 | - Select **luajit** 242 | - Click **SAVE** 243 | - Select **SYSTEM** tab 244 | - Click **luajit** in the **Launch Application** list 245 | - Click **DELETE** 246 | - Click **YES** to confirm 247 | - *Important note:* 248 | Now you have the **RUN_D_SAVER** macro on the **MACROS** tab. 249 | NEVER manually assign this macro to any button, this macro must not be invoked by a human. 250 | 251 | #### Step 4. Copy the content of `LGS_script_template.lua` into LGS/GHUB Lua script editor. 252 | 253 | ---- 254 | 255 | ### How to move the folder `C:\LGS extension` to another location 256 | - Move all the files from `C:\LGS extension` to your new folder. 257 | - Change the path in the assignment `extension_module_full_path = ...` in `LGS_script_template.lua` line #280. 258 | _Please note that LGS and GHUB don't allow you to use non-English letters in string literals in your Lua script. 259 | All symbols beyond 7-bit ASCII in your folder path must be converted to their Windows ANSI codes. 260 | Example: `D:\Папка\LGS` should be written as either `path = "D:\\\207\224\239\234\224\\LGS"` or 261 | `path = [[D:\]]..string.char(207,224,239,234,224)..[[\LGS]]`._ 262 | - Modify the command **RUN_D_SAVER**: 263 | * In **LGS**: 264 | - Edit the command **RUN_D_SAVER** and write your new folder path to the 3rd text field **Working Directory** 265 | * In **GHUB**: 266 | - Select **MACROS** tab (in the row of tabs: _COMMANDS-KEYS-ACTIONS-MACROS-SYSTEM_) 267 | - Click **RUN_D_SAVER** to enter macro editor 268 | - Click **MACRO OPTIONS** in the top right corner 269 | - Click **DELETE THIS MACRO** 270 | - Click **YES** to confirm 271 | - Create the command **RUN_D_SAVER** again: follow the instructions from _"Step 3. Create new command"_ in _"How to install"_ section above, but use your new folder path instead of `C:\LGS extension` 272 | 273 | ---- 274 | 275 | ## Known issue: 276 | 277 | #### LGS does not generate `PROFILE_DEACTIVATED` event on Windows shutdown, so table `D` on disk is not updated for the last profile being active before turning your computer off (usually it's the "Default profile"). 278 | 279 | Example: 280 | 281 | 1. You're turning your computer on: 282 | - **Default profile** is activated. 283 | 2. You're starting a game: 284 | - **Default profile** is deactivated, table `D` for **Default profile** is saved to disk in its `PROFILE_DEACTIVATED` event handler, 285 | - **Your Game** profile is activated. 286 | 3. You're exiting the game: 287 | - **Your Game** profile is deactivated, table `D` for **Your Game** is saved to disk in its `PROFILE_DEACTIVATED` event handler, 288 | - **Default profile** is activated. 289 | 4. You're shutting down Windows: 290 | - **Default profile** should be deactivated, but `PROFILE_DEACTIVATED` event is not generated (bug in LGS), so table `D` for **Default profile** is not updated on disk. 291 | 292 | This is LGS-only issue. 293 | GHUB is OK, table `D` is updated always. 294 | 295 | -------------------------------------------------------------------------------- /LGS_script_template.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------------------- 2 | -- LGS_script_template.lua 3 | --------------------------------------------------------------------------------------------- 4 | -- Version: 2020-05-13 5 | -- Author: Egor Skriptunoff 6 | -- License: MIT License 7 | -- 8 | -- This is a template for writing your own Lua scripts in the 'Logitech Gaming Software' programming environment. 9 | -- Both LGS and GHUB are supported. 10 | -- Five additional useful features are implemented here: 11 | -- 1. Function 'print()' now displays messages in the bottom window of the script editor, you can use it the same way as in original Lua; 12 | -- 2. 'random()' is an improved drop-in replacement for 'math.random()': better random numbers quality, no need to explicitly set the seed; 13 | -- 3. LGS standard functions 'PressMouseButton()', 'IsMouseButtonPressed()',... now accept strings "L", "R", "M" (instead of numbers) for the first 3 mouse buttons; 14 | -- 4. You can get and set mouse coordinates in pixels: 'GetMousePositionInPixels()', 'SetMousePositionInPixels()'; 15 | -- 5. Global variable 'D' in your Lua script is now a persistent Lua table: it is automatically saved to disk on profile exit and is automatically loaded from disk on profile start. 16 | -- 17 | -- Prior to using this template for writing your own LGS scripts, you have to copy some additional files to your disk. 18 | -- See details in 'How to install' section at the line #202 in this file. 19 | -- 20 | -- 21 | -- 22 | -- ------------------------------------------------------------------------------------------ 23 | -- FEATURE #1 - You can see the output of 'print()' in the LGS script editor 24 | -- ------------------------------------------------------------------------------------------ 25 | -- print(...) 26 | -- ------------------------------------------------------------------------------------------ 27 | -- This function is reimplemented to display messages in the bottom window of the script editor. 28 | -- You can use 'print()' just like you do in standard Lua! 29 | -- When using 'print()' instead of 'OutputLogMessage()', don't append "\n" to a message. 30 | -- 31 | -- 32 | -- 33 | -- ------------------------------------------------------------------------------------------ 34 | -- FEATURE #2 - Random numbers of very high quality 35 | -- ------------------------------------------------------------------------------------------ 36 | -- random() -- float 0 <= x < 1 37 | -- random(n) -- integer 1 <= x <= n 38 | -- random(m, n) -- integer m <= x <= n 39 | -- ------------------------------------------------------------------------------------------ 40 | -- This new function is a drop-in replacement for standard Lua function 'math.random()'. 41 | -- It generates different sequences of random numbers on every profile load, so you don't need to set the seed explicitly. 42 | -- The random number generator absorbs entropy from every event processed by 'OnEvent()'. 43 | -- It takes into account everything: event type, button index, mouse position on the screen, current date and running time. 44 | -- This entropy is converted by SHAKE128 (SHA3 hash function) into a stream of pseudo-random bits. 45 | -- That's why function 'random()' returns random numbers having excellent statistical properties. 46 | -- Actually, after user clicked mouse buttons 100-200 times (no hurry please), 47 | -- these pseudo-random numbers might be considered cryptographically strong. 48 | -- 49 | -- The code example #1 (at the end of this file) shows how you could simulate typing random alphanumeric string in Lua script. 50 | -- A user should open a text editor and press-and-hold middle mouse button until the string is long enough. 51 | -- This is the easiest way to generate a strong password. 52 | -- 53 | -- ------------------------------------------------------------------------------------------ 54 | -- GetEntropyCounter() 55 | -- ------------------------------------------------------------------------------------------ 56 | -- This function returns estimation of lower bound of number of random bits consumed by random numbers mixer. 57 | -- Wait until it reaches 256 bits prior to generating crypto keys. 58 | -- 59 | -- ------------------------------------------------------------------------------------------ 60 | -- SHA3_224(message) 61 | -- SHA3_256(message) 62 | -- SHA3_384(message) 63 | -- SHA3_512(message) 64 | -- SHAKE128(digest_size_in_bytes, message) 65 | -- SHAKE256(digest_size_in_bytes, message) 66 | -- ------------------------------------------------------------------------------------------ 67 | -- I don't know why you might need them, but SHA3 hash functions are available :-) 68 | -- The first four (SHA3_224, SHA3_256, SHA3_384, SHA3_512) generate message digest of fixed length. 69 | -- The last two (SHAKE128, SHAKE256) generate message digest of potentially infinite length. 70 | -- Example: How to get SHA3-digest of your message: 71 | -- SHA3_224("The quick brown fox jumps over the lazy dog") == "d15dadceaa4d5d7bb3b48f446421d542e08ad8887305e28d58335795" 72 | -- SHAKE128(5, "The quick brown fox jumps over the lazy dog") == "f4202e3c58" 73 | -- Example: How to convert your short password into infinite sequence of very high quality pseudo-random bytes: 74 | -- -- start the sequence, initialize it with your password 75 | -- local get_hex_byte = SHAKE128(-1, "your password") 76 | -- while .... do 77 | -- -- get next integer number from the inifinite sequence of pseudo-random bytes 78 | -- local next_random_byte = tonumber(get_hex_byte(), 16) -- integer 0 <= n <= 255 79 | -- local next_random_dword = tonumber(get_hex_byte(4), 16) -- integer 0 <= n <= 4294967295 80 | -- -- get next floating point number 0 <= x < 1 81 | -- local next_random_double = (tonumber(get_hex_byte(3), 16) % 2^21 * 2^32 + tonumber(get_hex_byte(4), 16)) / 2^53 82 | -- .... 83 | -- end 84 | -- 85 | -- 86 | -- 87 | -- ------------------------------------------------------------------------------------------ 88 | -- FEATURE #3 - Handy names for first three mouse buttons 89 | -- ------------------------------------------------------------------------------------------ 90 | -- "L", "R", "M" are now names for the first three mouse buttons 91 | -- ------------------------------------------------------------------------------------------ 92 | -- There is an unpleasant feature in LGS: Logitech and Microsoft enumerate mouse buttons differently. 93 | -- In 'OnEvent("MOUSE_BUTTON_PRESSED", arg, "mouse")' parameter 'arg' uses Logitech order: 94 | -- 1=Left, 2=Right, 3=Middle, 4=Backward(X1), 5=Forward(X2), 6,7,8,... 95 | -- In 'PressMouseButton(button)' and 'IsMouseButtonPressed(button)' parameter 'button' uses Microsoft order: 96 | -- 1=Left, 2=Middle, 3=Right, 4=X1(Backward), 5=X2(Forward) 97 | -- As you see, Right and Middle buttons are swapped; this is very confusing. 98 | -- To make your code more clear and less error-prone, try to avoid using numbers 1, 2 and 3. 99 | -- Now you can use strings "L", "R", "M" for the first three mouse buttons in all the functions. 100 | -- Two modifications have been made: 101 | -- 1) The following standard LGS functions now accept strings "L", "R", "M" as its argument: 102 | -- PressMouseButton(), 103 | -- ReleaseMouseButton(), 104 | -- PressAndReleaseMouseButton(), 105 | -- IsMouseButtonPressed() 106 | -- 2) 'mouse_button' variable was defined inside OnEvent() function body, it contains: 107 | -- either string "L", "R", "M" (for the first three mouse buttons) 108 | -- or number 4, 5, 6, 7, 8,... (for other mouse buttons). 109 | -- These modifications don't break compatibility with your old code. 110 | -- You can still use numbers if you want: 111 | -- if event == "MOUSE_BUTTON_PRESSED" and arg == 2 then -- 2 = RMB in Logitech order 112 | -- repeat 113 | -- ... 114 | -- Sleep(50) 115 | -- until not IsMouseButtonPressed(3) -- 3 = RMB in Microsoft order 116 | -- But using "L"/"M"/"R" allows you to avoid inconsistent numbers: 117 | -- if event == "MOUSE_BUTTON_PRESSED" and mouse_button == "R" then 118 | -- repeat 119 | -- ... 120 | -- Sleep(50) 121 | -- until not IsMouseButtonPressed("R") 122 | -- 123 | -- 124 | -- 125 | -- ------------------------------------------------------------------------------------------ 126 | -- FEATURE #4 - Pixel-oriented functions for mouse coordinates 127 | -- ------------------------------------------------------------------------------------------ 128 | -- GetMousePositionInPixels() 129 | -- SetMousePositionInPixels(x,y) 130 | -- ------------------------------------------------------------------------------------------ 131 | -- You can now get and set mouse cursor position IN PIXELS. 132 | -- GetMousePositionInPixels() returns 6 values (you would probably need only the first two): 133 | -- x_in_pixels, -- integer from 0 to (screen_width-1) 134 | -- y_in_pixels, -- integer from 0 to (screen_height-1) 135 | -- screen_width_in_pixels, -- for example, 1920 136 | -- screen_height_in_pixels, -- for example, 1080 137 | -- x_64K, -- normalized x coordinate 0..65535, this is the first value returned by 'GetMousePosition()' 138 | -- y_64K -- normalized y coordinate 0..65535, this is the second value returned by 'GetMousePosition()' 139 | 140 | -- We already have standard LGS function 'MoveMouseRelative' which operates with distance in pixels, but it has two problems: 141 | -- The first problem: 'MoveMouseRelative' is limited to narrow distance range: from -127 to +127 pixels from the current position. 142 | -- MoveMouseRelative(300, 300) -- This invocation will work incorrectly because 300 is greater than 127 143 | -- Now you can move mouse cursor farther than 127 pixels away using the new functions: 144 | -- local current_x, current_y = GetMousePositionInPixels() 145 | -- SetMousePositionInPixels(current_x + 300, current_y + 300) 146 | -- The second problem with 'MoveMouseRelative' is that it works incorrectly when 'Acceleration (Enhance Pointer Precision)' flag 147 | -- is checked in 'Pointer settings' tab (this is the third icon from the left at the bottom of the LGS application window): 148 | -- the real distance (how far the mouse pointer moves after you have invoked the function) does not equal 149 | -- to the number of pixels requested in the arguments of 'MoveMouseRelative'. 150 | -- The 'Acceleration' flag is set by default, so this problem hits every user who tryes to use 'MoveMouseRelative' in his scripts. 151 | -- Meanwhile the new functions 'GetMousePositionInPixels' and 'SetMousePositionInPixels' work fine independently of 'Acceleration' flag. 152 | -- 153 | -- Don't forget that you must wait a bit, for example Sleep(10), after simulating any of the following actions: 154 | -- mouse move, 155 | -- button press, 156 | -- button release. 157 | -- In other words, if you read 'GetMousePositionInPixels' right after invocation of 'SetMousePositionInPixels' 158 | -- without a 'Sleep' in between, you will get the old mouse coordinates instead of the new ones. 159 | -- This is because Windows needs some time to perform your simulation request. 160 | -- Windows messaging system works slowly, there is nothing you can do to make the simulations instant. 161 | -- 162 | -- 163 | -- Important note: 164 | -- The script 'LGS_script_template.lua' requires one second for initialization. 165 | -- In other words, when this LGS profile is started, you will have to wait for 1 second before you're able to play. 166 | -- Explanation: 167 | -- Every time this profile is activated (and every time when your game changes the screen resolution) 168 | -- the process of automatic determination of screen resolution is restarted 169 | -- This is necessary for correct working of pixel-oriented mouse functions. 170 | -- This process takes about one second. 171 | -- During this second, mouse cursor will be programmatically moved some distance away from its current location. 172 | -- This cursor movement might be a hindrance to use your mouse, so just wait until the cursor stops moving. 173 | -- 174 | -- 175 | -- 176 | -- ------------------------------------------------------------------------------------------ 177 | -- FEATURE #5 - Persistent table D 178 | -- ------------------------------------------------------------------------------------------ 179 | -- Now you have special global variable 'D' which contains a Lua table; you can store your own data inside this table. 180 | -- The variable 'D' is persistent: it is automatically saved to disk on profile exit and is automatically loaded from disk on profile start. 181 | -- So, 'D' means 'Disk'. 182 | -- You can accumulate some information in table 'D' across years of playing (e.g the total number of times you run this game). 183 | -- Table 'D' is allowed to contain only simple types: strings, numbers, booleans and nested tables. 184 | -- Circular table refrences (non-tree tables) are allowed, for example: D.a={}; D.b={}; D.a.next=D.b; D.b.prev=D.a 185 | -- Functions, userdatum and metatables will not be saved to disk (they will be silently replaced with nils), so don't store functions inside D. 186 | -- The table 'D' data will be stored in a file; you should give it a name: 187 | D_filename = "D_for_profile_1.lua" 188 | -- Replace 'profile_1' with your profile name (use only English letters and digits). 189 | -- This file will be located in the 'C:\LGS extension' folder and will contain human-readable data. 190 | -- If two profiles have the same 'D_filename' then they share the same table 'D'. 191 | -- That's why you might want to make 'D_filename' different for every profile. 192 | -- 193 | -- To avoid using my .EXE and .DLL files on your computer, you can turn feature #5 off: 194 | -- 1) Remove the assignment 'D_filename = ...' from this file (line #187) 195 | -- 2) (optional) Delete all the files from the folder 'C:\LGS extension' except the main module 'LGS_extension.lua' 196 | -- 3) (optional) Delete command 'RUN_D_SAVER' from LGS/GHUB application 197 | -- 198 | -- 199 | -- 200 | -- 201 | -- ------------------------------------------------------------------------------------------ 202 | -- How to install 203 | -- ------------------------------------------------------------------------------------------ 204 | -- 1) Create folder 'C:\LGS extension' 205 | -- 2) Copy the following 5 files into the folder 'C:\LGS extension' (SHA256 sums are provided for binary files): 206 | -- LGS_extension.lua the main module 207 | -- D_SAVER.lua external script which actually writes table D to the file 208 | -- wluajit.exe windowless LuaJIT 2.1 x64 (doesn't create a console window) E9C320E67020C2D85208AD449638BF1566C3ACE4CDA8024079B97C26833BF483 209 | -- lua51.dll LuaJIT DLL 112CB858E8448B0E2A6A6EA5CF9A7C25CFD45AC8A8C1A4BA85ECB04B20C2DE88 210 | -- luajit.exe LuaJIT 2.1 x64 (does create a console window) 0F593458024EB62035EC41342FC12DAA26108639E68D6236DCF3048E527AE6E5 211 | -- 3) Create new command: 212 | -- In LGS: 213 | -- Run 'Logitech Gaming Software' application 214 | -- Open 'Customise buttons' tab 215 | -- Select profile 216 | -- In the left side you will see the 'Commands' pane (list of bindable actions such as keyboard keys, macros, etc), press the big plus sign to add new command. 217 | -- In the 'Command Editor', select the 'Shortcut' in the left pane 218 | -- Set the 1st text field 'Name' to 'RUN_D_SAVER' 219 | -- Set the 2nd text field 'Enter a shortcut' to 'wluajit.exe D_SAVER.lua' 220 | -- Set the 3rd text field 'Working Directory' to 'C:\LGS extension' 221 | -- Press 'OK' button to close the 'Command Editor' 222 | -- Important note: 223 | -- DO NOT bind this new command to any button, this action must not be used by a human. 224 | -- In GHUB: 225 | -- Run 'G HUB' application 226 | -- Click on the mouse picture to open 'Gear page' 227 | -- Select 'Assignments' icon (plus-inside-square) at the left edge 228 | -- Select 'SYSTEM' tab (it's the last one in the row of tabs: COMMANDS-KEYS-ACTIONS-MACROS-SYSTEM) 229 | -- Click 'ADD APPLICATION' under the 'Launch Application' list, a file selection dialogue window will appear 230 | -- Find the file 'C:\LGS extension\luajit.exe' and click it 231 | -- Click 'ADD ARGUMENTS' and replace 'New argument' with 'D_SAVER.lua' 232 | -- Click 'SAVE' 233 | -- Select 'MACROS' tab (in the row of tabs: COMMANDS-KEYS-ACTIONS-MACROS-SYSTEM) 234 | -- Click 'CREATE NEW MACRO' 235 | -- Set 'RUN_D_SAVER' as macro name 236 | -- Select 'NO REPEAT' type of macro 237 | -- Click 'START NOW' 238 | -- Click 'LAUNCH APPLICATION' 239 | -- Select 'luajit' 240 | -- Click 'SAVE' 241 | -- Select 'SYSTEM' tab 242 | -- Click 'luajit' in the 'Launch Application' list 243 | -- Click 'DELETE' 244 | -- Click 'YES' to confirm 245 | -- Important note: 246 | -- Now you have the 'RUN_D_SAVER' macro on the 'MACROS' tab. 247 | -- NEVER manually assign this macro to any button, this macro must not be invoked by a human. 248 | -- 4) Copy this script into LGS/GHUB Lua script editor. 249 | -- 250 | -- 251 | -- 252 | -- ------------------------------------------------------------------------------------------ 253 | -- How to move the folder 'C:\LGS extension' to another location 254 | -- ------------------------------------------------------------------------------------------ 255 | -- 1) Move all the files from 'C:\LGS extension' to your new folder. 256 | -- 2) Change the path in the assignment 'extension_module_full_path = ...' in this file at line #280. 257 | -- Please note that LGS and GHUB don't allow you to use non-English letters in string literals in your Lua script. 258 | -- All symbols beyond 7-bit ASCII in your folder path must be converted to their Windows ANSI codes. 259 | -- Example: 'D:\Папка\LGS' should be written as 260 | -- either path = "D:\\\207\224\239\234\224\\LGS" 261 | -- or path = [[D:\]]..string.char(207,224,239,234,224)..[[\LGS]] 262 | -- 3) Modify the command 'RUN_D_SAVER': 263 | -- In LGS: 264 | -- Edit the command 'RUN_D_SAVER' and write your new folder path to the 3rd text field 'Working Directory' 265 | -- In GHUB: 266 | -- Select 'MACROS' tab (in the row of tabs: COMMANDS-KEYS-ACTIONS-MACROS-SYSTEM) 267 | -- Click 'RUN_D_SAVER' to enter macro editor 268 | -- Click 'MACRO OPTIONS' in the top right corner 269 | -- Click 'DELETE THIS MACRO' 270 | -- Click 'YES' to confirm 271 | -- Create the command 'RUN_D_SAVER' again: 272 | -- follow the instructions from step 3 'Create new command' in 'How to install' section above, 273 | -- but use your new folder path instead of 'C:\LGS extension' 274 | -- ------------------------------------------------------------------------------------------ 275 | 276 | 277 | 278 | 279 | -- Loading the main module 280 | extension_module_full_path = [[C:\LGS extension\LGS_extension.lua]] 281 | dofile(extension_module_full_path) 282 | 283 | 284 | ---------------------------------------------------------------------- 285 | -- FUNCTIONS AND VARIABLES 286 | ---------------------------------------------------------------------- 287 | -- insert all your functions and variables here 288 | -- 289 | 290 | 291 | 292 | function OnEvent(event, arg, family) 293 | local mouse_button 294 | if event == "MOUSE_BUTTON_PRESSED" or event == "MOUSE_BUTTON_RELEASED" then 295 | mouse_button = Logitech_order[arg] or arg -- convert 'arg' (number) to 'mouse_button' (either a string "L","R","M" or a number 4, 5, 6, 7, 8...) 296 | elseif event == "PROFILE_ACTIVATED" then 297 | ClearLog() 298 | EnablePrimaryMouseButtonEvents(true) 299 | update_internal_state(GetDate()) -- it takes about 1 second because of determining your screen resolution 300 | ---------------------------------------------------------------------- 301 | -- CODE FOR PROFILE ACTIVATION 302 | ---------------------------------------------------------------------- 303 | -- set your favourite mouse sensitivity 304 | SetMouseDPITableIndex(2) 305 | -- turn NumLock ON if it is currently OFF (to make numpad keys 0-9 usable in a game) 306 | if not IsKeyLockOn"NumLock" then 307 | PressAndReleaseKey"NumLock" 308 | end 309 | D = Load_table_D and Load_table_D() or {} -- load persistent table 'D' from disk 310 | 311 | ------ this is the first part of example how to use the persistent table 'D': 312 | D.profile_run_cnt = (D.profile_run_cnt or 0) + 1 313 | D.profile_total_time_in_msec = D.profile_total_time_in_msec or 0 314 | print("Total number of times this profile was started = "..D.profile_run_cnt) 315 | local t = math.floor(D.profile_total_time_in_msec / 1000) 316 | print("Total amount of time spent in this profile (hr:min:sec) = "..string.format("%d:%02d:%02d", math.floor(t / 3600), math.floor(t / 60) % 60, t % 60)) 317 | ------ (end of the first part of example) 318 | 319 | -- insert your code here (initialize variables, display "Hello" on LCD screen, etc.) 320 | -- 321 | end 322 | update_internal_state(event, arg, family) -- this invocation adds entropy to RNG (it's very fast) 323 | ---------------------------------------------------------------------- 324 | -- LOG THIS EVENT 325 | ---------------------------------------------------------------------- 326 | -- print( 327 | -- "event = '"..event.."'", 328 | -- not mouse_button and "arg = "..arg or "mouse_button = "..(type(mouse_button) == "number" and mouse_button or "'"..mouse_button.."'"), 329 | -- "family = '"..family.."'" 330 | -- ) 331 | -- 332 | if event == "PROFILE_DEACTIVATED" then 333 | EnablePrimaryMouseButtonEvents(false) 334 | ---------------------------------------------------------------------- 335 | -- CODE FOR PROFILE DEACTIVATION 336 | ---------------------------------------------------------------------- 337 | -- to avoid LGS/GHUB crash, profile deactivation event must be handled in less than 1 second 338 | -- insert your code here (display "Bye!" on LCD screen, etc.) 339 | -- 340 | 341 | ------ this is the second part of example how to use the persistent table 'D': 342 | D.profile_total_time_in_msec = D.profile_total_time_in_msec + GetRunningTime() 343 | ------ (end of the second part of example) 344 | 345 | if Save_table_D then Save_table_D() end -- save persistent table 'D' to disk 346 | return 347 | end 348 | 349 | ---------------------------------------------------------------------- 350 | -- MOUSE EVENTS PROCESSING 351 | -- (you need it if you have Logitech G-series mouse) 352 | ---------------------------------------------------------------------- 353 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == "L" then -- left mouse button 354 | end 355 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == "L" then -- left mouse button 356 | end 357 | 358 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == "R" then -- right mouse button 359 | end 360 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == "R" then -- right mouse button 361 | end 362 | 363 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == "M" then -- middle mouse button 364 | 365 | -- (this is code example #1, remove it after reading or testing) 366 | -- press-and-hold MMB (middle mouse button) in your text editor to simulate typing a random string 367 | -- press Ctrl+MMB to cyclically change mode: alphanumeric/digit/hexadecimal/disabled 368 | random_string_mode = random_string_mode or "alphanumeric" 369 | if IsModifierPressed"Ctrl" then 370 | local next_mode = {alphanumeric = "digits", digits = "hexadecimal", hexadecimal = "disabled", disabled = "alphanumeric"} 371 | random_string_mode = next_mode[random_string_mode] 372 | elseif not IsModifierPressed"Shift" and not IsModifierPressed"Alt" and random_string_mode ~= "disabled" then 373 | local alpha = "abcdefghijklmnopqrstuvwxyz" 374 | local all_chars = 375 | "0123456789"..( 376 | random_string_mode == "hexadecimal" and alpha:sub(1, 6) 377 | or random_string_mode == "digits" and "" 378 | or alpha..alpha:upper() 379 | ) 380 | repeat 381 | for j = 1, random_string_mode == "hexadecimal" and 2 or 1 do -- in "hexadecimal" mode hex digits are generated in pairs (whole bytes) 382 | local k = random(#all_chars) 383 | local c = all_chars:sub(k, k) 384 | local shift_needed = c:find"%u" 385 | if shift_needed then 386 | PressKey"RShift" 387 | end 388 | PressKey(c) 389 | Sleep() 390 | ReleaseKey(c) 391 | if shift_needed then 392 | ReleaseKey"RShift" 393 | end 394 | Sleep() 395 | end 396 | Sleep(40) 397 | until not IsMouseButtonPressed("M") 398 | end 399 | -- (end of code example #1) 400 | 401 | end 402 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == "M" then -- middle mouse button 403 | end 404 | 405 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == 4 then -- 'backward' (X1) mouse button 406 | end 407 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == 4 then -- 'backward' (X1) mouse button 408 | end 409 | 410 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == 5 then -- 'forward' (X2) mouse button 411 | end 412 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == 5 then -- 'forward' (X2) mouse button 413 | end 414 | 415 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == 6 then 416 | 417 | -- (this is code example #2, remove it after reading or testing) 418 | -- move mouse cursor along a circle 419 | local R = 50 -- the radius 420 | if IsModifierPressed"Shift" then 421 | -- with Shift pressed, the mouse is moving CCW using MoveMouseRelative() 422 | local prev_x, prev_y = R, 0 423 | for j = 1, 90 do 424 | local angle = (2 * math.pi) * (j / 90) 425 | local x = math.floor( R * math.cos(angle) + 0.5) 426 | local y = math.floor(-R * math.sin(angle) + 0.5) 427 | MoveMouseRelative(x - prev_x, y - prev_y) 428 | prev_x, prev_y = x, y 429 | Sleep() 430 | end 431 | else 432 | -- without Shift, the mouse is moving CW using SetMousePositionInPixels() 433 | local x_center, y_center = GetMousePositionInPixels() 434 | x_center = x_center + R 435 | for j = 1, 90 do 436 | local angle = (2 * math.pi) * (j / 90) 437 | SetMousePositionInPixels(x_center - R * math.cos(angle), y_center - R * math.sin(angle)) 438 | Sleep() 439 | end 440 | end 441 | -- actual radii of these two circles (CW and CCW) may appear different due to quirks of MoveMouseRelative() 442 | -- (end of code example #2) 443 | 444 | end 445 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == 6 then 446 | end 447 | 448 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == 7 then 449 | end 450 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == 7 then 451 | end 452 | 453 | if event == "MOUSE_BUTTON_PRESSED" and mouse_button == 8 then 454 | 455 | -- (this is code example #3, remove it after reading or testing) 456 | -- print misc info (in the bottom panel of LGS/GHUB script editor) on mouse button 8 press 457 | print("=============================================================") 458 | print("Current date & time: "..GetDate()) 459 | local t = math.floor(GetRunningTime() / 1000) 460 | print("profile running time (hr:min:sec) = "..string.format("%d:%02d:%02d", math.floor(t / 3600), math.floor(t / 60) % 60, t % 60)) 461 | print("approximately "..GetEntropyCounter().." bits of entropy were collected from button press events") 462 | local i = random(6) -- integer 1 <= i <= 6 463 | print("random dice roll:", i) 464 | local b = random(0, 255) -- integer 0 <= b <= 255 465 | print("random byte:", ("%02X"):format(b)) 466 | local x = random() -- float 0 <= x < 1 467 | print("random float:", x) 468 | local mouse_x, mouse_y, screen_width, screen_height = GetMousePositionInPixels() 469 | print("your screen size is "..screen_width.."x"..screen_height) 470 | print("your mouse cursor is at pixel ("..mouse_x..","..mouse_y..")") 471 | print("=============================================================") 472 | -- (end of code example #3) 473 | 474 | end 475 | if event == "MOUSE_BUTTON_RELEASED" and mouse_button == 8 then 476 | end 477 | 478 | ---------------------------------------------------------------------- 479 | -- KEYBOARD AND LEFT-HANDED-CONTROLLER EVENTS PROCESSING 480 | -- (you need it if you have any Logitech device with keys G1, G2, ...) 481 | ---------------------------------------------------------------------- 482 | if event == "G_PRESSED" and arg == 1 then -- G1 key 483 | end 484 | if event == "G_RELEASED" and arg == 1 then -- G1 key 485 | end 486 | 487 | if event == "G_PRESSED" and arg == 12 then -- G12 key 488 | end 489 | if event == "G_RELEASED" and arg == 12 then -- G12 key 490 | end 491 | 492 | 493 | if event == "M_PRESSED" and arg == 1 then -- M1 key 494 | end 495 | if event == "M_RELEASED" and arg == 1 then -- M1 key 496 | end 497 | 498 | if event == "M_PRESSED" and arg == 2 then -- M2 key 499 | end 500 | if event == "M_RELEASED" and arg == 2 then -- M2 key 501 | end 502 | 503 | if event == "M_PRESSED" and arg == 3 then -- M3 key 504 | end 505 | if event == "M_RELEASED" and arg == 3 then -- M3 key 506 | end 507 | 508 | 509 | ---------------------------------------------------------------------- 510 | -- EXIT EVENT PROCESSING 511 | ---------------------------------------------------------------------- 512 | -- After current event is processed, we have some time before the next event occurs, because a human can't press buttons very frequently 513 | -- So, it's a good time for 'background calculations' 514 | perform_calculations() -- precalculate next 25 strong random numbers (only if needed), it will take about 30 ms on a modern PC 515 | end 516 | -------------------------------------------------------------------------------- /LGS extension/D_SAVER.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------------------- 2 | -- D_SAVER.lua 3 | --------------------------------------------------------------------------------------------- 4 | -- This module was inspired by "LGS Debug Interceptor" project created by Peter from Gondwana Software 5 | -- https://gondwanasoftware.net.au/lgsdi.shtml 6 | 7 | local ffi = require"ffi" 8 | local psapi = ffi.load"psapi" 9 | local ffi_C = ffi.C 10 | local ffi_string = ffi.string 11 | local ipairs = ipairs 12 | local pairs = pairs 13 | local tonumber = tonumber 14 | local tostring = tostring 15 | local type = type 16 | local floor = math.floor 17 | local math_log = math.log 18 | local max = math.max 19 | local min = math.min 20 | local byte = string.byte 21 | local char = string.char 22 | local format = string.format 23 | local gsub = string.gsub 24 | local match = string.match 25 | local rep = string.rep 26 | local sub = string.sub 27 | local lower = string.lower 28 | local table_sort = table.sort 29 | local table_concat = table.concat 30 | 31 | ffi.cdef[[ 32 | uint32_t EnumProcesses(uint32_t *, uint32_t, uint32_t *); 33 | size_t OpenProcess(uint32_t, uint32_t, uint32_t); 34 | uint32_t CloseHandle(size_t); 35 | uint32_t GetModuleBaseNameA(size_t, size_t, void *, uint32_t); 36 | uint32_t DebugActiveProcess(uint32_t); 37 | uint32_t DebugActiveProcessStop(uint32_t); 38 | uint32_t DebugSetProcessKillOnExit(uint32_t); 39 | typedef struct { 40 | uint32_t dwDebugEventCode; 41 | uint32_t dwProcessId; 42 | uint32_t dwThreadId; 43 | union { 44 | uint32_t ExceptionCode; 45 | struct { 46 | size_t hFile; 47 | size_t hProcess; 48 | }; 49 | struct { 50 | void * lpDebugStringData; 51 | uint16_t fUnicode; 52 | uint16_t nDebugStringLength; 53 | }; 54 | }; 55 | } * LPDEBUG_EVENT; 56 | uint32_t WaitForDebugEvent(LPDEBUG_EVENT, uint32_t); 57 | uint32_t ContinueDebugEvent(uint32_t, uint32_t, uint32_t); 58 | uint32_t ReadProcessMemory(size_t, void *, void *, size_t, size_t *); 59 | ]] 60 | 61 | local DWORDS = ffi.typeof"uint32_t[?]" 62 | local block64K = DWORDS(16384) 63 | local DebugEvent = ffi.cast("LPDEBUG_EVENT", block64K) 64 | local block20 = DWORDS(5) 65 | local sizeof_block20 = ffi.sizeof(block20) 66 | 67 | local PID 68 | 69 | if psapi.EnumProcesses(block64K, ffi.sizeof(block64K), block20) ~= 0 then 70 | for j = 0, block20[0]/4 - 1 do 71 | local next_PID = block64K[j] 72 | local ProcessHandle = ffi_C.OpenProcess(0x0410, 0, next_PID) -- PROCESS_VM_READ | PROCESS_QUERY_INFORMATION 73 | if ProcessHandle ~= 0 then 74 | local name_len = psapi.GetModuleBaseNameA(ProcessHandle, 0, block20, sizeof_block20) 75 | ffi_C.CloseHandle(ProcessHandle) 76 | local ProcessName = lower(ffi_string(block20, name_len)) 77 | if ProcessName == "lcore.exe" or ProcessName == "lghub_agent.exe" then -- either LGS or GHUB 78 | PID = next_PID 79 | break 80 | end 81 | end 82 | end 83 | end 84 | 85 | local timeout_msec = 0xFFFFFFFF -- infinite waiting 86 | local arrived_data 87 | local message_no 88 | local prefix = "ESk" 89 | 90 | if PID and ffi_C.DebugActiveProcess(PID) ~= 0 then 91 | ffi_C.DebugSetProcessKillOnExit(0) 92 | arrived_data = {} 93 | local ProcessHandle 94 | repeat 95 | local exit = ffi_C.WaitForDebugEvent(DebugEvent, timeout_msec) == 0 96 | if exit then 97 | arrived_data = nil 98 | else 99 | local ContinueStatus = 0x00010002 -- DBG_CONTINUE 100 | local ThreadId = DebugEvent.dwThreadId 101 | local DebugEventCode = DebugEvent.dwDebugEventCode 102 | if DebugEventCode == 1 then -- EXCEPTION_DEBUG_EVENT 103 | local exception = DebugEvent.ExceptionCode 104 | if exception ~= 0x80000003 then -- EXCEPTION_BREAKPOINT 105 | ContinueStatus = 0x80010001 -- DBG_EXCEPTION_NOT_HANDLED 106 | end 107 | elseif DebugEventCode == 3 then -- CREATE_PROCESS_DEBUG_EVENT 108 | ProcessHandle = DebugEvent.hProcess 109 | ffi_C.CloseHandle(DebugEvent.hFile) 110 | elseif DebugEventCode == 5 then -- EXIT_PROCESS_DEBUG_EVENT 111 | arrived_data = nil 112 | elseif DebugEventCode == 6 then -- LOAD_DLL_DEBUG_EVENT 113 | ffi_C.CloseHandle(DebugEvent.hFile) 114 | elseif DebugEventCode == 8 then -- OUTPUT_DEBUG_STRING_EVENT 115 | local len = DebugEvent.nDebugStringLength 116 | if len ~= 0 and DebugEvent.fUnicode == 0 then 117 | if ffi_C.ReadProcessMemory(ProcessHandle, DebugEvent.lpDebugStringData, block64K, len, nil) == 0 then 118 | arrived_data = nil 119 | else 120 | local message = ffi_string(block64K, len - 1) 121 | if message == "\n" then 122 | exit = true 123 | elseif sub(message, 1, 3) == prefix then 124 | if sub(message, 4, 4) == tostring(message_no or "-") then 125 | message_no = ((message_no or 1) + 1) % 10 126 | arrived_data[#arrived_data + 1] = message 127 | else 128 | arrived_data = nil 129 | end 130 | end 131 | end 132 | end 133 | end 134 | ffi_C.ContinueDebugEvent(PID, ThreadId, ContinueStatus) 135 | end 136 | until exit or not arrived_data 137 | ffi_C.DebugActiveProcessStop(PID) 138 | ffi_C.CloseHandle(ProcessHandle) 139 | end 140 | 141 | -- process arrived data 142 | local queue 143 | if arrived_data then 144 | queue = {} 145 | for k = 1, #arrived_data do 146 | local message = arrived_data[k] -- message contains only bytes 0x20..0x7E, without percent (0x25) 147 | arrived_data[k] = nil 148 | for j = 5, #message - 1 do 149 | local b = byte(message, j) 150 | b = b == 0x7E and 5 or b - 0x20 151 | if b >= 0 and b < 94 then 152 | queue[#queue + 1] = b 153 | else 154 | queue = nil 155 | break 156 | end 157 | end 158 | if not queue then 159 | break 160 | end 161 | end 162 | arrived_data = nil 163 | end 164 | 165 | if queue then 166 | -- Calculating checksum 167 | local chksum = 0 168 | for pos = 1, #queue - 7 do 169 | local b = queue[pos] 170 | local L36 = chksum % 68719476736 -- 2^36 171 | local H17 = (chksum - L36) / 68719476736 172 | chksum = L36 * 126611 + H17 * 505231 + b * 3083 173 | end 174 | local tail7 = 0 175 | for pos = #queue, #queue - 6, -1 do 176 | local b = queue[pos] 177 | tail7 = tail7 * 94 + b 178 | end 179 | if tail7 ~= chksum % 64847759419249 then -- max prime below 94^7 180 | queue = nil 181 | end 182 | end 183 | 184 | if queue then 185 | local pos = 0 186 | local popped_string = {} 187 | local popped_string_special_chars = {nil, 10, 13, 9} 188 | 189 | local function in94() 190 | pos = pos + 1 191 | return queue[pos] 192 | end 193 | 194 | local function PopInt52OrSymbol() 195 | local b = in94() 196 | if b < 3 * 30 then 197 | local value52 = b % 30 198 | local group_no = (b - value52) / 30 199 | if value52 >= 22 then 200 | local L = value52 - 21 201 | value52 = 0 202 | for j = 1, L do 203 | b = in94() 204 | value52 = value52 * 94 + b + 1 205 | end 206 | value52 = value52 + 21 207 | end 208 | return group_no, value52 + 1 209 | else 210 | return nil, b - 3 * 30 211 | end 212 | end 213 | 214 | local function PopInt11() 215 | local b = in94() 216 | if b < 71 then 217 | return b - 17 218 | else 219 | local c = in94() 220 | return (b - 83) * 94 + c 221 | end 222 | end 223 | 224 | local function PopString() 225 | local str_length = 0 226 | repeat 227 | local b = in94() 228 | if b < 2 then 229 | local c = in94() 230 | b = (b * 94 + c + 120) % 256 231 | elseif b > 5 then 232 | b = b + 26 233 | else 234 | b = popped_string_special_chars[b] 235 | end 236 | if b then 237 | str_length = str_length + 1 238 | popped_string[str_length] = b 239 | end 240 | until not b 241 | local aggr_cnt = 0 242 | for j = 1, str_length, 100 do 243 | aggr_cnt = aggr_cnt + 1 244 | popped_string[aggr_cnt] = char(unpack(popped_string, j, min(str_length, j + 99))) 245 | end 246 | return table_concat(popped_string, "", 1, aggr_cnt) 247 | end 248 | 249 | local received_data = {} 250 | local serialized_objects = {} 251 | local list_of_known_values = {0/0, 0, -0, 1/0, -1/0, false, true, received_data} 252 | local stack_of_tables_to_parse = {8} -- contains indexes of non-parsed tables in array_of_known_values 253 | local end_of_list = {} 254 | 255 | local function PopValue() 256 | local group_no, num = PopInt52OrSymbol() 257 | if group_no == 1 then 258 | return list_of_known_values[num] 259 | elseif not group_no and num == 3 then 260 | return end_of_list 261 | else 262 | local value 263 | if group_no then 264 | value = (group_no - 1) * (num * 2 - 1) * 2^PopInt11() 265 | elseif num == 0 then 266 | value = PopString() 267 | else 268 | value = {} 269 | if num == 1 then 270 | serialized_objects[value] = PopString() 271 | else -- num == 2 272 | table.insert(stack_of_tables_to_parse, #list_of_known_values + 1) 273 | end 274 | end 275 | table.insert(list_of_known_values, value) 276 | return value 277 | end 278 | end 279 | 280 | repeat 281 | local tbl = list_of_known_values[table.remove(stack_of_tables_to_parse)] 282 | repeat 283 | local value = PopValue() 284 | local finished = value == end_of_list 285 | if not finished then 286 | table.insert(tbl, value) 287 | end 288 | until finished 289 | repeat 290 | local key = PopValue() 291 | local finished = key == end_of_list 292 | if not finished then 293 | local value = PopValue() 294 | tbl[key] = value 295 | end 296 | until finished 297 | until #stack_of_tables_to_parse == 0 298 | queue = pos + 7 == #queue and {received_data = received_data, serialized_objects = serialized_objects} 299 | end 300 | 301 | if queue then 302 | local D = queue.received_data[1] 303 | local D_filename = queue.received_data[2] 304 | local already_serialized = queue.serialized_objects 305 | 306 | -- serializing table D 307 | 308 | local Lua_keywords = { -- for 5.1 309 | ["and"]=1, ["break"]=1, ["do"]=1, ["else"]=1, ["elseif"]=1, ["end"]=1, ["false"]=1, ["for"]=1, ["function"]=1, ["if"]=1, 310 | ["in"]=1, ["local"]=1, ["nil"]=1, ["not"]=1, ["or"]=1, ["repeat"]=1, ["return"]=1, ["then"]=1, ["true"]=1, ["until"]=1, ["while"]=1 311 | } 312 | 313 | local function boolean_ordering_function(a, b) 314 | return not a and b 315 | end 316 | 317 | local function alphanumeric_ordering_function(a, b) 318 | local pa, pb, lena, lenb = 1, 1, #a, #b 319 | local last_str_a, last_str_b = 1, 1 320 | local pna, pnb 321 | 322 | while pa <= lena and pb <= lenb do 323 | local ca, cb = byte(a, pa), byte(b, pb) 324 | local da = ca >= 48 and ca <= 57 325 | local db = cb >= 48 and cb <= 57 326 | if not da and not db then 327 | if ca ~= cb then 328 | return ca < cb 329 | end 330 | pa, pb = pa + 1, pb + 1 331 | elseif da ~= db then 332 | return db 333 | else 334 | pna, pa = match(a, "0*()%d*()", pa) 335 | pnb, pb = match(b, "0*()%d*()", pb) 336 | local nlamb = (pa - pna) - (pb - pnb) 337 | if nlamb ~= 0 then 338 | return nlamb < 0 339 | end 340 | local na, nb = sub(a, pna, pa - 1), sub(b, pnb, pb - 1) 341 | if na ~= nb then 342 | return na < nb 343 | end 344 | repeat 345 | na = match(a, "^%.?%d", pa) or "" 346 | nb = match(b, "^%.?%d", pb) or "" 347 | if na ~= nb then 348 | return na < nb 349 | end 350 | pa = pa + #na 351 | pb = pb + #nb 352 | until na == "" 353 | last_str_a, last_str_b = pa, pb 354 | end 355 | end 356 | 357 | local lamb = (lena - pa) - (lenb - pb) 358 | return lamb < 0 or lamb == 0 and a < b 359 | end 360 | 361 | 362 | local ser_simple_type 363 | do 364 | 365 | local function get_shortest_string(str1, str2) 366 | return str1 and (str2 and #str2 < #str1 and str2 or str1) or str2 367 | end 368 | 369 | local function fraction_to_string(N, D, k) 370 | if (D ~= 1 or k ~= 0) and k >= -1074 and k <= 1023 and N < 2^20 and D < 2^20 then 371 | local div, denom = N ~= D and k < 0 and k >= -1023, format("%.f", D) 372 | return 373 | N == 1 and D ~= 1 and k ~= 0 and "2^"..k.." / "..denom 374 | or 375 | (N == D and "" or format("%.f", N)..(D == 1 and "" or "/"..denom)..(k == 0 and "" or div and " / " or " * ")) 376 | ..(k == 0 and "" or "2^"..(div and -k or k)) 377 | end 378 | end 379 | 380 | local function serialize_number(float_number) 381 | if float_number ~= float_number then 382 | return "0/0" 383 | end 384 | if float_number == 0 then 385 | return 1/float_number < 0 and "-0" or "0" 386 | end 387 | local shortest 388 | local sign, positive_float = "", float_number 389 | if positive_float < 0 then 390 | sign, positive_float = "-", -positive_float 391 | end 392 | if positive_float == 1/0 then 393 | shortest = "1/0" 394 | else 395 | local integer = positive_float < 2^53 and floor(positive_float) == positive_float and format("%.f", positive_float) 396 | shortest = integer 397 | local mant_int, mant_frac, exponent = match(format("%.17e", positive_float), "^(%d)%D+(%d+)e([-+]%d+)$") 398 | local mantissa, trailing_zeroes = match(mant_int..mant_frac, "^([^0].-)(0*)$") 399 | exponent = tonumber(exponent) + #trailing_zeroes - #mant_frac 400 | --assert(tonumber(mantissa.."e"..exponent) == positive_float) 401 | repeat 402 | local truncated_mantissa, incr = match(mantissa, "^(.*)(.)$") 403 | incr = tonumber(incr) > 4 404 | for _ = 1, 2 do 405 | local new_exponent, new_mantissa = exponent + 1 406 | if incr then 407 | local head, digit, tail = match("0"..truncated_mantissa, "^(.-)(.)(9*)$") 408 | new_mantissa, incr = match(head, "^0?(.*)$")..char(byte(digit) + 1) 409 | new_exponent = new_exponent + #tail 410 | else 411 | new_mantissa, incr = match(truncated_mantissa, "^(.-)(0*)$") 412 | new_exponent = new_exponent + #incr 413 | end 414 | if tonumber(new_mantissa.."e"..new_exponent) == positive_float then 415 | mantissa, exponent, truncated_mantissa = new_mantissa, new_exponent 416 | break 417 | end 418 | end 419 | until truncated_mantissa 420 | local good_fixed_point, scientific 421 | local mm9 = #mantissa - 9 422 | for shift = min(-6, mm9), max(mm9 + 15, 9) do 423 | local e = exponent + shift 424 | local exp = e ~= 0 and format("e%+04d", e) or "" 425 | local str = 426 | shift < 1 and mantissa..rep("0", -shift)..(e == 0 and "" or exp) 427 | or shift < #mantissa and sub(mantissa, 1, -shift-1).."."..sub(mantissa, -shift)..exp 428 | or "0."..rep("0", shift - #mantissa)..mantissa..exp 429 | scientific = get_shortest_string(scientific, shift == mm9 + 8 and str) 430 | shortest = shortest or e == 0 and shift > 0 and str 431 | good_fixed_point = good_fixed_point or e == 0 and shift >= mm9 and shift <= 9 and mm9 <= 0 and str 432 | end 433 | if good_fixed_point then 434 | shortest = get_shortest_string(integer, good_fixed_point) 435 | else 436 | shortest = get_shortest_string(shortest, scientific) 437 | local k = floor(math_log(positive_float, 2) + 0.5) 438 | local e = 2^k 439 | if positive_float < e then 440 | k = k - 1 441 | e = 2^k 442 | end 443 | local x, pn, n, pd, d, N, D = positive_float / e - 1, 0, 1, 1, 0 444 | repeat 445 | local Q, q = x + 0.5, x - x % 1 446 | Q = Q - Q % 1 447 | pd, d, D = d, q*d + pd, Q*d + pd 448 | pn, n, N = n, q*n + pn, Q*n + pn + D 449 | if N >= 2^20 then 450 | break 451 | elseif N/D * e == positive_float then 452 | while k > 0 and D % 2 == 0 do 453 | k, D = k - 1, D / 2 454 | end 455 | while k < 0 and N % 2 == 0 do 456 | k, N = k + 1, N / 2 457 | end 458 | local frac = fraction_to_string(k > 0 and N * 2^k or N, k < 0 and D * 2^-k or D, 0) 459 | shortest = get_shortest_string(shortest, frac) 460 | if not frac then 461 | local dk = k > 0 and 1 or -1 462 | local fN = (3 + dk) / 2 463 | local fD, shortest_fraction = 2 / fN 464 | k, N, D = k - dk, N * fN, D * fD 465 | while N % fN + D % fD == 0 do 466 | k, N, D = k + dk, N / fN, D / fD 467 | shortest_fraction = fraction_to_string(N, D, k) or shortest_fraction 468 | end 469 | shortest = get_shortest_string(shortest, shortest_fraction) 470 | end 471 | break 472 | end 473 | x = 1 / (x - q) 474 | until x >= 2^20 or x ~= x 475 | end 476 | end 477 | shortest = sign..shortest 478 | --assert(assert(loadstring("return "..shortest))() == float_number) 479 | return shortest 480 | end 481 | 482 | local escapings = { 483 | ["\\"] = "\\\\", 484 | ["\a"] = "\\a", 485 | ["\b"] = "\\b", 486 | ["\f"] = "\\f", 487 | ["\n"] = "\\n", 488 | ["\r"] = "\\r", 489 | ["\t"] = "\\t", 490 | ["\v"] = "\\v", 491 | ["'"] = "\\'", 492 | ['"'] = '\\"' 493 | } 494 | 495 | local function quote_string(str, quote) -- " or ' 496 | return 497 | quote 498 | ..gsub( 499 | gsub( 500 | gsub( 501 | str, 502 | "[%c\\"..quote.."]", 503 | function(c) 504 | return escapings[c] or format("\a%03d", byte(c)) 505 | end 506 | ), 507 | "\a(%d%d%d%d)", 508 | "\\%1" 509 | ), 510 | "\a0?0?", 511 | "\\" 512 | ) 513 | ..quote 514 | end 515 | 516 | local function serialize_string_value(str) 517 | local single = quote_string(str, "'") 518 | local double = quote_string(str, '"') 519 | return #single < #double and single or double 520 | end 521 | 522 | function ser_simple_type(val) 523 | local tp = type(val) 524 | if tp == "number" then 525 | return serialize_number(val) 526 | elseif tp == "string" then 527 | return serialize_string_value(val) 528 | elseif tp == "boolean" or tp == "nil" then 529 | return tostring(val) 530 | end 531 | end 532 | 533 | end 534 | 535 | local add_to_heap, extract_from_heap 536 | do 537 | local heap_size, heap, indices = 0, {}, {} 538 | 539 | local function comparison_func(a, b) 540 | local a_pq, b_pq = a.non_ready_pairs_qty, b.non_ready_pairs_qty 541 | return a_pq < b_pq or a_pq == b_pq and a.times_used > b.times_used 542 | end 543 | 544 | function add_to_heap(elem) 545 | local elem_pos = indices[elem] 546 | if not elem_pos then 547 | heap_size = heap_size + 1 548 | elem_pos = heap_size 549 | end 550 | while elem_pos > 1 do 551 | local parent_pos = (elem_pos - elem_pos % 2) / 2 552 | local parent = heap[parent_pos] 553 | if comparison_func(elem, parent) then 554 | heap[elem_pos] = parent 555 | indices[parent] = elem_pos 556 | elem_pos = parent_pos 557 | else 558 | break 559 | end 560 | end 561 | heap[elem_pos] = elem 562 | indices[elem] = elem_pos 563 | end 564 | 565 | function extract_from_heap() 566 | if heap_size > 0 then 567 | local root_elem = heap[1] 568 | local parent = heap[heap_size] 569 | heap[heap_size] = nil 570 | indices[root_elem] = nil 571 | heap_size = heap_size - 1 572 | if heap_size > 0 then 573 | local pos = 1 574 | local last_node_pos = heap_size / 2 575 | while pos <= last_node_pos do 576 | local child_pos = pos + pos 577 | local child = heap[child_pos] 578 | if child_pos < heap_size then 579 | local child_pos2 = child_pos + 1 580 | local child2 = heap[child_pos2] 581 | if comparison_func(child2, child) then 582 | child_pos = child_pos2 583 | child = child2 584 | end 585 | end 586 | if comparison_func(child, parent) then 587 | heap[pos] = child 588 | indices[child] = pos 589 | pos = child_pos 590 | else 591 | break 592 | end 593 | end 594 | heap[pos] = parent 595 | indices[parent] = pos 596 | end 597 | return root_elem 598 | end 599 | end 600 | end 601 | 602 | local user_tables = {} 603 | local index_of_user_table = {} 604 | 605 | do 606 | local table_to_process = {D} 607 | local table_index_to_process = 0 608 | 609 | repeat 610 | local real_table 611 | if table_index_to_process ~= 0 then 612 | real_table = user_tables[table_index_to_process] 613 | table_to_process = real_table.the_table 614 | end 615 | local non_ready_pairs_qty = 0 616 | for k, v in pairs(table_to_process) do 617 | local this_pair_depends_on_a_table = 0 618 | local the_table = k 619 | for kv = 1, 2 do 620 | if type(the_table) == "table" and not already_serialized[the_table] then 621 | this_pair_depends_on_a_table = 1 622 | local user_table_index = index_of_user_table[the_table] 623 | local user_table 624 | if user_table_index then 625 | user_table = user_tables[user_table_index] 626 | user_table.times_used = user_table.times_used + 1 627 | else 628 | user_table = {the_table = the_table, used_by = {}, times_used = 1} 629 | user_tables[#user_tables + 1] = user_table 630 | user_table_index = #user_tables 631 | index_of_user_table[the_table] = user_table_index 632 | end 633 | if table_index_to_process ~= 0 then 634 | local used_by = user_table.used_by 635 | local used_by_idx = used_by[table_index_to_process] 636 | if not used_by_idx then 637 | used_by_idx = {} 638 | used_by[table_index_to_process] = used_by_idx 639 | end 640 | if kv == 2 and k ~= v then 641 | used_by_idx[#used_by_idx + 1] = k 642 | end 643 | end 644 | end 645 | the_table = v 646 | end 647 | non_ready_pairs_qty = non_ready_pairs_qty + this_pair_depends_on_a_table 648 | end 649 | if real_table then 650 | real_table.non_ready_pairs_qty = non_ready_pairs_qty 651 | add_to_heap(real_table) 652 | end 653 | table_index_to_process = table_index_to_process + 1 654 | until table_index_to_process > #user_tables 655 | end 656 | 657 | local instructions = {} 658 | local return_indent_level = "" 659 | do 660 | local function value_ready(x) 661 | local idx = index_of_user_table[x] 662 | return not idx or user_tables[idx].definition 663 | end 664 | 665 | local function ser_existing_value(val, ref, referred_vars) 666 | local ser_by_user = already_serialized[val] 667 | if ser_by_user then 668 | return ser_by_user 669 | end 670 | local idx = index_of_user_table[val] 671 | if idx then 672 | local def = user_tables[idx].definition 673 | local instr = instructions[def] 674 | local inner_level = instr.inlined_level 675 | if inner_level < 7 then 676 | instr.type = "inlined" 677 | --assert(not instr.last_ref) 678 | for referred_var in pairs(instr.referred_vars) do 679 | instructions[referred_var].last_ref = ref 680 | if referred_vars then 681 | referred_vars[referred_var] = true 682 | end 683 | end 684 | local outer_instr = instructions[ref] 685 | if outer_instr then 686 | local outer_level = outer_instr.inlined_level 687 | if outer_level then 688 | outer_instr.inlined_level = max(outer_level, 1 + inner_level) 689 | end 690 | end 691 | else 692 | if referred_vars then 693 | referred_vars[def] = true 694 | end 695 | instr.last_ref = ref 696 | end 697 | return def 698 | end 699 | return ser_simple_type(val) 700 | end 701 | 702 | repeat 703 | local table_to_define = extract_from_heap() 704 | if table_to_define then 705 | local the_table = table_to_define.the_table 706 | local inlinable = table_to_define.times_used == 1 and table_to_define.non_ready_pairs_qty == 0 707 | local referred_vars = inlinable and {} or nil 708 | local new_instruction = {type = "definition", inlined_level = inlinable and 1 or 1/0, referred_vars = referred_vars} 709 | local instr_index = #instructions + 1 710 | instructions[instr_index] = new_instruction 711 | local keys_of_type_number = {} 712 | local keys_of_type_string = {} 713 | local keys_of_type_boolean = {} 714 | local keys_of_other_types = {} 715 | local keys_by_type = {number = keys_of_type_number, string = keys_of_type_string, boolean = keys_of_type_boolean} 716 | for k, v in pairs(the_table) do 717 | if value_ready(v) and value_ready(k) then 718 | local t = keys_by_type[type(k)] or keys_of_other_types 719 | t[#t + 1] = k 720 | end 721 | end 722 | table_sort(keys_of_type_number) 723 | table_sort(keys_of_type_string, alphanumeric_ordering_function) 724 | table_sort(keys_of_type_boolean, boolean_ordering_function) 725 | local j = 1 726 | for _, key_array in ipairs{keys_of_type_number, keys_of_type_string, keys_of_type_boolean, keys_of_other_types} do 727 | for _, k in ipairs(key_array) do 728 | new_instruction[j] = ser_existing_value(k, instr_index, referred_vars) 729 | new_instruction[j + 1] = ser_existing_value(the_table[k], instr_index, referred_vars) 730 | j = j + 2 731 | end 732 | end 733 | table_to_define.definition = instr_index 734 | for i, ti_key_list in pairs(table_to_define.used_by) do 735 | local ti = user_tables[i] 736 | local ti_table = ti.the_table 737 | local ti_def = ti.definition 738 | local new_ready_pairs = 0 739 | for j = 0, #ti_key_list do 740 | local x, k, v 741 | if j == 0 then 742 | k = the_table 743 | v = ti_table[k] 744 | --assert(#ti_key_list ~= 0 or v ~= nil) 745 | x = v 746 | else 747 | v = the_table 748 | k = ti_key_list[j] 749 | --assert(ti_table[k] == v) 750 | x = k 751 | end 752 | if x ~= nil and value_ready(x) then 753 | if ti_def then 754 | local ref = #instructions + 1 755 | instructions[ref] = {type = "assignment", table = ti_def, key = ser_existing_value(k, ref), value = ser_existing_value(v, ref)} 756 | instructions[ti_def].last_ref = ref 757 | else 758 | new_ready_pairs = new_ready_pairs + 1 759 | end 760 | end 761 | end 762 | if new_ready_pairs ~= 0 then 763 | ti.non_ready_pairs_qty = ti.non_ready_pairs_qty - new_ready_pairs 764 | add_to_heap(ti) 765 | end 766 | end 767 | table_to_define.used_by = nil 768 | end 769 | until not table_to_define 770 | local instr_index = #instructions + 1 771 | instructions[instr_index] = {ser_existing_value(D, instr_index), type = "return"} 772 | end 773 | 774 | local get_free_variable_name, release_variable_name 775 | do 776 | local variable_names = {} 777 | local total_variables_qty = 0 778 | 779 | function get_free_variable_name() 780 | for len = 1, #variable_names do 781 | local names = variable_names[len] 782 | local qty = #names 783 | if qty ~= 0 then 784 | local name = names[qty] 785 | names[qty] = nil 786 | return name, name.." = ", "\n" 787 | end 788 | end 789 | total_variables_qty = total_variables_qty + 1 790 | if total_variables_qty <= 18 then 791 | local name = char(96 + total_variables_qty) 792 | return name, "local "..name.." = ", "\n" 793 | elseif total_variables_qty <= 193 then 794 | local n = total_variables_qty - 19 795 | local m = n % 25 796 | local name = char((n - m) / 25 + 115, m + 97) 797 | return name, "local "..name.." = ", "\n" 798 | elseif total_variables_qty == 194 then 799 | return "z[1]", "local z = {", "}\n" 800 | else 801 | local name = "z["..tostring(total_variables_qty - 193).."]" 802 | return name, name.." = ", "\n" 803 | end 804 | end 805 | 806 | function release_variable_name(name) 807 | local len = #name 808 | local t = variable_names[len] 809 | if not t then 810 | t = {} 811 | variable_names[len] = t 812 | for j = len - 1, 1, -1 do 813 | if variable_names[j] then 814 | break 815 | end 816 | variable_names[j] = {} 817 | end 818 | end 819 | t[#t + 1] = name 820 | end 821 | 822 | end 823 | 824 | local text = {} 825 | 826 | local function write_text(some_text) 827 | --assert(some_text) 828 | text[#text + 1] = some_text 829 | end 830 | 831 | local function extract_identifier(expr) 832 | if type(expr) == "string" then 833 | local q, identifier = match(expr, "^(['\"])(.*)%1$") 834 | return identifier and not Lua_keywords[identifier] and match(identifier, "^[A-Za-z_][A-Za-z0-9_]*$") 835 | end 836 | end 837 | 838 | local write_table_constructor 839 | 840 | local function write_value(value, indent_level) 841 | --assert(type(indent_level) == "string") 842 | if type(value) == "string" then 843 | return write_text(value) 844 | else 845 | local instr = instructions[value] 846 | if instr.type == "inlined" then 847 | return write_table_constructor(value, indent_level) 848 | else 849 | return write_text(instr.variable_name) 850 | end 851 | end 852 | end 853 | 854 | function write_table_constructor(instr_index, indent_level) 855 | --assert(type(indent_level) == "string") 856 | write_text"{" 857 | local instr = instructions[instr_index] 858 | local instr_len = #instr 859 | if instr_len ~= 0 then 860 | local final_indent = indent_level 861 | indent_level = indent_level.."\t" 862 | write_text"\n" 863 | write_text(indent_level) 864 | local separator = ",\n"..indent_level 865 | for j = 1, instr_len, 2 do 866 | if j ~= 1 then 867 | write_text(separator) 868 | end 869 | local key = instr[j] 870 | local key_as_identifier = extract_identifier(key) 871 | if key_as_identifier then 872 | write_text(key_as_identifier) 873 | write_text" = " 874 | else 875 | write_text"[" 876 | write_value(key, indent_level) 877 | write_text"] = " 878 | end 879 | write_value(instr[j + 1], indent_level) 880 | end 881 | write_text"\n" 882 | write_text(final_indent) 883 | end 884 | write_text"}" 885 | end 886 | 887 | for current_instr_index, current_instr in ipairs(instructions) do 888 | local current_instr_type = current_instr.type 889 | local terminating_vars = current_instr.terminating_vars 890 | if terminating_vars then 891 | --assert(current_instr_type ~= "inlined") 892 | for _, var in ipairs(terminating_vars) do 893 | release_variable_name(instructions[var].variable_name) 894 | end 895 | end 896 | if current_instr_type == "definition" then 897 | do 898 | local term_instr = instructions[current_instr.last_ref] 899 | local term_vars = term_instr.terminating_vars 900 | if not term_vars then 901 | term_vars = {} 902 | term_instr.terminating_vars = term_vars 903 | end 904 | term_vars[#term_vars + 1] = current_instr_index 905 | end 906 | local variable_name, assignment_prefix, assignment_suffix = get_free_variable_name() 907 | current_instr.variable_name = variable_name 908 | write_text(assignment_prefix) 909 | write_table_constructor(current_instr_index, "") 910 | write_text(assignment_suffix) 911 | elseif current_instr_type == "assignment" then 912 | write_text(instructions[current_instr.table].variable_name) 913 | local key = current_instr.key 914 | local key_as_identifier = extract_identifier(key) 915 | if key_as_identifier then 916 | write_text"." 917 | write_text(key_as_identifier) 918 | write_text" = " 919 | else 920 | write_text"[" 921 | write_value(key, "") 922 | write_text"] = " 923 | end 924 | write_value(current_instr.value, "") 925 | write_text"\n" 926 | elseif current_instr_type == "return" then 927 | write_text"return " 928 | local separator = ",\n\t" 929 | for j, value in ipairs(current_instr) do 930 | if j ~= 1 then 931 | write_text(separator) 932 | end 933 | write_value(value, return_indent_level) 934 | end 935 | write_text"\n" 936 | end 937 | end 938 | 939 | text = gsub(table_concat(text), "\n", "\r\n") 940 | 941 | -- create D-script file in the current directory 942 | local file = io.open(D_filename, "wb") 943 | file:write(text) 944 | file:close() 945 | 946 | end 947 | -------------------------------------------------------------------------------- /LGS extension/LGS_extension.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------------------- 2 | -- LGS_extension.lua 3 | --------------------------------------------------------------------------------------------- 4 | -- This module is invoked by 'LGS_script_template.lua' 5 | -- The full path to this module must be assigned to variable 'extension_module_full_path', see 'LGS_script_template.lua' line #280 6 | 7 | local 8 | select, tostring, type, floor, min, max, sqrt, format, byte, char, rep, sub, gsub, concat = 9 | select, tostring, type, math.floor, math.min, math.max, math.sqrt, string.format, string.byte, string.char, string.rep, string.sub, string.gsub, table.concat 10 | local 11 | MoveMouseRelative, MoveMouseTo, GetMousePosition, Sleep_orig, GetRunningTime, OutputLogMessage, OutputDebugMessage = 12 | MoveMouseRelative, MoveMouseTo, GetMousePosition, Sleep, GetRunningTime, OutputLogMessage, OutputDebugMessage 13 | 14 | function print(...) 15 | local t = {...} 16 | for j = 1, select("#", ...) do 17 | t[j] = tostring(t[j]) 18 | end 19 | OutputLogMessage("%s\n", concat(t, "\t")) 20 | end 21 | 22 | local GetMousePositionInPixels 23 | do 24 | local xy_data, xy_64K, xy_pixels, enabled = {{}, {}}, {}, {}, true 25 | 26 | function GetMousePositionInPixels() 27 | -- The function returns mouse_x_pixels, mouse_y_pixels, screen_width, screen_height, x_64K, y_64K 28 | -- 0 <= mouse_x_pixels < screen_width 29 | -- 0 <= mouse_y_pixels < screen_height 30 | -- it's assumed that both width and height of your screen are between 150 and 10240 pixels 31 | xy_64K[1], xy_64K[2] = GetMousePosition() 32 | if enabled then 33 | local jump 34 | local attempts_qty = 3 35 | for attempt = 1, attempts_qty + 1 do 36 | for i = 1, 2 do 37 | local result 38 | local size = xy_data[i][4] 39 | if size then 40 | local coord_64K = xy_64K[i] 41 | -- How to convert between pos_64K (0...65535) and pixel (0...(size-1)) 42 | -- pos_64K = floor(pixel * 65535 / (size-1) + 0.5) 43 | -- pixel = floor((pos_64K + (0.5 + 2^-16)) * (size-1) / 65535) 44 | local pixels = floor((coord_64K + (0.5 + 2^-16)) * (size - 1) / 65535) 45 | if 65535 * pixels >= (coord_64K - (0.5 + 2^-16)) * (size - 1) then 46 | result = pixels 47 | end 48 | end 49 | xy_pixels[i] = result 50 | end 51 | if xy_pixels[1] and xy_pixels[2] then 52 | return xy_pixels[1], xy_pixels[2], xy_data[1][4], xy_data[2][4], xy_64K[1], xy_64K[2] 53 | elseif attempt <= attempts_qty then 54 | if jump then 55 | MoveMouseTo(3*2^14 - xy_64K[1]/2, 3*2^14 - xy_64K[2]/2) 56 | Sleep_orig(10) 57 | xy_64K[1], xy_64K[2] = GetMousePosition() 58 | end 59 | jump = true 60 | for _, data in ipairs(xy_data) do 61 | data[1] = {[0] = true} 62 | data[2] = 0 63 | data[3] = 45 * 225 64 | data[4] = nil 65 | data[5] = 6 66 | for j = 6, 229 do 67 | data[j] = (2^45 - 1) * 256 + 1 + j 68 | end 69 | data[230] = (2^45 - 1) * 256 70 | end 71 | local dx = xy_64K[1] < 2^15 and 1 or -1 72 | local dy = xy_64K[2] < 2^15 and 1 or -1 73 | local prev_coords_processed_1, prev_coords_processed_2, prev_variants_qty, trust 74 | for frame = 1, 90 * attempt do 75 | for i = 1, 2 do 76 | local data, coord_64K = xy_data[i], xy_64K[i] 77 | local data_1 = data[1] 78 | if not data_1[coord_64K] then 79 | data_1[coord_64K] = true 80 | data[2] = data[2] + 1 81 | local min_size 82 | local prev_idx = 5 83 | local idx = data[prev_idx] 84 | while idx > 0 do 85 | local N = data[idx] 86 | local mask = 2^53 87 | local size_from = idx * 45 + (150 - 6 * 45) 88 | for size = size_from, size_from + 44 do 89 | mask = mask / 2 90 | if N >= mask then 91 | N = N - mask 92 | if 65535 * floor((coord_64K + (0.5 + 2^-16)) * (size - 1) / 65535) < (coord_64K - (0.5 + 2^-16)) * (size - 1) then 93 | data[idx] = data[idx] - mask 94 | data[3] = data[3] - 1 95 | else 96 | min_size = min_size or size 97 | end 98 | end 99 | end 100 | if data[idx] < mask then 101 | data[prev_idx] = data[prev_idx] + (N - idx) 102 | else 103 | prev_idx = idx 104 | end 105 | idx = N 106 | end 107 | data[4] = min_size 108 | end 109 | end 110 | local variants_qty = xy_data[1][3] + xy_data[2][3] 111 | local coords_processed_1 = xy_data[1][2] 112 | local coords_processed_2 = xy_data[2][2] 113 | if variants_qty ~= prev_variants_qty then 114 | prev_variants_qty = variants_qty 115 | prev_coords_processed_1 = coords_processed_1 116 | prev_coords_processed_2 = coords_processed_2 117 | end 118 | if min(coords_processed_1 - prev_coords_processed_1, coords_processed_2 - prev_coords_processed_2) >= 20 then 119 | trust = true 120 | break 121 | end 122 | local num = sqrt(frame + 0.1) % 1 < 0.5 and 2^13 or 0 123 | MoveMouseRelative( 124 | dx * max(1, floor(num / ((xy_64K[1] - 2^15) * dx + (2^15 + 2^13/8)))), 125 | dy * max(1, floor(num / ((xy_64K[2] - 2^15) * dy + (2^15 + 2^13/8)))) 126 | ) 127 | Sleep_orig(10) 128 | xy_64K[1], xy_64K[2] = GetMousePosition() 129 | end 130 | if not trust then 131 | xy_data[1][4], xy_data[2][4] = nil 132 | end 133 | end 134 | end 135 | enabled = false 136 | print'Function "GetMousePositionInPixels()" failed to determine screen resolution and has been disabled' 137 | end 138 | return 0, 0, 0, 0, xy_64K[1], xy_64K[2] -- functionality is disabled, so no pixel-related information is returned 139 | end 140 | 141 | end 142 | _G.GetMousePositionInPixels = GetMousePositionInPixels 143 | 144 | function SetMousePositionInPixels(x, y) 145 | local _, _, width, height = GetMousePositionInPixels() 146 | if width > 0 then 147 | MoveMouseTo( 148 | floor(max(0, min(width - 1, x)) * (2^16-1) / (width - 1) + 0.5), 149 | floor(max(0, min(height - 1, y)) * (2^16-1) / (height - 1) + 0.5) 150 | ) 151 | end 152 | end 153 | 154 | local update_internal_state, perform_calculations 155 | do 156 | 157 | local function create_array_of_lanes() 158 | local arr = {} 159 | for j = 1, 50 do 160 | arr[j] = 0 161 | end 162 | return arr 163 | end 164 | 165 | local keccak_feed, XOR53 166 | do 167 | local RC_lo, RC_hi, AND, XOR = {}, {} 168 | do 169 | local AND_of_two_bytes, m, sh_reg = {[0] = 0}, 0, 29 170 | for y = 0, 127 * 256, 256 do 171 | for x = y, y + 127 do 172 | x = AND_of_two_bytes[x] * 2 173 | AND_of_two_bytes[m] = x 174 | AND_of_two_bytes[m + 1] = x 175 | AND_of_two_bytes[m + 256] = x 176 | AND_of_two_bytes[m + 257] = x + 1 177 | m = m + 2 178 | end 179 | m = m + 256 180 | end 181 | 182 | function AND(x, y, xor) 183 | local x0 = x % 2^32 184 | local y0 = y % 2^32 185 | local rx = x0 % 256 186 | local ry = y0 % 256 187 | local res = AND_of_two_bytes[rx + ry * 256] 188 | x = x0 - rx 189 | y = (y0 - ry) / 256 190 | rx = x % 65536 191 | ry = y % 256 192 | res = res + AND_of_two_bytes[rx + ry] * 256 193 | x = (x - rx) / 256 194 | y = (y - ry) / 256 195 | rx = x % 65536 + y % 256 196 | res = res + AND_of_two_bytes[rx] * 65536 197 | res = res + AND_of_two_bytes[(x + y - rx) / 256] * 16777216 198 | if xor then 199 | return x0 + y0 - 2 * res 200 | else 201 | return res 202 | end 203 | end 204 | 205 | function XOR(x, y, z, t, u) 206 | if z then 207 | if t then 208 | if u then 209 | t = AND(t, u, true) 210 | end 211 | z = AND(z, t, true) 212 | end 213 | y = AND(y, z, true) 214 | end 215 | return AND(x, y, true) 216 | end 217 | 218 | local function split53(x) 219 | local lo = x % 2^32 220 | return lo, (x - lo) / 2^32 221 | end 222 | 223 | function XOR53(x, y) 224 | local x_lo, x_hi = split53(x) 225 | local y_lo, y_hi = split53(y) 226 | return XOR(x_hi, y_hi) * 2^32 + XOR(x_lo, y_lo) 227 | end 228 | 229 | local function next_bit() 230 | local r = sh_reg % 2 231 | sh_reg = XOR((sh_reg - r) / 2, 142 * r) 232 | return r * m 233 | end 234 | 235 | for idx = 1, 24 do 236 | local lo = 0 237 | for j = 0, 5 do 238 | m = 2^(2^j - 1) 239 | lo = lo + next_bit() 240 | end 241 | RC_lo[idx], RC_hi[idx] = lo, next_bit() 242 | end 243 | end 244 | 245 | function keccak_feed(lanes, str, offs, size, block_size_in_bytes) 246 | for pos = offs, offs + size - 1, block_size_in_bytes do 247 | for j = 1, block_size_in_bytes / 4 do 248 | pos = pos + 4 249 | local a, b, c, d = byte(str, pos - 3, pos) 250 | lanes[j] = XOR(lanes[j], ((d * 256 + c) * 256 + b) * 256 + a) 251 | end 252 | local 253 | L01_lo, L01_hi, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi, 254 | L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi, 255 | L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi = 256 | lanes[01], lanes[02], lanes[03], lanes[04], lanes[05], lanes[06], lanes[07], lanes[08], lanes[09], lanes[10], lanes[11], 257 | lanes[12], lanes[13], lanes[14], lanes[15], lanes[16], lanes[17], lanes[18], lanes[19], lanes[20], lanes[21], lanes[22], lanes[23], lanes[24], 258 | lanes[25], lanes[26], lanes[27], lanes[28], lanes[29], lanes[30], lanes[31], lanes[32], lanes[33], lanes[34], lanes[35], lanes[36], lanes[37], 259 | lanes[38], lanes[39], lanes[40], lanes[41], lanes[42], lanes[43], lanes[44], lanes[45], lanes[46], lanes[47], lanes[48], lanes[49], lanes[50] 260 | for round_idx = 1, 24 do 261 | local C1_lo = XOR(L01_lo, L06_lo, L11_lo, L16_lo, L21_lo) 262 | local C1_hi = XOR(L01_hi, L06_hi, L11_hi, L16_hi, L21_hi) 263 | local C2_lo = XOR(L02_lo, L07_lo, L12_lo, L17_lo, L22_lo) 264 | local C2_hi = XOR(L02_hi, L07_hi, L12_hi, L17_hi, L22_hi) 265 | local C3_lo = XOR(L03_lo, L08_lo, L13_lo, L18_lo, L23_lo) 266 | local C3_hi = XOR(L03_hi, L08_hi, L13_hi, L18_hi, L23_hi) 267 | local C4_lo = XOR(L04_lo, L09_lo, L14_lo, L19_lo, L24_lo) 268 | local C4_hi = XOR(L04_hi, L09_hi, L14_hi, L19_hi, L24_hi) 269 | local C5_lo = XOR(L05_lo, L10_lo, L15_lo, L20_lo, L25_lo) 270 | local C5_hi = XOR(L05_hi, L10_hi, L15_hi, L20_hi, L25_hi) 271 | local D_lo = XOR(C1_lo, C3_lo * 2 + (C3_hi - C3_hi % 2^31) / 2^31) 272 | local D_hi = XOR(C1_hi, C3_hi * 2 + (C3_lo - C3_lo % 2^31) / 2^31) 273 | local T0_lo = XOR(D_lo, L02_lo) 274 | local T0_hi = XOR(D_hi, L02_hi) 275 | local T1_lo = XOR(D_lo, L07_lo) 276 | local T1_hi = XOR(D_hi, L07_hi) 277 | local T2_lo = XOR(D_lo, L12_lo) 278 | local T2_hi = XOR(D_hi, L12_hi) 279 | local T3_lo = XOR(D_lo, L17_lo) 280 | local T3_hi = XOR(D_hi, L17_hi) 281 | local T4_lo = XOR(D_lo, L22_lo) 282 | local T4_hi = XOR(D_hi, L22_hi) 283 | L02_lo = (T1_lo - T1_lo % 2^20) / 2^20 + T1_hi * 2^12 284 | L02_hi = (T1_hi - T1_hi % 2^20) / 2^20 + T1_lo * 2^12 285 | L07_lo = (T3_lo - T3_lo % 2^19) / 2^19 + T3_hi * 2^13 286 | L07_hi = (T3_hi - T3_hi % 2^19) / 2^19 + T3_lo * 2^13 287 | L12_lo = T0_lo * 2 + (T0_hi - T0_hi % 2^31) / 2^31 288 | L12_hi = T0_hi * 2 + (T0_lo - T0_lo % 2^31) / 2^31 289 | L17_lo = T2_lo * 2^10 + (T2_hi - T2_hi % 2^22) / 2^22 290 | L17_hi = T2_hi * 2^10 + (T2_lo - T2_lo % 2^22) / 2^22 291 | L22_lo = T4_lo * 2^2 + (T4_hi - T4_hi % 2^30) / 2^30 292 | L22_hi = T4_hi * 2^2 + (T4_lo - T4_lo % 2^30) / 2^30 293 | D_lo = XOR(C2_lo, C4_lo * 2 + (C4_hi - C4_hi % 2^31) / 2^31) 294 | D_hi = XOR(C2_hi, C4_hi * 2 + (C4_lo - C4_lo % 2^31) / 2^31) 295 | T0_lo = XOR(D_lo, L03_lo) 296 | T0_hi = XOR(D_hi, L03_hi) 297 | T1_lo = XOR(D_lo, L08_lo) 298 | T1_hi = XOR(D_hi, L08_hi) 299 | T2_lo = XOR(D_lo, L13_lo) 300 | T2_hi = XOR(D_hi, L13_hi) 301 | T3_lo = XOR(D_lo, L18_lo) 302 | T3_hi = XOR(D_hi, L18_hi) 303 | T4_lo = XOR(D_lo, L23_lo) 304 | T4_hi = XOR(D_hi, L23_hi) 305 | L03_lo = (T2_lo - T2_lo % 2^21) / 2^21 + T2_hi * 2^11 306 | L03_hi = (T2_hi - T2_hi % 2^21) / 2^21 + T2_lo * 2^11 307 | L08_lo = (T4_lo - T4_lo % 2^3) / 2^3 + T4_hi * 2^29 % 2^32 308 | L08_hi = (T4_hi - T4_hi % 2^3) / 2^3 + T4_lo * 2^29 % 2^32 309 | L13_lo = T1_lo * 2^6 + (T1_hi - T1_hi % 2^26) / 2^26 310 | L13_hi = T1_hi * 2^6 + (T1_lo - T1_lo % 2^26) / 2^26 311 | L18_lo = T3_lo * 2^15 + (T3_hi - T3_hi % 2^17) / 2^17 312 | L18_hi = T3_hi * 2^15 + (T3_lo - T3_lo % 2^17) / 2^17 313 | L23_lo = (T0_lo - T0_lo % 2^2) / 2^2 + T0_hi * 2^30 % 2^32 314 | L23_hi = (T0_hi - T0_hi % 2^2) / 2^2 + T0_lo * 2^30 % 2^32 315 | D_lo = XOR(C3_lo, C5_lo * 2 + (C5_hi - C5_hi % 2^31) / 2^31) 316 | D_hi = XOR(C3_hi, C5_hi * 2 + (C5_lo - C5_lo % 2^31) / 2^31) 317 | T0_lo = XOR(D_lo, L04_lo) 318 | T0_hi = XOR(D_hi, L04_hi) 319 | T1_lo = XOR(D_lo, L09_lo) 320 | T1_hi = XOR(D_hi, L09_hi) 321 | T2_lo = XOR(D_lo, L14_lo) 322 | T2_hi = XOR(D_hi, L14_hi) 323 | T3_lo = XOR(D_lo, L19_lo) 324 | T3_hi = XOR(D_hi, L19_hi) 325 | T4_lo = XOR(D_lo, L24_lo) 326 | T4_hi = XOR(D_hi, L24_hi) 327 | L04_lo = T3_lo * 2^21 % 2^32 + (T3_hi - T3_hi % 2^11) / 2^11 328 | L04_hi = T3_hi * 2^21 % 2^32 + (T3_lo - T3_lo % 2^11) / 2^11 329 | L09_lo = T0_lo * 2^28 % 2^32 + (T0_hi - T0_hi % 2^4) / 2^4 330 | L09_hi = T0_hi * 2^28 % 2^32 + (T0_lo - T0_lo % 2^4) / 2^4 331 | L14_lo = T2_lo * 2^25 % 2^32 + (T2_hi - T2_hi % 2^7) / 2^7 332 | L14_hi = T2_hi * 2^25 % 2^32 + (T2_lo - T2_lo % 2^7) / 2^7 333 | L19_lo = (T4_lo - T4_lo % 2^8) / 2^8 + T4_hi * 2^24 % 2^32 334 | L19_hi = (T4_hi - T4_hi % 2^8) / 2^8 + T4_lo * 2^24 % 2^32 335 | L24_lo = (T1_lo - T1_lo % 2^9) / 2^9 + T1_hi * 2^23 % 2^32 336 | L24_hi = (T1_hi - T1_hi % 2^9) / 2^9 + T1_lo * 2^23 % 2^32 337 | D_lo = XOR(C4_lo, C1_lo * 2 + (C1_hi - C1_hi % 2^31) / 2^31) 338 | D_hi = XOR(C4_hi, C1_hi * 2 + (C1_lo - C1_lo % 2^31) / 2^31) 339 | T0_lo = XOR(D_lo, L05_lo) 340 | T0_hi = XOR(D_hi, L05_hi) 341 | T1_lo = XOR(D_lo, L10_lo) 342 | T1_hi = XOR(D_hi, L10_hi) 343 | T2_lo = XOR(D_lo, L15_lo) 344 | T2_hi = XOR(D_hi, L15_hi) 345 | T3_lo = XOR(D_lo, L20_lo) 346 | T3_hi = XOR(D_hi, L20_hi) 347 | T4_lo = XOR(D_lo, L25_lo) 348 | T4_hi = XOR(D_hi, L25_hi) 349 | L05_lo = T4_lo * 2^14 + (T4_hi - T4_hi % 2^18) / 2^18 350 | L05_hi = T4_hi * 2^14 + (T4_lo - T4_lo % 2^18) / 2^18 351 | L10_lo = T1_lo * 2^20 % 2^32 + (T1_hi - T1_hi % 2^12) / 2^12 352 | L10_hi = T1_hi * 2^20 % 2^32 + (T1_lo - T1_lo % 2^12) / 2^12 353 | L15_lo = T3_lo * 2^8 + (T3_hi - T3_hi % 2^24) / 2^24 354 | L15_hi = T3_hi * 2^8 + (T3_lo - T3_lo % 2^24) / 2^24 355 | L20_lo = T0_lo * 2^27 % 2^32 + (T0_hi - T0_hi % 2^5) / 2^5 356 | L20_hi = T0_hi * 2^27 % 2^32 + (T0_lo - T0_lo % 2^5) / 2^5 357 | L25_lo = (T2_lo - T2_lo % 2^25) / 2^25 + T2_hi * 2^7 358 | L25_hi = (T2_hi - T2_hi % 2^25) / 2^25 + T2_lo * 2^7 359 | D_lo = XOR(C5_lo, C2_lo * 2 + (C2_hi - C2_hi % 2^31) / 2^31) 360 | D_hi = XOR(C5_hi, C2_hi * 2 + (C2_lo - C2_lo % 2^31) / 2^31) 361 | T1_lo = XOR(D_lo, L06_lo) 362 | T1_hi = XOR(D_hi, L06_hi) 363 | T2_lo = XOR(D_lo, L11_lo) 364 | T2_hi = XOR(D_hi, L11_hi) 365 | T3_lo = XOR(D_lo, L16_lo) 366 | T3_hi = XOR(D_hi, L16_hi) 367 | T4_lo = XOR(D_lo, L21_lo) 368 | T4_hi = XOR(D_hi, L21_hi) 369 | L06_lo = T2_lo * 2^3 + (T2_hi - T2_hi % 2^29) / 2^29 370 | L06_hi = T2_hi * 2^3 + (T2_lo - T2_lo % 2^29) / 2^29 371 | L11_lo = T4_lo * 2^18 + (T4_hi - T4_hi % 2^14) / 2^14 372 | L11_hi = T4_hi * 2^18 + (T4_lo - T4_lo % 2^14) / 2^14 373 | L16_lo = (T1_lo - T1_lo % 2^28) / 2^28 + T1_hi * 2^4 374 | L16_hi = (T1_hi - T1_hi % 2^28) / 2^28 + T1_lo * 2^4 375 | L21_lo = (T3_lo - T3_lo % 2^23) / 2^23 + T3_hi * 2^9 376 | L21_hi = (T3_hi - T3_hi % 2^23) / 2^23 + T3_lo * 2^9 377 | L01_lo = XOR(D_lo, L01_lo) 378 | L01_hi = XOR(D_hi, L01_hi) 379 | L01_lo, L02_lo, L03_lo, L04_lo, L05_lo = XOR(L01_lo, AND(-1-L02_lo, L03_lo)), XOR(L02_lo, AND(-1-L03_lo, L04_lo)), XOR(L03_lo, AND(-1-L04_lo, L05_lo)), XOR(L04_lo, AND(-1-L05_lo, L01_lo)), XOR(L05_lo, AND(-1-L01_lo, L02_lo)) 380 | L01_hi, L02_hi, L03_hi, L04_hi, L05_hi = XOR(L01_hi, AND(-1-L02_hi, L03_hi)), XOR(L02_hi, AND(-1-L03_hi, L04_hi)), XOR(L03_hi, AND(-1-L04_hi, L05_hi)), XOR(L04_hi, AND(-1-L05_hi, L01_hi)), XOR(L05_hi, AND(-1-L01_hi, L02_hi)) 381 | L06_lo, L07_lo, L08_lo, L09_lo, L10_lo = XOR(L09_lo, AND(-1-L10_lo, L06_lo)), XOR(L10_lo, AND(-1-L06_lo, L07_lo)), XOR(L06_lo, AND(-1-L07_lo, L08_lo)), XOR(L07_lo, AND(-1-L08_lo, L09_lo)), XOR(L08_lo, AND(-1-L09_lo, L10_lo)) 382 | L06_hi, L07_hi, L08_hi, L09_hi, L10_hi = XOR(L09_hi, AND(-1-L10_hi, L06_hi)), XOR(L10_hi, AND(-1-L06_hi, L07_hi)), XOR(L06_hi, AND(-1-L07_hi, L08_hi)), XOR(L07_hi, AND(-1-L08_hi, L09_hi)), XOR(L08_hi, AND(-1-L09_hi, L10_hi)) 383 | L11_lo, L12_lo, L13_lo, L14_lo, L15_lo = XOR(L12_lo, AND(-1-L13_lo, L14_lo)), XOR(L13_lo, AND(-1-L14_lo, L15_lo)), XOR(L14_lo, AND(-1-L15_lo, L11_lo)), XOR(L15_lo, AND(-1-L11_lo, L12_lo)), XOR(L11_lo, AND(-1-L12_lo, L13_lo)) 384 | L11_hi, L12_hi, L13_hi, L14_hi, L15_hi = XOR(L12_hi, AND(-1-L13_hi, L14_hi)), XOR(L13_hi, AND(-1-L14_hi, L15_hi)), XOR(L14_hi, AND(-1-L15_hi, L11_hi)), XOR(L15_hi, AND(-1-L11_hi, L12_hi)), XOR(L11_hi, AND(-1-L12_hi, L13_hi)) 385 | L16_lo, L17_lo, L18_lo, L19_lo, L20_lo = XOR(L20_lo, AND(-1-L16_lo, L17_lo)), XOR(L16_lo, AND(-1-L17_lo, L18_lo)), XOR(L17_lo, AND(-1-L18_lo, L19_lo)), XOR(L18_lo, AND(-1-L19_lo, L20_lo)), XOR(L19_lo, AND(-1-L20_lo, L16_lo)) 386 | L16_hi, L17_hi, L18_hi, L19_hi, L20_hi = XOR(L20_hi, AND(-1-L16_hi, L17_hi)), XOR(L16_hi, AND(-1-L17_hi, L18_hi)), XOR(L17_hi, AND(-1-L18_hi, L19_hi)), XOR(L18_hi, AND(-1-L19_hi, L20_hi)), XOR(L19_hi, AND(-1-L20_hi, L16_hi)) 387 | L21_lo, L22_lo, L23_lo, L24_lo, L25_lo = XOR(L23_lo, AND(-1-L24_lo, L25_lo)), XOR(L24_lo, AND(-1-L25_lo, L21_lo)), XOR(L25_lo, AND(-1-L21_lo, L22_lo)), XOR(L21_lo, AND(-1-L22_lo, L23_lo)), XOR(L22_lo, AND(-1-L23_lo, L24_lo)) 388 | L21_hi, L22_hi, L23_hi, L24_hi, L25_hi = XOR(L23_hi, AND(-1-L24_hi, L25_hi)), XOR(L24_hi, AND(-1-L25_hi, L21_hi)), XOR(L25_hi, AND(-1-L21_hi, L22_hi)), XOR(L21_hi, AND(-1-L22_hi, L23_hi)), XOR(L22_hi, AND(-1-L23_hi, L24_hi)) 389 | L01_lo = XOR(L01_lo, RC_lo[round_idx]) 390 | L01_hi = L01_hi + RC_hi[round_idx] 391 | end 392 | lanes[01], lanes[02], lanes[03], lanes[04], lanes[05], lanes[06], lanes[07], lanes[08], lanes[09], lanes[10], lanes[11], 393 | lanes[12], lanes[13], lanes[14], lanes[15], lanes[16], lanes[17], lanes[18], lanes[19], lanes[20], lanes[21], lanes[22], lanes[23], lanes[24], 394 | lanes[25], lanes[26], lanes[27], lanes[28], lanes[29], lanes[30], lanes[31], lanes[32], lanes[33], lanes[34], lanes[35], lanes[36], lanes[37], 395 | lanes[38], lanes[39], lanes[40], lanes[41], lanes[42], lanes[43], lanes[44], lanes[45], lanes[46], lanes[47], lanes[48], lanes[49], lanes[50] = 396 | L01_lo, L01_hi % 2^32, L02_lo, L02_hi, L03_lo, L03_hi, L04_lo, L04_hi, L05_lo, L05_hi, L06_lo, L06_hi, L07_lo, L07_hi, L08_lo, L08_hi, 397 | L09_lo, L09_hi, L10_lo, L10_hi, L11_lo, L11_hi, L12_lo, L12_hi, L13_lo, L13_hi, L14_lo, L14_hi, L15_lo, L15_hi, L16_lo, L16_hi, 398 | L17_lo, L17_hi, L18_lo, L18_hi, L19_lo, L19_hi, L20_lo, L20_hi, L21_lo, L21_hi, L22_lo, L22_hi, L23_lo, L23_hi, L24_lo, L24_hi, L25_lo, L25_hi 399 | end 400 | end 401 | 402 | local function keccak(block_size_in_bytes, digest_size_in_bytes, is_SHAKE, message) 403 | local tail, lanes = "", create_array_of_lanes() 404 | local result 405 | 406 | local function partial(message_part) 407 | if message_part then 408 | if tail then 409 | local offs = 0 410 | if tail ~= "" and #tail + #message_part >= block_size_in_bytes then 411 | offs = block_size_in_bytes - #tail 412 | keccak_feed(lanes, tail..sub(message_part, 1, offs), 0, block_size_in_bytes, block_size_in_bytes) 413 | tail = "" 414 | end 415 | local size = #message_part - offs 416 | local size_tail = size % block_size_in_bytes 417 | keccak_feed(lanes, message_part, offs, size - size_tail, block_size_in_bytes) 418 | tail = tail..sub(message_part, #message_part + 1 - size_tail) 419 | return partial 420 | else 421 | error("Adding more chunks is not allowed after receiving the result", 2) 422 | end 423 | else 424 | if tail then 425 | local gap_start = is_SHAKE and 31 or 6 426 | tail = tail..(#tail + 1 == block_size_in_bytes and char(gap_start + 128) or char(gap_start)..rep("\0", (-2 - #tail) % block_size_in_bytes).."\128") 427 | keccak_feed(lanes, tail, 0, #tail, block_size_in_bytes) 428 | tail = nil 429 | 430 | local lanes_used = 0 431 | local total_lanes = block_size_in_bytes / 4 432 | local dwords = {} 433 | 434 | local function get_next_dwords_of_digest(dwords_qty) 435 | if lanes_used >= total_lanes then 436 | keccak_feed(lanes, nil, 0, 1, 1) 437 | lanes_used = 0 438 | end 439 | dwords_qty = floor(min(dwords_qty, total_lanes - lanes_used)) 440 | for j = 1, dwords_qty do 441 | dwords[j] = format("%08x", lanes[lanes_used + j]) 442 | end 443 | lanes_used = lanes_used + dwords_qty 444 | return 445 | gsub(concat(dwords, "", 1, dwords_qty), "(..)(..)(..)(..)", "%4%3%2%1"), 446 | dwords_qty * 4 447 | end 448 | 449 | local parts = {} 450 | local last_part, last_part_size = "", 0 451 | 452 | local function get_next_part_of_digest(bytes_needed) 453 | bytes_needed = bytes_needed or 1 454 | if bytes_needed <= last_part_size then 455 | last_part_size = last_part_size - bytes_needed 456 | local part_size_in_nibbles = bytes_needed * 2 457 | local result = sub(last_part, 1, part_size_in_nibbles) 458 | last_part = sub(last_part, part_size_in_nibbles + 1) 459 | return result 460 | end 461 | local parts_qty = 0 462 | if last_part_size > 0 then 463 | parts_qty = 1 464 | parts[parts_qty] = last_part 465 | bytes_needed = bytes_needed - last_part_size 466 | end 467 | while bytes_needed >= 4 do 468 | local next_part, next_part_size = get_next_dwords_of_digest(bytes_needed / 4) 469 | parts_qty = parts_qty + 1 470 | parts[parts_qty] = next_part 471 | bytes_needed = bytes_needed - next_part_size 472 | end 473 | if bytes_needed > 0 then 474 | last_part, last_part_size = get_next_dwords_of_digest(1) 475 | parts_qty = parts_qty + 1 476 | parts[parts_qty] = get_next_part_of_digest(bytes_needed) 477 | else 478 | last_part, last_part_size = "", 0 479 | end 480 | return concat(parts, "", 1, parts_qty) 481 | end 482 | 483 | if digest_size_in_bytes < 0 then 484 | result = get_next_part_of_digest 485 | else 486 | result = get_next_part_of_digest(digest_size_in_bytes) 487 | end 488 | 489 | end 490 | return result 491 | end 492 | end 493 | 494 | if message then 495 | -- Actually perform calculations and return the SHA3 digest of a message 496 | return partial(message)() 497 | else 498 | -- Return function for chunk-by-chunk loading 499 | -- User should feed every chunk of input data as single argument to this function and finally get SHA3 digest by invoking this function without an argument 500 | return partial 501 | end 502 | 503 | end 504 | 505 | function SHA3_224(message) return keccak(144, 28, false, message) end 506 | function SHA3_256(message) return keccak(136, 32, false, message) end 507 | function SHA3_384(message) return keccak(104, 48, false, message) end 508 | function SHA3_512(message) return keccak( 72, 64, false, message) end 509 | function SHAKE128(digest_size_in_bytes, message) return keccak(168, digest_size_in_bytes, true, message) end 510 | function SHAKE256(digest_size_in_bytes, message) return keccak(136, digest_size_in_bytes, true, message) end 511 | 512 | end 513 | 514 | local entropy_counter = 0 515 | 516 | function GetEntropyCounter() 517 | return floor(entropy_counter) 518 | end 519 | 520 | local to_be_refined, to_be_refined_qty = {}, 0 -- buffer for entropy from user actions: 32-bit values, max 128 elements 521 | local refined, refined_qty = {}, 0 -- buffer for precalculated random numbers: 53-bit values, max 1024 elements 522 | local rnd_lanes = create_array_of_lanes() 523 | local RND = 0 524 | 525 | local function mix16(n) 526 | n = ((n + 0xDEAD) % 2^16 + 1) * 0xBEEF % (2^16 + 1) - 1 527 | local K53 = RND 528 | local L36 = K53 % 2^36 529 | RND = L36 * 126611 + (K53 - L36) * (505231 / 2^36) + n % 256 * 598352261448 + n * 2348539529 530 | end 531 | 532 | function perform_calculations() 533 | -- returns true if job's done 534 | if to_be_refined_qty >= 42 or refined_qty <= 1024 - 25 then 535 | local used_qty = min(42, to_be_refined_qty) 536 | for j = 1, used_qty do 537 | rnd_lanes[j] = rnd_lanes[j] + to_be_refined[j] 538 | end 539 | for j = 42 + 1, to_be_refined_qty do 540 | to_be_refined[j - 42] = to_be_refined[j] 541 | end 542 | to_be_refined_qty = to_be_refined_qty - used_qty 543 | keccak_feed(rnd_lanes, nil, 0, 1, 1) 544 | local lane_idx, queued_bits_qty, queued_bits = 0, 0, 0 545 | for j = 1, 25 do 546 | if queued_bits_qty < 21 then 547 | lane_idx = lane_idx + 1 548 | queued_bits = queued_bits * 2^32 + rnd_lanes[lane_idx] 549 | queued_bits_qty = queued_bits_qty + 32 550 | end 551 | local value53 = queued_bits % 2^21 552 | queued_bits = (queued_bits - value53) / 2^21 553 | queued_bits_qty = queued_bits_qty - 21 554 | lane_idx = lane_idx + 1 555 | value53 = rnd_lanes[lane_idx] * 2^21 + value53 556 | if refined_qty < 1024 then 557 | refined_qty = refined_qty + 1 558 | refined[refined_qty] = value53 559 | else 560 | local refined_idx = RND % refined_qty + 1 561 | local old_value53 = refined[refined_idx] 562 | refined[refined_idx] = XOR53(old_value53, value53) 563 | mix16(old_value53) 564 | end 565 | end 566 | else 567 | return true -- nothing to do 568 | end 569 | end 570 | _G.perform_calculations = perform_calculations 571 | 572 | local function refine32(value32) 573 | if to_be_refined_qty < 128 then 574 | to_be_refined_qty = to_be_refined_qty + 1 575 | to_be_refined[to_be_refined_qty] = value32 % 2^32 576 | else 577 | local idx = RND % to_be_refined_qty + 1 578 | to_be_refined[idx] = (to_be_refined[idx] + value32) % 2^32 579 | end 580 | end 581 | 582 | do 583 | local log = math.log 584 | local log4 = log(4) 585 | 586 | local function entropy_from_delta(delta) 587 | -- 'delta' is a difference between two sequential measurements of some integer parameter controlled by user: mouse position, current time 588 | -- all bits except 3 highest might be considered random 589 | delta = delta * delta / 64 590 | return delta < 1 and 0 or log(delta) / log4 591 | end 592 | 593 | local prev_x, prev_y, prev_t 594 | local enumerated = {MOUSE_BUTTON_PRESSED = 600, G_PRESSED = 500, M_PRESSED = 400, MOUSE_BUTTON_RELEASED = 300, G_RELEASED = 200, M_RELEASED = 100, lhc = 50} 595 | 596 | function update_internal_state(event, arg, family) 597 | local x, y, size_x, size_y, c, d = GetMousePositionInPixels() 598 | mix16(c) 599 | mix16(d) 600 | local t = GetRunningTime() 601 | mix16(t) 602 | if event then 603 | if arg then 604 | event = (enumerated[event] or 0) + (enumerated[family] or 0) + arg 605 | mix16(event) 606 | else 607 | for j = 1, #event, 2 do 608 | local low, high = byte(event, j, j + 1) 609 | local value16 = low + (high or 0) * 256 610 | mix16(value16) 611 | refine32(value16) 612 | end 613 | event, prev_x, prev_y, prev_t = 400, x, y, t 614 | end 615 | if event >= 400 then -- only 'pressed' events increase entropy 616 | refine32(t * 2^10 + event) 617 | mix16(x) 618 | refine32(c * 2^16 + d) 619 | mix16(y) 620 | entropy_counter = entropy_counter + entropy_from_delta((t - prev_t) / 16) -- timer's resolution is 16 ms 621 | + ((x < 16 or x >= size_x - 16) and 0 or min(4, entropy_from_delta(x - prev_x))) -- mouse x (mouse position modulo 16 pixels might be considered pure random except when near screen edge) 622 | + ((y < 16 or y >= size_y - 16) and 0 or min(4, entropy_from_delta(y - prev_y))) -- mouse y 623 | prev_x, prev_y, prev_t = x, y, t 624 | end 625 | end 626 | end 627 | _G.update_internal_state = update_internal_state 628 | 629 | end 630 | 631 | local function get_53_random_bits() 632 | if refined_qty == 0 then 633 | perform_calculations() -- precalculate next 25 random numbers (53 bits each), it will take 30 ms 634 | end 635 | local refined_idx = RND % refined_qty + 1 636 | local value53 = refined[refined_idx] 637 | refined[refined_idx] = refined[refined_qty] 638 | refined_qty = refined_qty - 1 639 | mix16(value53) 640 | return value53 641 | end 642 | 643 | local cached_bits, cached_bits_qty = 0, 0 644 | 645 | local function get_random_bits(number_of_bits) 646 | local pwr_number_of_bits = 2^number_of_bits 647 | local result 648 | if number_of_bits <= cached_bits_qty then 649 | result = cached_bits % pwr_number_of_bits 650 | cached_bits = (cached_bits - result) / pwr_number_of_bits 651 | else 652 | local new_bits = get_53_random_bits() 653 | result = new_bits % pwr_number_of_bits 654 | cached_bits = (new_bits - result) / pwr_number_of_bits * 2^cached_bits_qty + cached_bits 655 | cached_bits_qty = 53 + cached_bits_qty 656 | end 657 | cached_bits_qty = cached_bits_qty - number_of_bits 658 | return result 659 | end 660 | 661 | local prev_width, prev_bits_in_factor, prev_k = 0 662 | 663 | function random(m, n) 664 | -- drop-in replacement for math.random() 665 | if m then 666 | if not n then 667 | m, n = 1, m 668 | end 669 | local k = n - m + 1 670 | if k < 1 or k > 2^53 then 671 | error("Invalid arguments for function 'random()'", 2) 672 | end 673 | local width, bits_in_factor, modk 674 | if k == prev_k then 675 | width, bits_in_factor = prev_width, prev_bits_in_factor 676 | else 677 | local pwr_prev_width = 2^prev_width 678 | if k > pwr_prev_width / 2 and k <= pwr_prev_width then 679 | width = prev_width 680 | else 681 | width = 53 682 | local width_low = -1 683 | repeat 684 | local w = floor((width_low + width) / 2) 685 | if k <= 2^w then 686 | width = w 687 | else 688 | width_low = w 689 | end 690 | until width - width_low == 1 691 | prev_width = width 692 | end 693 | bits_in_factor = 0 694 | local bits_in_factor_high = width + 1 695 | while bits_in_factor_high - bits_in_factor > 1 do 696 | local bits_in_new_factor = floor((bits_in_factor + bits_in_factor_high) / 2) 697 | if k % 2^bits_in_new_factor == 0 then 698 | bits_in_factor = bits_in_new_factor 699 | else 700 | bits_in_factor_high = bits_in_new_factor 701 | end 702 | end 703 | prev_k, prev_bits_in_factor = k, bits_in_factor 704 | end 705 | local factor, saved_bits, saved_bits_qty, pwr_saved_bits_qty = 2^bits_in_factor, 0, 0, 2^0 706 | k = k / factor 707 | width = width - bits_in_factor 708 | local pwr_width = 2^width 709 | local gap = pwr_width - k 710 | repeat 711 | modk = get_random_bits(width - saved_bits_qty) * pwr_saved_bits_qty + saved_bits 712 | local modk_in_range = modk < k 713 | if not modk_in_range then 714 | local interval = gap 715 | saved_bits = modk - k 716 | saved_bits_qty = width - 1 717 | pwr_saved_bits_qty = pwr_width / 2 718 | repeat 719 | saved_bits_qty = saved_bits_qty - 1 720 | pwr_saved_bits_qty = pwr_saved_bits_qty / 2 721 | if pwr_saved_bits_qty <= interval then 722 | if saved_bits < pwr_saved_bits_qty then 723 | interval = nil 724 | else 725 | interval = interval - pwr_saved_bits_qty 726 | saved_bits = saved_bits - pwr_saved_bits_qty 727 | end 728 | end 729 | until not interval 730 | end 731 | until modk_in_range 732 | return m + modk * factor + get_random_bits(bits_in_factor) 733 | else 734 | return get_53_random_bits() / 2^53 735 | end 736 | end 737 | 738 | end 739 | 740 | -- function Sleep() is reimplemented to automatically update internal state on every wake-up and to precalculate random numbers instead of long sleeping 741 | local function Sleep(delay_ms) 742 | delay_ms = delay_ms or 5 743 | local start_time = GetRunningTime() 744 | local time_now, jobs_done = start_time 745 | while not jobs_done and time_now < start_time + delay_ms - 50 do 746 | jobs_done = perform_calculations() -- 30 ms of useful job 747 | time_now = GetRunningTime() 748 | end 749 | delay_ms = delay_ms - (time_now - start_time) 750 | if delay_ms > 0 then 751 | Sleep_orig(delay_ms) 752 | end 753 | update_internal_state() -- this invocation adds entropy to RNG (it's very fast) 754 | end 755 | _G.Sleep = Sleep 756 | 757 | local Logitech_order = {"L", "R", "M"} 758 | local Microsoft_order = {L=1, M=2, R=3, l=1, m=2, r=3} 759 | 760 | local 761 | PressMouseButton_orig, ReleaseMouseButton_orig, PressAndReleaseMouseButton_orig, IsMouseButtonPressed_orig = 762 | PressMouseButton, ReleaseMouseButton, PressAndReleaseMouseButton, IsMouseButtonPressed 763 | 764 | -- These functions now accept strings "L", "R", "M" instead of button number 765 | function PressMouseButton(button) 766 | PressMouseButton_orig(Microsoft_order[button] or button) 767 | end 768 | 769 | function ReleaseMouseButton(button) 770 | ReleaseMouseButton_orig(Microsoft_order[button] or button) 771 | end 772 | 773 | function PressAndReleaseMouseButton(button) 774 | PressAndReleaseMouseButton_orig(Microsoft_order[button] or button) 775 | end 776 | 777 | function IsMouseButtonPressed(button) 778 | return IsMouseButtonPressed_orig(Microsoft_order[button] or button) 779 | end 780 | 781 | if D_filename then 782 | 783 | local D_folder = extension_module_full_path:match"^(.-)[^\\/]*$" 784 | 785 | local function execute_Lua_script(filespec, err_message) 786 | local ok, result = pcall(dofile, filespec) 787 | if ok then 788 | return result 789 | elseif err_message then 790 | print(err_message..tostring(result)) 791 | end 792 | end 793 | 794 | function Load_table_D() 795 | local table_d = execute_Lua_script(D_folder..D_filename, "ERROR loading table D from disk\n") 796 | PlayMacro"RUN_D_SAVER" 797 | return table_d 798 | end 799 | 800 | local out94 801 | local standard_Lua_tables = {} 802 | local pushed_digits = {} 803 | local pushed_string_chars = {[10] = 2, [13] = 3, [9] = 4} 804 | for c = 32, 119 do 805 | pushed_string_chars[c] = c - 26 806 | end 807 | local low_zeroes = {[0] = 11} 808 | do 809 | local pos = 1 810 | 811 | local function fill_low_zeroes(k) 812 | if k > 0 then 813 | k = k - 1 814 | fill_low_zeroes(k) 815 | low_zeroes[pos] = k 816 | pos = pos + 1 817 | fill_low_zeroes(k) 818 | end 819 | end 820 | 821 | fill_low_zeroes(11) 822 | end 823 | local list_of_known_values = {0/0, 0, -0, 1/0, -1/0, false, true} 824 | local known_value_to_list_index = {[1/0]=4, [-1/0]=5, [false]=6, [true]=7} 825 | local stack_of_tables_to_parse = {8} 826 | 827 | local function PushValue(val, tp) 828 | local prev_index 829 | if val ~= val then 830 | prev_index = 1 831 | elseif val == 0 then 832 | prev_index = 1/val < 0 and 3 or 2 833 | else 834 | prev_index = known_value_to_list_index[val] 835 | end 836 | local g, m, e 837 | if prev_index then 838 | g = 1 839 | m = prev_index - g 840 | else 841 | local known_index = #list_of_known_values + 1 842 | list_of_known_values[known_index] = val 843 | known_value_to_list_index[val] = known_index 844 | local string_to_send 845 | if tp == "number" then 846 | g = val < 0 and 0 or 2 847 | m, e = math.frexp((g - 1) * val) 848 | m = m * 2^53 849 | e = e - 53 850 | repeat 851 | local w = low_zeroes[m % 2048] 852 | m = m / 2^w 853 | e = e + w 854 | until w < 11 855 | m = (m - 1) / 2 856 | elseif tp == "string" then 857 | out94(90) 858 | string_to_send = val 859 | else 860 | local expr = standard_Lua_tables[val] 861 | if expr then 862 | out94(91) 863 | string_to_send = expr 864 | else 865 | out94(92) 866 | table.insert(stack_of_tables_to_parse, known_index) 867 | end 868 | end 869 | if string_to_send then 870 | for j = 1, #string_to_send do 871 | local c = byte(string_to_send, j) 872 | local b = pushed_string_chars[c] 873 | if not b then 874 | c = (c - 120) % 256 875 | b = c % 94 876 | c = (c - b) / 94 877 | out94(c) 878 | end 879 | out94(b) 880 | end 881 | out94(5) 882 | end 883 | end 884 | if g then 885 | g = g * 30 886 | if m < 22 then 887 | out94(g + m) 888 | else 889 | m = m - 22 890 | local L = 0 891 | repeat 892 | local mod = m % 94 893 | L = L + 1 894 | pushed_digits[L] = mod 895 | m = (m - mod) / 94 - 1 896 | until m < 0 897 | out94(g + 21 + L) 898 | for L = L, 1, -1 do 899 | out94(pushed_digits[L]) 900 | end 901 | end 902 | if e then 903 | if e >= -17 and e <= 53 then 904 | out94(e + 17) 905 | else 906 | local m = e % 94 907 | e = (e - m) / 94 908 | out94(83 + e) 909 | out94(m) 910 | end 911 | end 912 | end 913 | end 914 | 915 | function Save_table_D() 916 | if type(D) ~= "table" then 917 | return 918 | end 919 | local prefix = "ESk" 920 | local prepared_messages = {} 921 | local current_message = {} 922 | local current_message_length = 0 923 | local chksum = 0 924 | 925 | local function flush_message() 926 | if current_message_length > 0 then 927 | for j = 1, current_message_length do 928 | current_message[j] = char(current_message[j]) 929 | end 930 | prepared_messages[#prepared_messages + 1] = concat(current_message, "", 1, current_message_length) 931 | current_message_length = 0 932 | end 933 | end 934 | 935 | function out94(b) 936 | if current_message_length == 60000 then 937 | flush_message() 938 | end 939 | local L36 = chksum % 68719476736 940 | local H17 = (chksum - L36) / 68719476736 941 | chksum = L36 * 126611 + H17 * 505231 + b * 3083 942 | b = b == 5 and 0x7E or b + 0x20 943 | current_message_length = current_message_length + 1 944 | current_message[current_message_length] = b 945 | end 946 | 947 | local data_to_send = {D, D_filename} 948 | list_of_known_values[8] = data_to_send 949 | known_value_to_list_index[data_to_send] = 8 950 | repeat 951 | local tbl = list_of_known_values[table.remove(stack_of_tables_to_parse)] 952 | local next_arr_index = 1 953 | repeat 954 | local value = tbl[next_arr_index] 955 | local tp = type(value) 956 | local ok = tp == "number" or tp == "string" or tp == "table" or tp == "boolean" 957 | if ok then 958 | next_arr_index = next_arr_index + 1 959 | PushValue(value, tp) 960 | end 961 | until not ok 962 | out94(93) 963 | for key, value in pairs(tbl) do 964 | local tpk = type(key) 965 | if tpk == "string" or tpk == "table" or tpk == "boolean" or tpk == "number" and not (key > 0 and key < next_arr_index and key == floor(key)) then 966 | local tpv = type(value) 967 | if tpv == "number" or tpv == "string" or tpv == "table" or tpv == "boolean" then 968 | PushValue(key, tpk) 969 | PushValue(value, tpv) 970 | end 971 | end 972 | end 973 | out94(93) 974 | until #stack_of_tables_to_parse == 0 975 | local cs = chksum % 64847759419249 976 | for _ = 1, 7 do 977 | local b = cs % 94 978 | cs = (cs - b) / 94 979 | out94(b) 980 | end 981 | flush_message() 982 | for j, mes in ipairs(prepared_messages) do 983 | OutputDebugMessage(prefix..(j == 1 and "-" or j % 10)..mes.."\n") 984 | end 985 | OutputDebugMessage"\n" 986 | end 987 | 988 | local function enum_standard_tables(expr) 989 | local t = loadstring("return "..expr)() 990 | local old_expr = standard_Lua_tables[t] 991 | if not old_expr or #old_expr > #expr then 992 | standard_Lua_tables[t] = expr 993 | for key, value in pairs(t) do 994 | if type(key) == "string" and type(value) == "table" and not key:find'[%c"\\]' then 995 | local key_is_identifier = key:find"^[%a_][%w_]*$" 996 | enum_standard_tables(expr == "_G" and key_is_identifier and key or expr..(key_is_identifier and "."..key or '["'..key..'"]')) 997 | end 998 | end 999 | end 1000 | end 1001 | 1002 | enum_standard_tables"debug.getmetatable''" 1003 | enum_standard_tables"debug.getregistry()" 1004 | enum_standard_tables"_G" 1005 | end 1006 | 1007 | do 1008 | -- Test SHA3 functions 1009 | assert(SHA3_224("The quick brown fox jumps over the lazy dog") == "d15dadceaa4d5d7bb3b48f446421d542e08ad8887305e28d58335795") 1010 | assert(SHA3_256("") == "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a") 1011 | assert(SHA3_384("The quick brown fox jumps over the lazy dog") == "7063465e08a93bce31cd89d2e3ca8f602498696e253592ed26f07bf7e703cf328581e1471a7ba7ab119b1a9ebdf8be41") 1012 | assert(SHAKE128(11, "The quick brown fox jumps over the lazy dog") == "f4202e3c5852f9182a0430") 1013 | local generator = SHAKE128(-1, "The quick brown fox jumps over the lazy dog") -- negative digest size means 'return generator of infinite stream' 1014 | assert(generator(5) == "f4202e3c58") -- first 5 bytes 1015 | assert(generator(4) == "52f9182a") -- next 4 bytes, and so on... 1016 | end 1017 | 1018 | _G.Logitech_order = Logitech_order 1019 | --------------------------------------------------------------------------------