├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── build.bat ├── examples ├── RCE.lua │ ├── Libs │ │ ├── httpcodes.lua │ │ ├── mimetypes.lua │ │ └── svhttp.lua │ ├── README.md │ └── main.lua ├── README.md └── ScriptMenu.lua ├── misc ├── Langs │ ├── en.lng │ └── ru.lng ├── Pack.ps1 └── ScriptHook.ps1 └── src ├── base.cpp ├── base.hpp ├── constants.hpp ├── dllmain.cpp ├── dllmain.hpp ├── emu ├── native.cpp └── native.hpp ├── json.hpp ├── langctl.cpp ├── langctl.hpp ├── lualang.cpp ├── lualang.hpp ├── lualog.cpp ├── lualog.hpp ├── luamenu.cpp ├── luamenu.hpp ├── luamisc.cpp ├── luamisc.hpp ├── luanative.cpp ├── luanative.hpp ├── luascript.cpp ├── luascript.hpp ├── menus ├── about.cpp ├── about.hpp ├── helpers.hpp ├── langlist.cpp ├── langlist.hpp ├── main.cpp ├── main.hpp ├── position.cpp ├── position.hpp ├── scripts.cpp ├── scripts.hpp ├── settings.cpp ├── settings.hpp ├── updalert.cpp ├── updalert.hpp ├── updates.cpp └── updates.hpp ├── native ├── cache.hpp ├── call.hpp ├── object.hpp ├── typemap.hpp └── types.hpp ├── nativedb.cpp ├── nativedb.hpp ├── natives.hpp ├── scripthook.hpp ├── settingsctl.cpp ├── settingsctl.hpp ├── thirdparty ├── easyloggingpp.cpp ├── easyloggingpp.h ├── json.hpp ├── keyboard.cpp ├── keyboard.h ├── scriptmenu.cpp └── scriptmenu.h ├── updatesctl.cpp └── updatesctl.hpp /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: [ "https://funding.igvx.ru/" ] 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: build asi 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | name: RedLua 11 | runs-on: windows-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Get previous tag 19 | id: prevtag 20 | shell: bash 21 | run: | 22 | echo "tag=$(GIT.EXE describe --abbrev=0 --tags ${{ github.ref_name }}^)" >> $GITHUB_OUTPUT 23 | 24 | - name: Checkout LuaJIT 25 | uses: actions/checkout@v3 26 | with: 27 | path: .\src\thirdparty\LuaJIT 28 | repository: LuaJIT/LuaJIT 29 | fetch-depth: 0 30 | 31 | - name: Download ScriptHookSDK 32 | shell: pwsh 33 | run: | 34 | .\misc\ScriptHook.ps1 35 | .\misc\ScriptHook.ps1 gtav 36 | 37 | - name: Detect MSVC 38 | uses: ilammy/msvc-dev-cmd@v1 39 | with: 40 | arch: x86_64 41 | 42 | - name: Build plugin 43 | run: | 44 | .\build.bat nodebug 45 | .\build.bat gtav nodebug 46 | 47 | - name: Build release archive 48 | shell: pwsh 49 | run: .\misc\Pack.ps1 ${{ github.ref_name }} 50 | 51 | - name: Create release 52 | uses: softprops/action-gh-release@v1 53 | with: 54 | prerelease: true 55 | name: Pre-release ${{ github.ref_name }} 56 | body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.prevtag.outputs.tag }}...${{ github.ref_name }}" 57 | files: | 58 | ./objs/RedLuaRDR2-${{ github.ref_name }}.zip 59 | ./objs/RedLuaV-${{ github.ref_name }}.zip 60 | 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/settings.json 2 | src/thirdparty/ScriptHook*/ 3 | src/thirdparty/LuaJIT/ 4 | objs/ 5 | 6 | *.pdb 7 | *.asi 8 | *.ilk 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Run standalone", 5 | "type": "cppvsdbg", 6 | "request": "launch", 7 | "program": "D:\\LuaJIT\\src\\LuaJIT.exe", 8 | "args": ["-lRedLua.native"], 9 | "stopAtEntry": false, 10 | "cwd": "${workspaceFolder}\\objs\\outputRDR2\\", 11 | "environment": [], 12 | "console": "integratedTerminal", 13 | "preLaunchTask": "Build RedLua (Standalone)" 14 | }, 15 | { 16 | "name": "Attach game process", 17 | "type": "cppvsdbg", 18 | "request": "attach", 19 | "processId": "${command:pickProcess}" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build RedLua (Standalone)", 6 | "type": "shell", 7 | "command": "build.bat", 8 | "args": ["standalone"], 9 | "group": "build", 10 | "presentation": { 11 | "reveal": "silent" 12 | }, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 igor725 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 |

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 | # Lua scripting mod for RDR2/GTAV 14 | 15 | RedLua is a AB's ScriptHook library that simplifies the game modding process. 16 | 17 | ## Installation 18 | 19 | Download the latest [release](https://github.com/igor725/RedLua/releases) and extract the archive contents into your game folder (must be writeable). **Note that the RedLua won't work without the ScriptHook[RDR2](https://www.dev-c.com/rdr2/scripthookrdr2/)/[V](http://www.dev-c.com/gtav/scripthookv/) library!** 20 | 21 | ## Usage 22 | In **RDR2** press **F7**, in **GTAV** default bind is **F4** (you can change the default hotkey in `\RedLua\Settings.json` file, keycodes available [here](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes)) to open the RedLua menu. Here you can load/reload/stop/unload scripts, reload the NativeDB and change library settings. 23 | 24 | RedLua uses the [**Easylogging++**](https://github.com/amrayn/easyloggingpp#configuration) library, so if you want to configure the logger, just navigate to `\RedLua\` and create a file called `Log.conf` with following contents: 25 | ```conf 26 | * GLOBAL: 27 | FILENAME = "RedLua\Run.log" 28 | ENABLED = true 29 | TO_FILE = false 30 | TO_STANDARD_OUTPUT = true 31 | SUBSECOND_PRECISION = 6 32 | PERFORMANCE_TRACKING = true 33 | MAX_LOG_FILE_SIZE = 2097152 34 | LOG_FLUSH_THRESHOLD = 0 35 | * INFO: 36 | FORMAT = "%datetime INFO %msg" 37 | * DEBUG: 38 | FORMAT = "%datetime{%d/%M} DEBUG %func %msg" 39 | * WARNING: 40 | FORMAT = "%datetime WARN %msg" 41 | * ERROR: 42 | FORMAT = "%datetime ERROR %msg" 43 | * FATAL: 44 | FORMAT = "%datetime FATAL %msg" 45 | 46 | ``` 47 | 48 | ## Scripting 49 | 50 | RedLua searches for scripts in the `\RedLua\Scripts\`. Each script located in this folder will be loaded automatically (If the `Autorun feature` is enabled). Every script should return a `table` (can be empty) with functions `OnLoad`, `OnTick`, `OnStop`, `OnReload`. Almost every function of the RDR2's RAGE is described [here](https://alloc8or.re/rdr3/nativedb/). 51 | 52 | Example: 53 | ```lua 54 | local t = {} 55 | 56 | function t.OnLoad() 57 | print('Yay!') 58 | end 59 | 60 | function t.OnTick() 61 | print('Tick!') 62 | end 63 | 64 | function t.OnStop() 65 | print('Goodbye!') 66 | end 67 | 68 | return t 69 | ``` 70 | 71 | ### There are two ways to call native functions: 72 | 1. `native.call('PLAYER', 'PLAYER_ID')` 73 | 2. `PLAYER:GET_PLAYER_PED(PLAYER:PLAYER_ID())` 74 | 75 | Note that the native functions can return RedLua's `userdata` called `NativeObject`. Each `NativeObject` has its own type (like `const char *`, `Ped`, `Vehicle`, `Entity *`, `Vector`, etc), as Lua-values, the native functions return (take) only the following types: `boolean`, `int`, `float`, `Hash`, `Any` and `string` (take only). Also, `NativeObjects` that are pointers, can be indexed. 76 | 77 | Native functions that expect a parameter of type `Any *` as an argument can take a `cdata` value. Here is the example: 78 | ```lua 79 | -- This script will display an in-game toast notification for 2 seconds when F8 key is released 80 | local t = {} 81 | local ffi = require'ffi' 82 | 83 | function t.OnLoad() 84 | t.me = PLAYER:PLAYER_ID() 85 | t.me_ent = PLAYER:GET_PLAYER_PED(t.me) 86 | t.ent_arr = native.new('Entity', 1024) 87 | end 88 | 89 | function t.OnTick() 90 | if misc.iskeyjustup(VK_F8, true) then 91 | local duration = ffi.new([[ 92 | struct { 93 | int duration; 94 | int some_weird_padding[14]; 95 | } 96 | ]], { 97 | 2000 -- 2000 milliseconds 98 | }) 99 | 100 | local data = ffi.new([[ 101 | struct { 102 | uint64_t weird_padding_again; 103 | const char *title; 104 | const char *text; 105 | uint64_t god_another_unknown_field; 106 | uint64_t iconDict; 107 | uint64_t icon; 108 | uint64_t iconColor; 109 | } 110 | ]], { 111 | 0, 112 | MISC:VAR_STRING(10, 'LITERAL_STRING', 'Hello, RedLua!'):topointer(), 113 | MISC:VAR_STRING(10, 'LITERAL_STRING', jit.version):topointer(), 114 | 0, 115 | MISC:GET_HASH_KEY('HUD_TOASTS'), MISC:GET_HASH_KEY('toast_player_deadeye'), 116 | MISC:GET_HASH_KEY('COLOR_RED') 117 | }) 118 | 119 | UIFEED:_UI_FEED_POST_SAMPLE_TOAST(duration, data, true, true) 120 | end 121 | 122 | -- If you press F9 it will knock out all peds around you 123 | if misc.iskeyjustup(VK_F9, true) then 124 | local cnt = native.allpeds(t.ent_arr) - 1 125 | for i = 0, cnt do 126 | if t.ent_arr[i] ~= t.me_ent then 127 | TASK:CLEAR_PED_TASKS(t.ent_arr[i], false, false) 128 | TASK:TASK_KNOCKED_OUT(t.ent_arr[i], 10, false) 129 | end 130 | end 131 | end 132 | end 133 | 134 | return t 135 | ``` 136 | 137 | Here is a list of all the Lua functions provided by RedLua: 138 | 139 | ```lua 140 | --[[ 141 | Library: native 142 | ]] 143 | 144 | -- Cast NativeObject to a pointer (useful for ffi) 145 | NativeObject:topointer() 146 | 147 | -- Invoke the native function 148 | native.call('PLAYER', 'PLAYER_ID') -- Faster 149 | PLAYER:PLAYER_ID() -- Simpler, but uses a lot of metacalls 150 | 151 | -- Get the information about the function 152 | native.info('BUILTIN', 'WAIT') -- Returns: {hash = '0x4EDE34FBADD967A6', build = 1207, returns = 'void', params = {[1] = {type = 'int', name = 'ms'}}} or nothing if specified function does not exists 153 | 154 | -- Create new typed array 155 | native.new('Vehicle', 5) -- Returns: NativeObject of 5 Vehicles 156 | native.new('Vector3', 4) -- Returns: NativeVector, can be used in functions like ENTITY:GET_ENTITY_MATRIX(...) 157 | 158 | -- Get all world objects 159 | native.allobjects(objects_typed_array) -- Returns: number of objects 160 | native.allpeds(peds_typed_array) 161 | native.allpickups(pickups_typed_array) 162 | native.allvehicles(vehicles_typed_array) 163 | 164 | --[[ 165 | Library: misc 166 | ]] 167 | 168 | -- Is key down for the last 5 seconds 169 | misc.iskeydown(VK_*) 170 | 171 | -- Is key down for the last 30 seconds 172 | misc.iskeydownlong(VK_*) 173 | 174 | -- Is key just up 175 | misc.iskeyjustup(VK_*, exclusive = false) 176 | 177 | -- Reset key state 178 | misc.resetkey(VK_*) 179 | 180 | -- Get game version 181 | misc.gamever() -- Returns: number (getGameVersion() result) and string ("rdr3" or "gta5") 182 | 183 | -- Get RedLua version 184 | misc.libver() -- Returns: integer, e.g. 010, 020, etc. 185 | 186 | --[[ 187 | Library: menu 188 | ]] 189 | 190 | -- Create a menu section for the script 191 | -- An example script is available here: examples/ScriptMenu.lua 192 | menu.set {...} -- Returns: nothing. Note that this function is NOT safe, it may throw an Lua error. 193 | 194 | -- Remove menu section for the script 195 | -- Note that the menu will only be removed after 1 or more (depends on the number of submenus) full garbage collection steps! 196 | menu.remove() -- Returns: nothing 197 | 198 | --[[ 199 | Library: lang 200 | ]] 201 | 202 | -- Add locale strings 203 | -- Example script: examples/ScriptMenu.lua 204 | lang.install {...} -- Returns: nothing 205 | 206 | -- Get localized string for the current language 207 | lang.get(code) -- Returns: string 208 | 209 | ``` 210 | 211 | ## Contribution 212 | 213 | This is my first project that uses C++, so I'm not really good at it. If you see some cursed code and you know how to improve it, PRs are welcome. 214 | 215 | ## Thanks 216 | 217 | Thanks to [Mike Pall](https://github.com/LuaJIT/LuaJIT), [Alexander Blade](https://www.dev-c.com/rdr2/scripthookrdr2/), [alloc8or](https://github.com/alloc8or/rdr3-nativedb-data), [Niels Lohmann](https://github.com/nlohmann/json) and [abumusamq](https://github.com/amrayn/easyloggingpp) for all their awesome work. 218 | 219 | ## License 220 | 221 | RedLua is released under The MIT License. This license excludes files in the `src\thirdparty` that may be distributed under another license. Please read the `LICENSE` file for more information. 222 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | IF NOT "%VSCMD_ARG_TGT_ARCH%"=="x64" ( 3 | ECHO This script must be executed with Miscrosoft Visual Studio C++ ^(x64^) environment loaded 4 | EXIT /B 1 5 | ) 6 | 7 | SETLOCAL ENABLEDELAYEDEXPANSION 8 | SET RL_GTAV=0 9 | SET RL_STANDALONE=0 10 | SET RL_NODEBUG=0 11 | 12 | :argparse 13 | IF "%1"=="gtav" SET RL_GTAV=1 14 | IF "%1"=="standalone" SET RL_STANDALONE=1 15 | IF "%1"=="nodebug" SET RL_NODEBUG=1 16 | IF "%1"=="" GOTO argdone 17 | SHIFT 18 | GOTO argparse 19 | 20 | :argdone 21 | SET RL_LUAJIT_SOURCE_DIR=.\src\thirdparty\LuaJIT\src 22 | IF NOT EXIST "!RL_LUAJIT_SOURCE_DIR!\lua51.lib" ( 23 | PUSHD !RL_LUAJIT_SOURCE_DIR! 24 | CALL .\msvcbuild.bat 25 | IF !ERRORLEVEL! NEQ 0 ( 26 | ECHO Failed to compile LuaJIT 27 | EXIT /B 1 28 | ) 29 | POPD 30 | ) 31 | 32 | SET RL_OUT_BIN=RedLua.asi 33 | SET RL_LIBS=user32.lib shell32.lib Wininet.lib lua51.lib 34 | SET RL_SOURCES=src\*.cpp src\thirdparty\*.cpp src\menus\*.cpp 35 | SET RL_LDFLAGS=/DLL /INCREMENTAL /LIBPATH:"!RL_LUAJIT_SOURCE_DIR!" 36 | SET RL_CFLAGS=/DELPP_NO_DEFAULT_LOG_FILE /DELPP_DISABLE_LOG_FILE_FROM_ARG ^ 37 | /DELPP_THREAD_SAFE /DWIN32_LEAN_AND_MEAN /D_CRT_SECURE_NO_WARNINGS /FC /W2 ^ 38 | /Isrc\ /EHsc /MP /DLL 39 | 40 | IF !RL_NODEBUG! EQU 0 ( 41 | SET RL_CFLAGS=!RL_CFLAGS! /Zi /DEBUG /MTd 42 | ) ELSE ( 43 | SET RL_CFLAGS=!RL_CFLAGS! /MT 44 | ) 45 | 46 | IF !RL_GTAV! EQU 1 ( 47 | SET RL_OUT_PATH="D:\Games\Grand Theft Auto V" 48 | SET RL_SCRIPTHOOK_VARIANT=V 49 | SET RL_CFLAGS=!RL_CFLAGS! /DREDLUA_GTAV 50 | ) ELSE ( 51 | SET RL_OUT_PATH="D:\SteamLibrary\steamapps\common\Red Dead Redemption 2" 52 | SET RL_CFLAGS=!RL_CFLAGS! /DREDLUA_RDR3 53 | SET RL_SCRIPTHOOK_VARIANT=RDR2 54 | ) 55 | 56 | SET RL_SCRIPTHOOK_SDK_DIR=.\src\thirdparty\ScriptHook!RL_SCRIPTHOOK_VARIANT! 57 | 58 | IF !RL_STANDALONE! EQU 1 ( 59 | SET RL_OUT_PATH=".\objs\output!RL_SCRIPTHOOK_VARIANT!" 60 | SET RL_CFLAGS=!RL_CFLAGS! /DREDLUA_STANDALONE 61 | SET RL_CFLAGS=!RL_CFLAGS! /Fd!RL_OUT_PATH!\ 62 | SET RL_SOURCES=!RL_SOURCES! src\emu\native.cpp 63 | SET RL_OUT_BIN=RedLua.dll 64 | ) ELSE ( 65 | SET RL_LIBS=!RL_LIBS! ScriptHook!RL_SCRIPTHOOK_VARIANT!.lib 66 | SET RL_LDFLAGS=!RL_LDFLAGS! /LIBPATH:"!RL_SCRIPTHOOK_SDK_DIR!\lib" 67 | ) 68 | 69 | MKDIR ".\objs\!RL_SCRIPTHOOK_VARIANT!" 2> NUL 70 | SET RL_CFLAGS=!RL_CFLAGS! /Foobjs\!RL_SCRIPTHOOK_VARIANT!\ 71 | IF NOT EXIST !RL_OUT_PATH! ( 72 | SET RL_OUT_PATH=".\objs\output!RL_SCRIPTHOOK_VARIANT!" 73 | MKDIR !RL_OUT_PATH! 2> NUL 74 | ) 75 | IF !RL_STANDALONE! NEQ 1 IF NOT EXIST "!RL_OUT_PATH!\lua51.dll" ( 76 | COPY !RL_LUAJIT_SOURCE_DIR!\lua51.dll !RL_OUT_PATH! 2> NUL 77 | ) 78 | CL !RL_CFLAGS! /Fe!RL_OUT_PATH!\!RL_OUT_BIN! !RL_SOURCES! /link !RL_LDFLAGS! !RL_LIBS! 79 | IF !ERRORLEVEL! NEQ 0 ( 80 | ENDLOCAL 81 | EXIT /B 1 82 | ) 83 | ENDLOCAL 84 | -------------------------------------------------------------------------------- /examples/RCE.lua/Libs/httpcodes.lua: -------------------------------------------------------------------------------- 1 | return { 2 | [100] = "Continue", 3 | [101] = "Switching Protocols", 4 | [200] = "OK", 5 | [201] = "Created", 6 | [202] = "Accepted", 7 | [203] = "Non-Authoritative Information", 8 | [204] = "No Content", 9 | [205] = "Reset Content", 10 | [206] = "Partial Content", 11 | [300] = "Multiple Choices", 12 | [301] = "Moved Permanently", 13 | [302] = "Found", 14 | [303] = "See Other", 15 | [304] = "Not Modified", 16 | [305] = "Use Proxy", 17 | [307] = "Temporary Redirect", 18 | [400] = "Bad Request", 19 | [401] = "Unauthorized", 20 | [402] = "Payment Required", 21 | [403] = "Forbidden", 22 | [404] = "Not Found", 23 | [405] = "Method Not Allowed", 24 | [406] = "Not Acceptable", 25 | [407] = "Proxy Authentication Required", 26 | [408] = "Request Timeout", 27 | [409] = "Conflict", 28 | [410] = "Gone", 29 | [411] = "Length Required", 30 | [412] = "Precondition Failed", 31 | [413] = "Payload Too Large", 32 | [414] = "URI Too Long", 33 | [415] = "Unsupported Media Type", 34 | [416] = "Range Not Satisfiable", 35 | [417] = "Expectation Failed", 36 | [418] = "I'm a teapot", 37 | [426] = "Upgrade Required", 38 | [500] = "Internal Server Error", 39 | [501] = "Not Implemented", 40 | [502] = "Bad Gateway", 41 | [503] = "Service Unavailable", 42 | [504] = "Gateway Time-out", 43 | [505] = "HTTP Version Not Supported", 44 | [102] = "Processing", 45 | [207] = "Multi-Status", 46 | [226] = "IM Used", 47 | [308] = "Permanent Redirect", 48 | [422] = "Unprocessable Entity", 49 | [423] = "Locked", 50 | [424] = "Failed Dependency", 51 | [428] = "Precondition Required", 52 | [429] = "Too Many Requests", 53 | [431] = "Request Header Fields Too Large", 54 | [451] = "Unavailable For Legal Reasons", 55 | [506] = "Variant Also Negotiates", 56 | [507] = "Insufficient Storage", 57 | [511] = "Network Authentication Required" 58 | } 59 | -------------------------------------------------------------------------------- /examples/RCE.lua/Libs/mimetypes.lua: -------------------------------------------------------------------------------- 1 | -- mimetypes.lua 2 | -- Version 1.0.0 3 | 4 | --[[ 5 | Copyright (c) 2011 Matthew "LeafStorm" Frazier 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | ====== 29 | 30 | In addition, the MIME types contained in the Software were 31 | originally obtained from the Python 2.7.1 ``mimetypes.py`` module, 32 | though they have been considerably modified and augmented. 33 | Said file was made available under the Python Software Foundation 34 | license (http://python.org/psf/license/). 35 | ]] 36 | 37 | -- This table is the one that actually contains the exported functions. 38 | 39 | local mimetypes = {} 40 | 41 | mimetypes.version = '1.0.0' 42 | 43 | 44 | -- Extracts the extension from a filename and returns it. 45 | -- The extension must be at the end of the string, and preceded by a dot and 46 | -- at least one other character. Only the last part will be returned (so 47 | -- "package-1.2.tar.gz" will return "gz"). 48 | -- If there is no extension, this function will return nil. 49 | 50 | local function extension (filename) 51 | return filename:match(".+%.([%a%d]+)$") 52 | end 53 | 54 | 55 | -- Creates a deep copy of the given table. 56 | 57 | local function copy (tbl) 58 | local ntbl = {} 59 | for key, value in pairs(tbl) do 60 | if type(value) == 'table' then 61 | ntbl[key] = copy(value) 62 | else 63 | ntbl[key] = value 64 | end 65 | end 66 | return ntbl 67 | end 68 | 69 | 70 | -- This is the default MIME type database. 71 | -- It is a table with two members - "extensions" and "filenames". 72 | -- The filenames table maps complete file names (like README) to MIME types. 73 | -- The extensions just maps the files' extensions (like jpg) to types. 74 | 75 | local defaultdb = {extensions = {}, filenames = {}} 76 | 77 | local extensions = defaultdb.extensions 78 | local filenames = defaultdb.filenames 79 | 80 | -- The MIME types are sorted first by major type ("application/"), then by 81 | -- extension. Remember to not include the dot on the extension. 82 | 83 | -- application/ 84 | extensions['a'] = 'application/octet-stream' 85 | extensions['ai'] = 'application/postscript' 86 | extensions['asc'] = 'application/pgp-signature' 87 | extensions['atom'] = 'application/atom+xml' 88 | extensions['bcpio'] = 'application/x-bcpio' 89 | extensions['bin'] = 'application/octet-stream' 90 | extensions['bz2'] = 'application/x-bzip2' 91 | extensions['cab'] = 'application/vnd.ms-cab-compressed' 92 | extensions['chm'] = 'application/vnd.ms-htmlhelp' 93 | extensions['class'] = 'application/octet-stream' 94 | extensions['cdf'] = 'application/x-netcdf' 95 | extensions['cpio'] = 'application/x-cpio' 96 | extensions['csh'] = 'application/x-csh' 97 | extensions['deb'] = 'application/x-deb' 98 | extensions['dll'] = 'application/octet-stream' 99 | extensions['dmg'] = 'application/x-apple-diskimage' 100 | extensions['doc'] = 'application/msword' 101 | extensions['docx'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' 102 | extensions['dot'] = 'application/msword' 103 | extensions['dvi'] = 'application/x-dvi' 104 | extensions['eps'] = 'application/postscript' 105 | extensions['exe'] = 'application/octet-stream' 106 | extensions['gtar'] = 'application/x-gtar' 107 | extensions['gz'] = 'application/x-gzip' 108 | extensions['hdf'] = 'application/x-hdf' 109 | extensions['hqx'] = 'application/mac-binhex40' 110 | extensions['iso'] = 'application/octet-stream' 111 | extensions['jar'] = 'application/java-archive' 112 | extensions['js'] = 'application/javascript' 113 | extensions['json'] = 'application/json' 114 | extensions['latex'] = 'application/x-latex' 115 | extensions['man'] = 'application/x-troff-man' 116 | extensions['me'] = 'application/x-troff-me' 117 | extensions['mif'] = 'application/x-mif' 118 | extensions['ms'] = 'application/x-troff-ms' 119 | extensions['nc'] = 'application/x-netcdf' 120 | extensions['o'] = 'application/octet-stream' 121 | extensions['obj'] = 'application/octet-stream' 122 | extensions['oda'] = 'application/oda' 123 | extensions['odt'] = 'application/vnd.oasis.opendocument.text' 124 | extensions['odp'] = 'application/vnd.oasis.opendocument.presentation' 125 | extensions['ods'] = 'application/vnd.oasis.opendocument.spreadsheet' 126 | extensions['odg'] = 'application/vnd.oasis.opendocument.graphics' 127 | extensions['p12'] = 'application/x-pkcs12' 128 | extensions['p7c'] = 'application/pkcs7-mime' 129 | extensions['pdf'] = 'application/pdf' 130 | extensions['pfx'] = 'application/x-pkcs12' 131 | extensions['pgp'] = 'application/pgp-encrypted' 132 | extensions['pot'] = 'application/vnd.ms-powerpoint' 133 | extensions['ppa'] = 'application/vnd.ms-powerpoint' 134 | extensions['pps'] = 'application/vnd.ms-powerpoint' 135 | extensions['ppt'] = 'application/vnd.ms-powerpoint' 136 | extensions['pptx'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation' 137 | extensions['ps'] = 'application/postscript' 138 | extensions['pwz'] = 'application/vnd.ms-powerpoint' 139 | extensions['pyc'] = 'application/x-python-code' 140 | extensions['pyo'] = 'application/x-python-code' 141 | extensions['ram'] = 'application/x-pn-realaudio' 142 | extensions['rar'] = 'application/x-rar-compressed' 143 | extensions['rdf'] = 'application/rdf+xml' 144 | extensions['rpm'] = 'application/x-redhat-package-manager' 145 | extensions['rss'] = 'application/rss+xml' 146 | extensions['rtf'] = 'application/rtf' 147 | extensions['roff'] = 'application/x-troff' 148 | extensions['sh'] = 'application/x-sh' 149 | extensions['shar'] = 'application/x-shar' 150 | extensions['sig'] = 'application/pgp-signature' 151 | extensions['sit'] = 'application/x-stuffit' 152 | extensions['smil'] = 'application/smil+xml' 153 | extensions['so'] = 'application/octet-stream' 154 | extensions['src'] = 'application/x-wais-source' 155 | extensions['sv4cpio'] = 'application/x-sv4cpio' 156 | extensions['sv4crc'] = 'application/x-sv4crc' 157 | extensions['swf'] = 'application/x-shockwave-flash' 158 | extensions['t'] = 'application/x-troff' 159 | extensions['tar'] = 'application/x-tar' 160 | extensions['tcl'] = 'application/x-tcl' 161 | extensions['tex'] = 'application/x-tex' 162 | extensions['texi'] = 'application/x-texinfo' 163 | extensions['texinfo'] = 'application/x-texinfo' 164 | extensions['torrent'] = 'application/x-bittorrent' 165 | extensions['tr'] = 'application/x-troff' 166 | extensions['ustar'] = 'application/x-ustar' 167 | extensions['wiz'] = 'application/msword' 168 | extensions['wsdl'] = 'application/wsdl+xml' 169 | extensions['xht'] = 'application/xhtml+xml' 170 | extensions['xhtml'] = 'application/xhtml+xml' 171 | extensions['xlb'] = 'application/vnd.ms-excel' 172 | extensions['xls'] = 'application/vnd.ms-excel' 173 | extensions['xlsx'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 174 | extensions['xpdl'] = 'application/xml' 175 | extensions['xsl'] = 'application/xml' 176 | extensions['xul'] = 'application/vnd.mozilla.xul+xml' 177 | extensions['zip'] = 'application/zip' 178 | 179 | -- audio/ 180 | extensions['aif'] = 'audio/x-aiff' 181 | extensions['aifc'] = 'audio/x-aiff' 182 | extensions['aiff'] = 'audio/x-aiff' 183 | extensions['au'] = 'audio/basic' 184 | extensions['flac'] = 'audio/x-flac' 185 | extensions['mid'] = 'audio/midi' 186 | extensions['midi'] = 'audio/midi' 187 | extensions['mp2'] = 'audio/mpeg' 188 | extensions['mp3'] = 'audio/mpeg' 189 | extensions['m3u'] = 'audio/x-mpegurl' 190 | extensions['oga'] = 'audio/ogg' 191 | extensions['ogg'] = 'audio/ogg' 192 | extensions['ra'] = 'audio/x-pn-realaudio' 193 | extensions['snd'] = 'audio/basic' 194 | extensions['wav'] = 'audio/x-wav' 195 | 196 | -- image/ 197 | extensions['bmp'] = 'image/x-ms-bmp' 198 | extensions['djv'] = 'image/vnd.djvu' 199 | extensions['djvu'] = 'image/vnd.djvu' 200 | extensions['gif'] = 'image/gif' 201 | extensions['ico'] = 'image/vnd.microsoft.icon' 202 | extensions['ief'] = 'image/ief' 203 | extensions['jpe'] = 'image/jpeg' 204 | extensions['jpeg'] = 'image/jpeg' 205 | extensions['jpg'] = 'image/jpeg' 206 | extensions['pbm'] = 'image/x-portable-bitmap' 207 | extensions['pgm'] = 'image/x-portable-graymap' 208 | extensions['png'] = 'image/png' 209 | extensions['pnm'] = 'image/x-portable-anymap' 210 | extensions['ppm'] = 'image/x-portable-pixmap' 211 | extensions['psd'] = 'image/vnd.adobe.photoshop' 212 | extensions['ras'] = 'image/x-cmu-raster' 213 | extensions['rgb'] = 'image/x-rgb' 214 | extensions['svg'] = 'image/svg+xml' 215 | extensions['svgz'] = 'image/svg+xml' 216 | extensions['tif'] = 'image/tiff' 217 | extensions['tiff'] = 'image/tiff' 218 | extensions['xbm'] = 'image/x-xbitmap' 219 | extensions['xpm'] = 'image/x-xpixmap' 220 | extensions['xwd'] = 'image/x-xwindowdump' 221 | 222 | -- message/ 223 | extensions['eml'] = 'message/rfc822' 224 | extensions['mht'] = 'message/rfc822' 225 | extensions['mhtml'] = 'message/rfc822' 226 | extensions['nws'] = 'message/rfc822' 227 | 228 | -- model/ 229 | extensions['vrml'] = 'model/vrml' 230 | 231 | -- text/ 232 | extensions['asm'] = 'text/x-asm' 233 | extensions['bat'] = 'text/plain' 234 | extensions['c'] = 'text/x-c' 235 | extensions['cc'] = 'text/x-c' 236 | extensions['conf'] = 'text/plain' 237 | extensions['cpp'] = 'text/x-c' 238 | extensions['css'] = 'text/css' 239 | extensions['csv'] = 'text/csv' 240 | extensions['diff'] = 'text/x-diff' 241 | extensions['etx'] = 'text/x-setext' 242 | extensions['gemspec'] = 'text/x-ruby' 243 | extensions['h'] = 'text/x-c' 244 | extensions['hh'] = 'text/x-c' 245 | extensions['htm'] = 'text/html' 246 | extensions['html'] = 'text/html' 247 | extensions['ics'] = 'text/calendar' 248 | extensions['java'] = 'text/x-java' 249 | extensions['ksh'] = 'text/plain' 250 | extensions['lua'] = 'text/x-lua' 251 | extensions['manifest'] = 'text/cache-manifest' 252 | extensions['md'] = 'text/x-markdown' 253 | extensions['p'] = 'text/x-pascal' 254 | extensions['pas'] = 'text/x-pascal' 255 | extensions['pl'] = 'text/x-perl' 256 | extensions['pm'] = 'text/x-perl' 257 | extensions['py'] = 'text/x-python' 258 | extensions['rb'] = 'text/x-ruby' 259 | extensions['ru'] = 'text/x-ruby' 260 | extensions['rockspec'] = 'text/x-lua' 261 | extensions['rtx'] = 'text/richtext' 262 | extensions['s'] = 'text/x-asm' 263 | extensions['sgm'] = 'text/x-sgml' 264 | extensions['sgml'] = 'text/x-sgml' 265 | extensions['text'] = 'text/plain' 266 | extensions['tsv'] = 'text/tab-separated-values' 267 | extensions['txt'] = 'text/plain' 268 | extensions['vcf'] = 'text/x-vcard' 269 | extensions['vcs'] = 'text/x-vcalendar' 270 | extensions['xml'] = 'text/xml' 271 | extensions['yaml'] = 'text/yaml' 272 | extensions['yml'] = 'text/yml' 273 | 274 | -- video/ 275 | extensions['avi'] = 'video/x-msvideo' 276 | extensions['flv'] = 'video/x-flv' 277 | extensions['m1v'] = 'video/mpeg' 278 | extensions['mov'] = 'video/quicktime' 279 | extensions['movie'] = 'video/x-sgi-movie' 280 | extensions['mng'] = 'video/x-mng' 281 | extensions['mp4'] = 'video/mp4' 282 | extensions['mpa'] = 'video/mpeg' 283 | extensions['mpe'] = 'video/mpeg' 284 | extensions['mpeg'] = 'video/mpeg' 285 | extensions['mpg'] = 'video/mpeg' 286 | extensions['ogv'] = 'video/ogg' 287 | extensions['qt'] = 'video/quicktime' 288 | 289 | -- This contains filename overrides for certain files, like README files. 290 | -- Sort them in the same order as extensions. 291 | 292 | filenames['COPYING'] = 'text/plain' 293 | filenames['LICENSE'] = 'text/plain' 294 | filenames['Makefile'] = 'text/x-makefile' 295 | filenames['README'] = 'text/plain' 296 | 297 | 298 | -- Creates a copy of the MIME types database for customization. 299 | 300 | function mimetypes.copy (db) 301 | db = db or defaultdb 302 | return copy(db) 303 | end 304 | 305 | 306 | -- Guesses the MIME type of the file with the given name. 307 | -- It is returned as a string. If the type cannot be guessed, then nil is 308 | -- returned. 309 | 310 | function mimetypes.guess (filename, db) 311 | db = db or defaultdb 312 | if db.filenames[filename] then 313 | return db.filenames[filename] 314 | end 315 | local ext = extension(filename) 316 | if ext then 317 | return db.extensions[ext] 318 | end 319 | return nil 320 | end 321 | 322 | return mimetypes 323 | -------------------------------------------------------------------------------- /examples/RCE.lua/Libs/svhttp.lua: -------------------------------------------------------------------------------- 1 | local ffi = require'ffi' 2 | local C = ffi.C 3 | ffi.cdef[[ 4 | void *malloc(size_t); 5 | void *realloc(void *, size_t); 6 | void free(void *); 7 | 8 | void Sleep(unsigned long); 9 | 10 | struct WSADATA { 11 | short wLoVer, wHiVer; 12 | unsigned short iMaxSock; 13 | long dont_care[128]; 14 | }; 15 | 16 | int WSAStartup(short, struct WSADATA *); 17 | void WSACleanup(); 18 | unsigned long WSAGetLastError(); 19 | 20 | struct sockaddr { 21 | unsigned short sa_family; 22 | char sa_data[14]; 23 | }; 24 | 25 | struct in_addr { 26 | unsigned long s_addr; 27 | }; 28 | 29 | struct sockaddr_in { 30 | short sin_family; 31 | unsigned short sin_port; 32 | struct in_addr sin_addr; 33 | char sin_zero[8]; 34 | }; 35 | 36 | unsigned short htons(unsigned short); 37 | unsigned long htonl(unsigned long); 38 | int inet_pton(int, const char *, void *); 39 | 40 | int socket(int, int, int); 41 | int bind(int, const struct sockaddr *, int); 42 | int listen(int, int); 43 | int accept(int); 44 | int recv(int, char *, int, int); 45 | int send(int, const char *, int, int); 46 | int setsockopt(int, int, int, const void *, int); 47 | int ioctlsocket(int, long, unsigned long *); 48 | void closesocket(int); 49 | 50 | struct sockbuf { 51 | unsigned int size, pos; 52 | char *ptr; 53 | }; 54 | ]] 55 | local lib = ffi.load'ws2_32' 56 | local wdata = ffi.new('struct WSADATA') 57 | if lib.WSAStartup(0x0202, wdata) ~= 0 then 58 | error('WSAStartup failed') 59 | end 60 | 61 | local httpcodes = require('httpcodes') 62 | local no_body = { 63 | ['GET'] = true, ['DELETE'] = true, 64 | ['TRACE'] = true, ['OPTIONS'] = true, 65 | ['HEAD'] = true 66 | } 67 | 68 | local ctx_meta = { 69 | __metatable = 'none of your business', 70 | 71 | isPOST = function(self) 72 | return self.request.method == 'POST' 73 | end, 74 | isGET = function(self) 75 | return self.request.method == 'GET' 76 | end, 77 | isMethod = function(self, meth) 78 | return self.request.method == meth 79 | end, 80 | 81 | getUserAgent = function(self) 82 | return self.reqheaders['user-agent'] 83 | end, 84 | getRequestAddress = function(self) 85 | return self.client:getpeername() 86 | end, 87 | getRequestHeader = function(self, hdr) 88 | return self.reqheaders[hdr:lower()] 89 | end, 90 | getRequestBody = function(self) 91 | return self.reqheaders['content-type'], self.request.body 92 | end, 93 | 94 | setCode = function(self, code) 95 | self.respcode = code or 200 96 | return self 97 | end, 98 | setContentType = function(self, t) 99 | self.respheaders['Content-Type'] = t 100 | return self 101 | end, 102 | 103 | unpack = function(self, data) 104 | return self.respcode, self.respheaders, data 105 | end 106 | } 107 | ctx_meta.__index = ctx_meta 108 | 109 | local _M = { 110 | _VERSION = 1 111 | } 112 | _M.__index = _M 113 | 114 | local function ensure(buf, need) 115 | if buf.size - buf.pos < need then 116 | local newsz = buf.size + need + 256 117 | buf.ptr = C.realloc(buf.ptr, newsz) 118 | assert(buf.ptr ~= nil, 'realloc() failed') 119 | buf.size = newsz 120 | end 121 | end 122 | 123 | local sock_meta = { 124 | init = function(self, fd) 125 | self.fd = fd 126 | local val = ffi.new('long[1]', 0) 127 | if lib.setsockopt(fd, 0xffff, 0x0004, val, 4) ~= 0 then -- REUSEADDR 128 | error('setsockopt failed') 129 | end 130 | val[0] = 1 131 | if lib.ioctlsocket(fd, -2147195266, val) ~= 0 then -- FIONBIO 132 | print(lib.WSAGetLastError()) 133 | error('ioctlsocket failed') 134 | end 135 | end, 136 | bind = function(self, addr) 137 | if lib.bind(self.fd, ffi.cast('const struct sockaddr *', addr), ffi.sizeof(addr)) ~= 0 then 138 | print(lib.WSAGetLastError()) 139 | error('bind() failed') 140 | end 141 | if lib.listen(self.fd, 128) ~= 0 then 142 | print(lib.WSAGetLastError()) 143 | error('listen() failed') 144 | end 145 | end, 146 | receive = function(self, t) 147 | if self._sockbuf == nil then 148 | self._sockbuf = ffi.new('struct sockbuf', { 149 | 32, 0, C.malloc(32) 150 | }) 151 | end 152 | 153 | if t == '*l' then 154 | local len 155 | repeat 156 | ensure(self._sockbuf, 1) 157 | len = lib.recv(self.fd, self._sockbuf.ptr + self._sockbuf.pos, 1, 0) 158 | if len > 0 then 159 | local ch = self._sockbuf.ptr[self._sockbuf.pos] 160 | if ch == 10 then 161 | local e = self._sockbuf.pos 162 | self._sockbuf.pos = 0 163 | return ffi.string(self._sockbuf.ptr, e) 164 | elseif ch ~= 13 then 165 | self._sockbuf.pos = self._sockbuf.pos + 1 166 | end 167 | end 168 | until len == -1 or len == 0 169 | 170 | return nil, 'timeout' 171 | elseif t == '*a' then 172 | ensure(self._sockbuf, 128) 173 | local ret 174 | repeat 175 | ret = lib.recv(self.fd, self._sockbuf.ptr + self._sockbuf.pos, 128, 0) 176 | if ret > 0 then self._sockbuf.pos = self._sockbuf.pos + ret end 177 | until ret == -1 or ret == 0 178 | 179 | return self._sockbuf.pos 180 | elseif type(t) == 'number' then 181 | ensure(self._sockbuf, t) 182 | local len = lib.recv(self.fd, self._sockbuf.ptr, t, 0) 183 | return ffi.string(self._sockbuf.ptr, len) 184 | end 185 | 186 | return nil, 'fuck' 187 | end, 188 | send = function(self, data, from) 189 | from = from or 0 190 | local bs = #data - from 191 | local sent = from 192 | if bs > 0 then 193 | data = ffi.cast('char *', data) + from 194 | local ret = lib.send(self.fd, data, bs, 0) 195 | if ret < 0 then return 0, 'timeout', from end 196 | from = from + ret 197 | bs = bs - ret 198 | end 199 | 200 | local err 201 | if bs ~= 0 then 202 | err = 'timeout' 203 | else 204 | from = nil 205 | end 206 | 207 | return sent, err, from 208 | end, 209 | close = function(self) 210 | lib.closesocket(self.fd) 211 | if self._sockbuf then 212 | C.free(self._sockbuf.ptr) 213 | self._sockbuf.ptr = nil 214 | end 215 | end 216 | } 217 | sock_meta.__index = sock_meta 218 | sock_meta.accept = function(self) 219 | local cfd = lib.accept(self.fd) 220 | if cfd == -1 then return nil, true end 221 | local cl = setmetatable({}, sock_meta) 222 | cl:init(cfd) 223 | return cl 224 | end 225 | 226 | local function binder(ip, port) 227 | if ip == '*' then ip = '0.0.0.0' end 228 | local addr = ffi.new('struct sockaddr_in', { 229 | sin_family = 2, -- AF_INET 230 | sin_port = lib.htons(port), 231 | }) 232 | if lib.inet_pton(addr.sin_family, ip, addr.sin_addr) ~= 1 then 233 | error('Invalid IP specified') 234 | end 235 | local sock = setmetatable({}, sock_meta) 236 | sock:init(lib.socket(addr.sin_family, 1, 6)) 237 | sock:bind(addr) 238 | return sock 239 | end 240 | 241 | local function waitForLine(cl) 242 | local line, err 243 | local mark = os.clock() 244 | while not line do 245 | line, err = cl:receive('*l') 246 | if not line then 247 | if os.clock() - mark > 4 then 248 | error(err) 249 | end 250 | coroutine.yield() 251 | end 252 | end 253 | return line 254 | end 255 | 256 | local function reliableSend(cl, text) 257 | local err, from 258 | local mark = os.clock() 259 | 260 | repeat 261 | err, from = select(2, cl:send(text, from)) 262 | if err == 'closed' then 263 | return false 264 | elseif err == 'timeout' then 265 | if os.clock() - mark > 4 then 266 | error(err) 267 | end 268 | end 269 | if from then 270 | coroutine.yield() 271 | end 272 | until from == nil 273 | 274 | return true 275 | end 276 | 277 | function _M:newServer(ip, port, root) 278 | return setmetatable({ 279 | server = assert(binder(ip, port), 'Failed to bind port'), 280 | root = root or './www', 281 | no_io = false, 282 | clients = {}, 283 | vhs = {} 284 | }, self) 285 | end 286 | 287 | local valid = {["nil"] = true, ["function"] = true, ["string"] = true} 288 | function _M:registerVirtualHandler(path, func) 289 | assert(valid[type(func)]) 290 | self.vhs[path] = func 291 | end 292 | 293 | function _M:generateResponse(cl, code, headers, body) 294 | cl:receive('*a') -- Читаем остатки данных, если такие имеются 295 | local hint = httpcodes[code] or error(('Invalid HTTP code: %d'):format(code)) 296 | body = body ~= nil and tostring(body) or hint 297 | headers = headers or {} 298 | 299 | if not reliableSend(cl, ('HTTP/1.1 %d %s\r\n'):format(code, hint)) then 300 | return false 301 | end 302 | 303 | headers['Server'] = self.serverName 304 | headers['Content-Length'] = #body 305 | if not headers['Content-Type'] then 306 | headers['Content-Type'] = 'text/html' 307 | end 308 | for key, value in pairs(headers) do 309 | if not reliableSend(cl, ('%s: %s\r\n'):format(key, value)) then 310 | return false 311 | end 312 | end 313 | if not reliableSend(cl, '\r\n') then 314 | return false 315 | end 316 | 317 | return body and reliableSend(cl, body) or true 318 | end 319 | 320 | function _M:pushError(cl, err) 321 | return self:generateResponse(cl, 500, nil, err) 322 | end 323 | 324 | _M.processLuaBlock = function(block) 325 | local succ, ret = xpcall(load(block, 'LuaBlock', nil, getfenv()), debug.traceback) 326 | if not succ then error(ret)end 327 | return __BUFFER .. (ret ~= nil and tostring(ret) or "") 328 | end 329 | 330 | function _M:executeLuaBlocks(data, ctx) 331 | local env = setmetatable({ 332 | _CONTEXT = ctx, 333 | __BUFFER = '', 334 | 335 | os = {}, 336 | io = {} 337 | }, {__index = _G}) 338 | env.print = function(...) 339 | for i = 1, select('#', ...) do 340 | env.__BUFFER = env.__BUFFER .. tostring(select(i, ...)) 341 | end 342 | end 343 | env.io.write = env.print 344 | setfenv(self.processLuaBlock, env) 345 | data = data:gsub('%<%?lua(.-)%?%>', self.processLuaBlock) 346 | setfenv(self.processLuaBlock, _G) 347 | return data 348 | end 349 | 350 | function _M:processRequest(ctx) 351 | local req = ctx.request 352 | local vh = self.vhs[req.uri] 353 | if vh ~= nil then 354 | local data 355 | if type(vh) == 'function' then 356 | data = vh(self, ctx) 357 | else 358 | data = tostring(vh) 359 | end 360 | return ctx:unpack(data ~= ctx and data or nil) 361 | end 362 | 363 | if self.no_io == false then 364 | local path = self.root .. req.uri 365 | if path:find('/$') then 366 | path = path .. 'index.html' 367 | end 368 | 369 | local mime = require('mimetypes').guess(path) 370 | local file = io.open(path, 'rb') 371 | if file then 372 | local data = file:read('*a') 373 | if mime:find('^text/') then 374 | data = self:executeLuaBlocks(data, ctx) 375 | end 376 | file:close() 377 | ctx:setContentType(mime) 378 | return ctx:unpack(data) 379 | end 380 | end 381 | 382 | return 404 383 | end 384 | 385 | function _M:clientHandler(cl) 386 | local line 387 | local ctx = setmetatable({ 388 | client = cl, 389 | request = {}, 390 | reqheaders = {}, 391 | respcode = 200, 392 | respheaders = {} 393 | }, ctx_meta) 394 | 395 | -- Получаем заголовки 396 | line = waitForLine(cl) 397 | local r = ctx.request 398 | r.method, r.uri, r.version = line:match('(%w+)%s(/.*)%sHTTP/(.+)') 399 | if not r.method then 400 | return self:generateResponse(cl, 400, nil, 'Failed to parse request line') 401 | else 402 | r.method = r.method:upper() 403 | end 404 | 405 | if r.version ~= '1.1' and r.version ~= '1.0' then 406 | return self:generateResponse(cl, 505) 407 | end 408 | 409 | local twodots = r.uri:find('%.%.') 410 | if twodots then 411 | return self:generateResponse(cl, 400, nil, 'Prohibited URL') 412 | end 413 | 414 | local query_start = r.uri:find('%?') 415 | if query_start then 416 | local qstr = r.uri:sub(query_start + 1) 417 | r.uri = r.uri:sub(0, query_start - 1) 418 | if #qstr > 0 then 419 | local query 420 | for qprm in qstr:gmatch('([^&]+)') do 421 | local key, value = qprm:match('(.+)=(.*)') 422 | if not key then 423 | return self:generateResponse(cl, 400, nil, 'Malformed query string') 424 | end 425 | query = query or {} 426 | query[key] = tonumber(value)or value 427 | end 428 | r.query = query 429 | end 430 | end 431 | 432 | local h = ctx.reqheaders 433 | repeat 434 | line = waitForLine(cl) 435 | if #line > 0 then 436 | local key, value = line:match('(.+): (.+)') 437 | h[key:lower()] = tonumber(value) or value 438 | end 439 | until #line == 0 440 | 441 | local dleft = h['content-length'] 442 | if dleft then 443 | if no_body[r.method] == true then 444 | return self:generateResponse(cl, 400) 445 | end 446 | 447 | repeat 448 | local data = cl:receive(dleft) 449 | r.body = r.body and r.body..data or data 450 | dleft = dleft - #data 451 | until dleft == 0 452 | end 453 | 454 | return self:generateResponse(cl, self:processRequest(ctx)) 455 | end 456 | 457 | function _M:doStep() 458 | while true do 459 | local client, err = self.server:accept() 460 | if err ~= nil then 461 | if err == 'closed' then 462 | return false 463 | end 464 | 465 | break 466 | end 467 | self.clients[client] = coroutine.create(self.clientHandler) 468 | end 469 | 470 | for client, coro in pairs(self.clients) do 471 | local status = coroutine.status(coro) 472 | if status == 'suspended' then 473 | local succ, err = coroutine.resume(coro, self, client) 474 | if not succ then 475 | if not tostring(err):find('timeout$') then 476 | coro = coroutine.create(self.pushError) 477 | succ, err = coroutine.resume(coro, self, client, err) 478 | if not succ then print('Omfg, error in error handler', err)end 479 | self.clients[client] = coro 480 | end 481 | end 482 | elseif status == 'dead' then 483 | self.clients[client] = nil 484 | client:close() 485 | end 486 | end 487 | 488 | return true 489 | end 490 | 491 | function _M:startLoop() 492 | while self:doStep() do 493 | C.Sleep(10) 494 | end 495 | end 496 | 497 | function _M:disableIO(status) 498 | self.no_io = (status == true) 499 | end 500 | 501 | function _M:close() 502 | for cl in pairs(self.clients) do 503 | self.clients[cl] = nil 504 | cl:close() 505 | end 506 | self.server:close() 507 | collectgarbage() 508 | end 509 | 510 | function _M:getVersion() 511 | return self._VERSION 512 | end 513 | 514 | return _M 515 | -------------------------------------------------------------------------------- /examples/RCE.lua/README.md: -------------------------------------------------------------------------------- 1 | # Remote Code Execution example 2 | 3 | As soon as RCE loads, it starts the web server on [0.0.0.0:1337](https://127.0.0.1:1337/). When you open this page, you will see the code editor where you can execute Lua scripts in realtime. Note that the game **must not** be paused, otherwise the server will be unavailable! 4 | 5 | ## Editor hotkeys: 6 | 1. F5 - Execute code 7 | 2. Ctrl+R - Clear output window 8 | 3. Ctrl+S - Save session to localStorage 9 | 4. Ctrl+num`[0-9]` - Switch editor tab 10 | 11 | ## Credits 12 | 13 | This mod uses some thirdparty libraries such as [mimetypes](https://luarocks.org/modules/luarocks/mimetypes), [Ace Editor](https://ace.c9.io/) and [Toastify](https://github.com/apvarun/toastify-js) 14 | -------------------------------------------------------------------------------- /examples/RCE.lua/main.lua: -------------------------------------------------------------------------------- 1 | local t = {} 2 | local index_page = [[ 3 | 4 | 5 | 6 | Remote code execution 7 | 8 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 221 | 222 | 223 |
224 |
Exectuion output will appear here
225 | 226 | 227 | ]] 228 | 229 | function t.OnLoad() 230 | local server = require('svhttp'):newServer('*', 1337) 231 | local exec_env = setmetatable({ 232 | _SELF = server, 233 | io = { 234 | open = io.open 235 | }, 236 | os = {} 237 | }, { 238 | __index = _G 239 | }) 240 | exec_env.print = function(...) 241 | for i = 1, select('#', ...) do 242 | exec_env._BUF = exec_env._BUF .. tostring(select(i, ...)) 243 | end 244 | end 245 | exec_env.io.write = exec_env.print 246 | 247 | server:disableIO(true) 248 | server:registerVirtualHandler('/', index_page) 249 | server:registerVirtualHandler('/api', function(_, ctx) return ctx:setCode(403) end) 250 | server:registerVirtualHandler('/api/execute', function(_, ctx) 251 | if not ctx:isPOST() then 252 | return ctx:setCode(405) 253 | end 254 | 255 | local mime, data = ctx:getRequestBody() 256 | if mime == 'text/x-lua' and data ~= nil then 257 | exec_env._BUF = '' 258 | local chunk, err = load(data, 'api.execute', 't', exec_env) 259 | if chunk ~= nil then 260 | local succ, ret = pcall(chunk) 261 | ctx:setCode(succ and 200 or 500) 262 | return succ and (ret ~= nil and (exec_env._BUF .. tostring(ret)) or exec_env._BUF) or ret 263 | end 264 | 265 | ctx:setCode(500) 266 | return tostring(err) 267 | end 268 | 269 | return ctx:setCode(415) 270 | end) 271 | 272 | t.server = server 273 | end 274 | 275 | function t.OnTick() 276 | t.server:doStep() 277 | end 278 | 279 | function t.OnStop() 280 | t.server:close() 281 | end 282 | 283 | t.OnReload = t.OnStop 284 | 285 | if ... then 286 | return t 287 | else 288 | _STOP = false 289 | package.path = '.\\Libs\\?.lua' 290 | package.cpath = '.\\Libs\\C\\?.dll' 291 | t.OnLoad() 292 | while not _STOP do 293 | t.OnTick() 294 | end 295 | end 296 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to examples folder! 2 | 3 | This folder contains examples of RedLua's scripts. If you want to try some of these, just copy desired example to `\RedLua\Scripts`. **Note that if the example is a folder, you need to copy the whole folder!** 4 | -------------------------------------------------------------------------------- /examples/ScriptMenu.lua: -------------------------------------------------------------------------------- 1 | local t = {} 2 | 3 | function t.OnLoad() 4 | lang.install { 5 | en = { 6 | ['example.mystr'] = 'My localized button' 7 | }, 8 | ru = { 9 | ['example.mystr'] = 'Моя локализированная кнопка' 10 | } 11 | } 12 | end 13 | 14 | function t.OnTick(buildMenu) 15 | -- Menu builder MUST be here 16 | if buildMenu then 17 | menu.set { 18 | title = 'My menu', 19 | items = { 20 | { 21 | type = 1, 22 | title = lang.get 'example.mystr' 23 | }, 24 | { 25 | type = LUAMENU_ITEM_BUTTON, 26 | title = 'My button', 27 | onclick = function() 28 | print('Hello!') 29 | end 30 | }, 31 | { 32 | type = LUAMENU_ITEM_SWITCHABLE, 33 | title = 'My switchable', 34 | onclick = function(state) 35 | return not state 36 | end 37 | }, 38 | { 39 | type = LUAMENU_ITEM_MENU, 40 | islist = true, 41 | title = 'My sublist', 42 | menu = { 43 | title = 'My submenu', 44 | items = { 45 | { 46 | type = 1, 47 | title = 'My subbutton', 48 | onclick = function() end 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | end 56 | end 57 | 58 | function t.OnStop() 59 | menu.remove() 60 | end 61 | 62 | t.OnReload = t.OnStop 63 | 64 | return t 65 | -------------------------------------------------------------------------------- /misc/Langs/en.lng: -------------------------------------------------------------------------------- 1 | English 2 | 0 3 | 4 | # Main menu 5 | core.main.scripts=Scripts 6 | core.main.refr=Refresh scripts list 7 | core.main.setts=Settings 8 | core.main.about=About RedLua 9 | 10 | # Settings 11 | core.setts.hotkey=Change menu hotkey 12 | core.setts.autorun=Autorun feature enabled 13 | core.setts.updater=Check for updates at startup 14 | core.setts.chkupd=Check for updates 15 | core.setts.langs=Change language 16 | core.setts.rldndb=Reload NativeDB 17 | core.setts.pos=Change menu position 18 | core.setts.toggall=Toggle all scripts 19 | core.setts.relall=Reload all scripts 20 | core.setts.unlall=Unload all scripts 21 | 22 | core.setts.nfy.hksc=Menu hotkey changed to %s 23 | core.setts.nfy.hkfl=Unknown keyname specified %s 24 | core.setts.nfy.hkin=Unknown keycode specified %d 25 | core.setts.nfy.srlsc=Script list has been successfully updated 26 | core.setts.nfy.srlfl=Failed to update script list 27 | core.setts.nfy.nrlsc=NativeDB reloaded successfully! 28 | core.setts.nfy.nrlfl=Failed to reload NativeDB, error code: %d 29 | core.setts.nfy.runall=All scripts have been resumed 30 | core.setts.nfy.stpall=All scripts have been suspended 31 | core.setts.nfy.relall=All scripts have been reloaded 32 | core.setts.nfy.unlall=All scripts have been unloaded 33 | 34 | # Scripts list 35 | core.scripts.nf=Scripts not found 36 | 37 | # Script manager 38 | core.script.state=Status: %s 39 | core.script.state1=running 40 | core.script.state2=error 41 | core.script.state3=disabled 42 | core.script.usage=Usage: %.3f KB 43 | core.script.rvlusage=[press to show RAM usage] 44 | core.script.ownmenu=Script menu 45 | core.script.tgl=Toggle 46 | core.script.rel=Reload 47 | core.script.unl=Unload 48 | 49 | core.script.nfy.relsc=Script successfully reloaded 50 | core.script.nfy.relfl=Script failed to reload 51 | core.script.nfy.unlsc=Script successfully unloaded 52 | 53 | # Updater 54 | core.chkupd.rl=Check for RedLua updates 55 | core.chkupd.ndb=Check for NativeDB updates 56 | core.chkupd.nfy.noup=No updates found 57 | core.chkupd.nfy.upfl=Updater failed, error code: %d 58 | core.updalert.nfn=New version %s found 59 | core.updalert.btn=Go to release page 60 | 61 | # Выбор языка 62 | core.langs.ingame=Ingame language 63 | core.langs.nfy.chsc=Language changed successfully 64 | 65 | # Position 66 | core.pos.left=Left 67 | core.pos.center=Center 68 | core.pos.right=Right 69 | 70 | # Notifications 71 | core.nfy.redir=Link will be opened in your default browser in a few moments... 72 | -------------------------------------------------------------------------------- /misc/Langs/ru.lng: -------------------------------------------------------------------------------- 1 | Русский 2 | 7 3 | 4 | # Главное меню 5 | core.main.scripts=Скрипты 6 | core.main.refr=Обновить список скриптов 7 | core.main.setts=Настройки 8 | core.main.about=О RedLua 9 | 10 | # Настройки 11 | core.setts.hotkey=Изменить бинд меню 12 | core.setts.autorun=Автозагрузка скриптов 13 | core.setts.updater=Автообновление библиотеки 14 | core.setts.chkupd=Проверить обновления 15 | core.setts.langs=Изменить язык 16 | core.setts.rldndb=Перезагрузить NativeDB 17 | core.setts.pos=Расположение меню 18 | core.setts.toggall=Вкл/выкл все скрипты 19 | core.setts.relall=Перезагрузить все скрипты 20 | core.setts.unlall=Выгрузить все скрипты 21 | 22 | core.setts.nfy.hksc=Бинд меню изменён на %s 23 | core.setts.nfy.hkfl=Неизвестная кнопка %s 24 | core.setts.nfy.hkin=Введён недопустимый код кнопки %d 25 | core.setts.nfy.srlsc=Список скриптов успешно обновлён 26 | core.setts.nfy.srlfl=Не удалось обновить список скриптов 27 | core.setts.nfy.nrlsc=NativeDB успешно перезагружена 28 | core.setts.nfy.nrlfl=Не удалось перезагрузить NativeDB: %d 29 | core.setts.nfy.runall=Выполнение всех скриптов возобновлено 30 | core.setts.nfy.stpall=Выполнение всех скриптов приостановлено 31 | core.setts.nfy.relall=Все скрпиты перезагружены 32 | core.setts.nfy.unlall=Все скрпиты выгружены 33 | 34 | # Список скриптов 35 | core.scripts.nf=Скрипты не найдены 36 | 37 | # Управление скриптом 38 | core.script.state=Состояние: %s 39 | core.script.state1=запущен 40 | core.script.state2=ошибка 41 | core.script.state3=остановлен 42 | core.script.usage=Исп. ОЗУ: %.3f КБ 43 | core.script.rvlusage=[Показать исп. ОЗУ] 44 | core.script.ownmenu=Меню скрипта 45 | core.script.tgl=Вкл/выкл 46 | core.script.rel=Перезагрузить 47 | core.script.unl=Выгрузить 48 | 49 | core.script.nfy.relsc=Скрипт успешно перезагружен 50 | core.script.nfy.relfl=Не удалось перезапустить скрипт 51 | core.script.nfy.unlsc=Скрипт выгружен успешно 52 | 53 | # Проверка обновлений 54 | core.chkupd.rl=Обновить RedLua 55 | core.chkupd.ndb=Обновить NativeDB 56 | core.chkupd.nfy.noup=Обновления не найдены 57 | core.chkupd.nfy.upfl=Поризошла ошибка при обновлении: %d 58 | core.updalert.nfn=Новая версия %s 59 | core.updalert.btn=Открыть страницу загрузки 60 | 61 | # Выбор языка 62 | core.langs.ingame=Язык игры 63 | core.langs.nfy.chsc=Язык успешно изменён 64 | 65 | # Расположение меню 66 | core.pos.left=Слева 67 | core.pos.center=По центру 68 | core.pos.right=Справа 69 | 70 | # Уведомления 71 | core.nfy.redir=Выбранная ссылка скоро будет открыта в вашем браузере 72 | -------------------------------------------------------------------------------- /misc/Pack.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop"; 2 | 3 | $CODES = @{ 4 | "RDR2" = "rdr3" 5 | "V" = "gta5" 6 | } 7 | 8 | if ($args.Count -gt 0) { 9 | $VER = $args[0] 10 | } else { 11 | $VER = "unknown" 12 | } 13 | 14 | Foreach($item in @("RDR2", "V")) { 15 | New-Item -Path ".\objs\output$($item)\" -Name "RedLua" -ItemType "Directory" -ErrorAction "SilentlyContinue" 16 | Copy-Item -Path ".\misc\Langs\" -Destination ".\objs\output$($item)\RedLua\" -Container -Recurse -Force 17 | Copy-Item ".\README.md" ".\objs\output$($item)\RedLua\" 18 | Copy-Item ".\LICENSE" ".\objs\output$($item)\RedLua\" 19 | CURL.EXE "-o.\objs\output$($item)\RedLua\natives.json" "https://raw.githubusercontent.com/alloc8or/$($CODES[$item])-nativedb-data/master/natives.json" 20 | 7Z.EXE "a" "-tzip" ".\objs\RedLua$($item)-$VER.zip" ".\objs\output$($item)\*" "-r" "-x!*.ilk" "-x!vc*.pdb" "-x!*.exp" "-x!*.lib" 21 | } 22 | -------------------------------------------------------------------------------- /misc/ScriptHook.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop"; 2 | 3 | $SCR_DIR = $(Split-Path $MyInvocation.MyCommand.Path -Parent) 4 | if ($args[0] -eq "gtav") { 5 | $VARIANT = "V" 6 | $LINK = "/gtav/scripthookv/" 7 | } else { 8 | $VARIANT = "RDR2" 9 | $LINK = "/rdr2/scripthookrdr2/" 10 | } 11 | 12 | $SHOOK_DIR = "$SCR_DIR/../src/thirdparty/ScriptHook$($VARIANT)" 13 | $DOMAIN = "http://www.dev-c.com" 14 | $FILES = @( 15 | "inc/", "lib/", 16 | "inc/enums.h", "inc/main.h", "inc/nativeCaller.h", 17 | "inc/types.h", "readme.txt", "lib/ScriptHook$($VARIANT).lib" 18 | ) 19 | 20 | if (Test-Path -PathType Container $SHOOK_DIR) { 21 | $corrupted = $false 22 | 23 | Foreach($item in $FILES) { 24 | if (!(Test-Path "$SHOOK_DIR/$item")) { 25 | $corrupted = $true; 26 | break 27 | } 28 | } 29 | if ($corrupted -eq $false) { 30 | Exit 0 31 | } 32 | 33 | $title = "Corrupted ScriptHook$($VARIANT) installation" 34 | $text = "Looks like you have a broken ScriptHook$($VARIANT), do you want to redownload it?" 35 | $choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription] 36 | $choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList "&Yes", "ScriptHook$($VARIANT) will be redownloaded")) 37 | $choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList "&No", "This script will close")) 38 | switch ($Host.UI.PromptForChoice($title, $text, $choices, 1)) { 39 | 0 { 40 | Remove-Item -Recurse -Force $SHOOK_DIR 41 | } 42 | 1 { 43 | Write-Host "Operation cancelled" 44 | Exit 0 45 | } 46 | } 47 | } 48 | 49 | try { 50 | $HEADERS = @{ 51 | "Referer" = ($DOMAIN + $LINK); 52 | "User-Agent" = "RL/1.0" 53 | } 54 | 55 | $RESP = Invoke-WebRequest -URI ($DOMAIN + $LINK) 56 | } catch { 57 | Write-Warning "Failed to download SDK from the AB's site, falling back to own server..." 58 | $HEADERS = @{ 59 | "User-Agent" = "RL/1.0" 60 | } 61 | 62 | $DOMAIN = "https://igvx.ru" 63 | $RESP = Invoke-WebRequest -URI ($DOMAIN + "/shsdk/") 64 | } 65 | 66 | Foreach($elem in ($RESP).Links.Href) { 67 | if ($elem.IndexOf("ScriptHook$($VARIANT)_SDK") -ige 0) { 68 | $outFile = "$SCR_DIR/temp.zip" 69 | Invoke-WebRequest -Uri ($DOMAIN + $elem) -OutFile $outFile -Headers $HEADERS 70 | Add-Type -Assembly System.IO.Compression.FileSystem 71 | $zip = [IO.Compression.ZipFile]::OpenRead($outFile) 72 | New-Item -ItemType Directory -Path $SHOOK_DIR 73 | Foreach($ent in $zip.Entries) { 74 | $fileName = $ent.FullName 75 | if ($FILES.Contains($fileName)) { 76 | if ($fileName.Substring($fileName.Length - 1) -eq '/') { 77 | New-Item -ItemType Directory -Path "$SHOOK_DIR\$filename" 78 | Continue 79 | } 80 | [IO.Compression.ZipFileExtensions]::ExtractToFile($ent, "$SHOOK_DIR\$fileName", $true) 81 | } 82 | } 83 | $zip.Dispose() 84 | Remove-Item -Path $outFile 85 | Exit 0 86 | } 87 | } 88 | 89 | Write-Error "SDK link not found" 90 | -------------------------------------------------------------------------------- /src/base.cpp: -------------------------------------------------------------------------------- 1 | #include "base.hpp" 2 | #include "luascript.hpp" 3 | #include "nativedb.hpp" 4 | #include "settingsctl.hpp" 5 | #include "updatesctl.hpp" 6 | #include "menus\main.hpp" 7 | #include "menus\updalert.hpp" 8 | #include "constants.hpp" 9 | #include "langctl.hpp" 10 | 11 | #include "thirdparty\easyloggingpp.h" 12 | #include 13 | #include 14 | 15 | std::map Scripts {}; 16 | static BOOL HasConsole = false; 17 | 18 | bool RedLuaScanLangs(void) { 19 | LOG(INFO) << "Searching for lng files..."; 20 | WIN32_FIND_DATA findData; 21 | HANDLE hFind = FindFirstFile(REDLUA_LANGS_DIR "*.lng", &findData); 22 | if (hFind == INVALID_HANDLE_VALUE) return false; 23 | std::string currLang = "en"; 24 | Settings.Read("menu_language", currLang); 25 | 26 | do { 27 | if (findData.dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY) { 28 | std::string lngCode = findData.cFileName; 29 | auto len = lngCode.length(); 30 | if (len > 4) { 31 | lngCode.erase(lngCode.length() - 4); 32 | Lng.Load(lngCode); 33 | } 34 | } 35 | } while (FindNextFile(hFind, &findData)); 36 | 37 | Lng.Change(currLang); 38 | return true; 39 | } 40 | 41 | bool RedLuaScanScripts(void) { 42 | LOG(INFO) << "Searching for new scripts..."; 43 | WIN32_FIND_DATA findData; 44 | std::string scpath = REDLUA_SCRIPTS_DIR; 45 | HANDLE hFind = FindFirstFile((scpath + "*.lua").c_str(), &findData); 46 | if (hFind == INVALID_HANDLE_VALUE) return false; 47 | bool autorun = Settings.Read("autorun", true); 48 | 49 | do { 50 | if (Scripts[findData.cFileName]) continue; 51 | LuaScript *script; 52 | if (findData.dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY) 53 | script = new LuaScript(scpath, findData.cFileName); 54 | else 55 | script = new LuaScript(scpath + findData.cFileName, "main.lua", true); 56 | Scripts[findData.cFileName] = script; 57 | if (autorun) script->Load(); 58 | else LOG(WARNING) << "Script " << script->GetPath() << " found but not loaded (autorun disabled)"; 59 | } while (FindNextFile(hFind, &findData)); 60 | 61 | FindClose(hFind); 62 | return true; 63 | } 64 | 65 | void RedLuaMain(void) { 66 | el::Configurations conf (REDLUA_LOGCONF_FILE); 67 | el::Loggers::reconfigureLogger("default", conf); 68 | el::base::TypedConfigurations *logger; 69 | logger = el::Loggers::getLogger("default")->typedConfigurations(); 70 | if (logger->enabled(el::Level::Global) && logger->toStandardOutput(el::Level::Global)) { 71 | if (AttachConsole(ATTACH_PARENT_PROCESS) || (HasConsole = AllocConsole())) { 72 | if (HasConsole) SetConsoleTitle(REDLUA_NAME " debug console"); 73 | freopen("CONOUT$", "w", stdout); 74 | freopen("CONOUT$", "w", stderr); 75 | } 76 | } 77 | 78 | LOG(INFO) << "Logger initialized"; 79 | auto menuController = new MenuController(); 80 | menuController->SetCurrentPosition( 81 | Settings.Read("menu_position", 0) 82 | ); 83 | LOG(DEBUG) << REDLUA_NAME " menu initialized"; 84 | if (Settings.Read("auto_updates", false)) { 85 | LOG(DEBUG) << "Starting updates checker..."; 86 | std::string nevwer; 87 | if (auto code = UpdatesCtl.CheckRedLua(nevwer)) { 88 | if (code != UpdatesController::ERR_NO_UPDATES) 89 | LOG(ERROR) << "RedLua updater failed: " << code; 90 | } else 91 | CreateUpdateAlert(menuController, nevwer); 92 | 93 | if (auto code = UpdatesCtl.CheckNativeDB()) { 94 | if (code != UpdatesController::ERR_NO_UPDATES) 95 | LOG(ERROR) << "NativeDB updater failed: " << code; 96 | } else 97 | LOG(INFO) << "NativeDB updated successfully"; 98 | LOG(DEBUG) << "Updates checker finished"; 99 | } 100 | 101 | if (Natives.GetMethodCount() == 0) 102 | if (auto code = Natives.Load()) 103 | LOG(ERROR) << "Failed to load " REDLUA_NATIVES_FILE ": " << code; 104 | 105 | RedLuaScanLangs(); 106 | RedLuaScanScripts(); 107 | MenuBase *mainMenu = nullptr; 108 | bool needRebuild; 109 | 110 | while (true) { 111 | if (needRebuild = menuController->IsRebuildRequested()) { 112 | menuController->UnregisterAll(); 113 | mainMenu = CreateMainMenu(menuController); 114 | } 115 | 116 | if (MenuInput::MenuSwitchPressed()) { 117 | MenuInput::MenuInputBeep(); 118 | if (menuController->HasActiveMenu()) 119 | menuController->PopMenu(0); 120 | else 121 | menuController->PushMenu(mainMenu); 122 | } 123 | 124 | menuController->Update(); 125 | for (auto &s : Scripts) 126 | s.second->OnTick(needRebuild); 127 | 128 | if (needRebuild) 129 | menuController->RebuildDone(); 130 | 131 | WAIT(0); 132 | } 133 | } 134 | 135 | void RedLuaFinish(void) { 136 | for (auto it = Scripts.begin(); it != Scripts.end();) { 137 | delete it->second; 138 | it = Scripts.erase(it); 139 | } 140 | Settings.Save(); 141 | UpdatesCtl.Stop(); 142 | LOG(INFO) << "RedLua stopped"; 143 | fclose(stderr); fclose(stdout); 144 | if (HasConsole && !FreeConsole()) 145 | LOG(ERROR) << "FreeConsole() failed: " << GetLastError(); 146 | } 147 | -------------------------------------------------------------------------------- /src/base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "luascript.hpp" 4 | 5 | #include 6 | 7 | extern std::map Scripts; 8 | 9 | void RedLuaMain(void); 10 | void RedLuaFinish(void); 11 | bool RedLuaScanScripts(void); 12 | -------------------------------------------------------------------------------- /src/constants.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define REDLUA_NAME "RedLua" 4 | #define REDLUA_VERSION_MAJOR 0 5 | #define REDLUA_VERSION_MINOR 6 6 | #define REDLUA_VERSION_PATCH 5 7 | 8 | #define _VTSH(x) #x 9 | #define _VERTOSTR(MA, MI, PA) "v" _VTSH(MA) "." _VTSH(MI) "." _VTSH(PA) 10 | #define REDLUA_VERSION _VERTOSTR(REDLUA_VERSION_MAJOR, \ 11 | REDLUA_VERSION_MINOR, REDLUA_VERSION_PATCH) 12 | #define REDLUA_VERSION_NUM (REDLUA_VERSION_MAJOR * 100) + \ 13 | (REDLUA_VERSION_MINOR * 10) + REDLUA_VERSION_PATCH 14 | #define REDLUA_FULLNAME REDLUA_NAME " " REDLUA_VERSION 15 | 16 | #if defined(REDLUA_GTAV) 17 | # define REDLUA_GAMECODE "gta5" 18 | # define REDLUA_HOTKEY_DEFAULT 0x73 19 | #elif defined(REDLUA_RDR3) 20 | # define REDLUA_GAMECODE "rdr3" 21 | # define REDLUA_HOTKEY_DEFAULT 0x76 22 | #endif 23 | 24 | #define REDLUA_TAGS_URL "https://api.github.com/repos/igor725/RedLua/tags" 25 | #define REDLUA_RELS_URL "https://github.com/igor725/RedLua/releases/tag/" 26 | #define REDLUA_NATIVEDB_URL "https://raw.githubusercontent.com/alloc8or/" REDLUA_GAMECODE "-nativedb-data/master/natives.json" 27 | 28 | #define REDLUA_ROOT_DIR ".\\RedLua\\" 29 | #define REDLUA_SCRIPTS_DIR REDLUA_ROOT_DIR "Scripts\\" 30 | #define REDLUA_LANGS_DIR REDLUA_ROOT_DIR "Langs\\" 31 | #define REDLUA_LIBS_DIR "Libs\\" 32 | #define REDLUA_CLIBS_DIR "Libs\\C\\" 33 | 34 | #define REDLUA_NATIVES_FILE REDLUA_ROOT_DIR "Natives.json" 35 | #define REDLUA_SETTINGS_FILE REDLUA_ROOT_DIR "Settings.json" 36 | #define REDLUA_LOGCONF_FILE REDLUA_ROOT_DIR "Log.conf" 37 | 38 | #define REDLUA_LPATH1 REDLUA_LIBS_DIR "?.lua;" 39 | #define REDLUA_LPATH2 REDLUA_LIBS_DIR "?\\init.lua" 40 | 41 | #define REDLUA_CPATH1 REDLUA_CLIBS_DIR "?.dll;" 42 | #define REDLUA_CPATH2 REDLUA_CLIBS_DIR "?\\core.dll" 43 | 44 | #define REDLUA_PATHS REDLUA_ROOT_DIR REDLUA_LPATH1 REDLUA_ROOT_DIR REDLUA_LPATH2 45 | #define REDLUA_CPATHS REDLUA_ROOT_DIR REDLUA_CPATH1 REDLUA_ROOT_DIR REDLUA_CPATH2 46 | -------------------------------------------------------------------------------- /src/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include "dllmain.hpp" 2 | #include "base.hpp" 3 | #include "constants.hpp" 4 | 5 | #include "thirdparty\keyboard.h" 6 | #include "scripthook.hpp" 7 | BOOL registred = FALSE; 8 | 9 | BOOL DllMain(HMODULE hInstance, DWORD dwReason, LPVOID lpReserved) { 10 | #ifndef REDLUA_STANDALONE 11 | switch (dwReason) { 12 | case DLL_PROCESS_ATTACH: 13 | tryagain: 14 | if (!EnsureDirectory(REDLUA_ROOT_DIR) 15 | || !EnsureDirectory(REDLUA_SCRIPTS_DIR) 16 | || !EnsureDirectory(REDLUA_LANGS_DIR) 17 | || !EnsureDirectory(REDLUA_LIBS_DIR) 18 | || !EnsureDirectory(REDLUA_CLIBS_DIR)) { 19 | switch (MessageBox(NULL, "Failed to create RedLua " 20 | "directory, please set write permissions " 21 | "to the root folder of the game then press " 22 | "\"Continue\".", REDLUA_FULLNAME, 23 | MB_ICONERROR | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2 24 | )) { 25 | case IDCANCEL: 26 | ExitProcess(ERROR_ACCESS_DENIED); 27 | return FALSE; 28 | case IDTRYAGAIN: 29 | goto tryagain; 30 | case IDCONTINUE: 31 | return FALSE; 32 | } 33 | } 34 | 35 | registred = TRUE; 36 | scriptRegister(hInstance, RedLuaMain); 37 | keyboardHandlerRegister(OnKeyboardMessage); 38 | break; 39 | case DLL_PROCESS_DETACH: 40 | if (registred) { 41 | scriptUnregister(hInstance); 42 | keyboardHandlerUnregister(OnKeyboardMessage); 43 | RedLuaFinish(); 44 | } 45 | break; 46 | } 47 | #else 48 | switch (dwReason) { 49 | case DLL_PROCESS_ATTACH: 50 | RedLuaMain(); 51 | break; 52 | case DLL_PROCESS_DETACH: 53 | RedLuaFinish(); 54 | break; 55 | } 56 | #endif 57 | 58 | return TRUE; 59 | } 60 | -------------------------------------------------------------------------------- /src/dllmain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "thirdparty\easyloggingpp.h" 5 | 6 | INITIALIZE_EASYLOGGINGPP; 7 | #define EnsureDirectory(D) (!CreateDirectory(D, NULL) ? ERROR_ALREADY_EXISTS == GetLastError() : true) 8 | BOOL APIENTRY DllMain(HMODULE hInstance, DWORD dwReason, LPVOID lpReserved); 9 | -------------------------------------------------------------------------------- /src/emu/native.cpp: -------------------------------------------------------------------------------- 1 | #include "emu\native.hpp" 2 | #include "thirdparty\easyloggingpp.h" 3 | #include "thirdparty\keyboard.h" 4 | #include 5 | 6 | struct KeyEvent { 7 | DWORD key; 8 | WORD repeats; 9 | BYTE scanCode; 10 | BOOL isExtended; 11 | BOOL isWithAlt; 12 | BOOL wasDownBefore; 13 | BOOL isUpNow; 14 | }; 15 | 16 | static std::vector VirtualPress {}; 17 | 18 | void emu_scriptWait(DWORD ms) { 19 | Sleep(100); 20 | if (VirtualPress.size() > 0) { 21 | auto &ke = VirtualPress.back(); 22 | OnKeyboardMessage(ke.key, ke.repeats, ke.scanCode, 23 | ke.isExtended, ke.isWithAlt, ke.wasDownBefore, ke.isUpNow); 24 | VirtualPress.pop_back(); 25 | } 26 | } 27 | 28 | void emu_nativeInit(UINT64 hash) { 29 | LOG(DEBUG) << "[NC] START " << (void *)hash; 30 | } 31 | 32 | void emu_nativePush64(UINT64 val) { 33 | LOG(DEBUG) << "\t [NC] Argument: " << (void *)val; 34 | } 35 | 36 | static struct _Ret { 37 | int _pad[6]; 38 | } ret; 39 | 40 | PUINT64 emu_nativeCall(void) { 41 | LOG(DEBUG) << "[NC] END"; 42 | return (PUINT64)&ret; 43 | } 44 | -------------------------------------------------------------------------------- /src/emu/native.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define WAIT emu_scriptWait 6 | #define nativeInit emu_nativeInit 7 | #define nativePush64 emu_nativePush64 8 | #define nativeCall emu_nativeCall 9 | 10 | void emu_scriptWait(DWORD ms); 11 | void emu_nativeInit(UINT64 hash); 12 | void emu_nativePush64(UINT64 val); 13 | PUINT64 emu_nativeCall(void); 14 | -------------------------------------------------------------------------------- /src/json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define JSON_SKIP_UNSUPPORTED_COMPILER_CHECK 4 | #define JSON_SKIP_LIBRARY_VERSION_CHECK 5 | #include "thirdparty\json.hpp" 6 | using json = nlohmann::json; 7 | -------------------------------------------------------------------------------- /src/langctl.cpp: -------------------------------------------------------------------------------- 1 | #include "langctl.hpp" 2 | #include "constants.hpp" 3 | #include "natives.hpp" 4 | 5 | LangCtl Lng; 6 | 7 | std::string LangCtl::Get(std::string code) { 8 | if (auto localLang = m_currLang) { 9 | lngr_tryagain: 10 | for (auto &it : localLang->f_map) 11 | if (it.first == code) return it.second; 12 | 13 | if (localLang != m_defaultLang) { 14 | localLang = m_defaultLang; 15 | goto lngr_tryagain; 16 | } 17 | } 18 | 19 | return code; 20 | } 21 | 22 | bool LangCtl::Load(std::string lngcode) { 23 | std::ifstream lfile (REDLUA_LANGS_DIR + lngcode + ".lng"); 24 | if (!lfile.is_open()) return false; 25 | 26 | LangMap &lm = m_lngMap[lngcode]; 27 | if (!std::getline(lfile, lm.f_locName) || !lm.f_locName.length()) { 28 | m_lngMap.erase(lngcode); 29 | lfile.close(); 30 | return false; 31 | } 32 | lfile >> lm.f_langCode; 33 | 34 | for (std::string line; std::getline(lfile, line);) { 35 | if (line.length() > 0 && line.at(0) != '#') { 36 | std::stringstream ls(line); 37 | std::string lncode; 38 | if (std::getline(ls, lncode, '=')) { 39 | std::getline(ls, lm.f_map[lncode]); 40 | continue; 41 | } 42 | 43 | return false; 44 | } 45 | } 46 | 47 | if (lngcode == "en" || !m_defaultLang) 48 | m_defaultLang = &lm; 49 | 50 | return true; 51 | } 52 | 53 | void LangCtl::Change(std::string lng) { 54 | m_currLang = nullptr; 55 | if (lng == "ingame") { 56 | for (auto &it : m_lngMap) { 57 | int code = NATIVES::GET_LOCALE(); 58 | if (it.second.f_langCode == code) { 59 | m_currLang = &it.second; 60 | break; 61 | } 62 | } 63 | } else { 64 | for (auto &it : m_lngMap) { 65 | if (it.first == lng) { 66 | m_currLang = &it.second; 67 | break; 68 | } 69 | } 70 | } 71 | } 72 | 73 | LangsMap &LangCtl::GetMap(void) { 74 | return m_lngMap; 75 | } 76 | -------------------------------------------------------------------------------- /src/langctl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct { 9 | int f_langCode; 10 | std::string f_locName; 11 | std::map f_map; 12 | } LangMap; 13 | 14 | typedef std::map LangsMap; 15 | 16 | class LangCtl { 17 | LangsMap m_lngMap; 18 | LangMap *m_currLang, 19 | *m_defaultLang; 20 | 21 | public: 22 | LangCtl() : m_lngMap({}), m_currLang(nullptr), m_defaultLang(nullptr) {}; 23 | 24 | template 25 | std::string Get(std::string code, Args... ar) { 26 | if (auto localLang = m_currLang) { 27 | lng_tryagain: 28 | for (auto &it : localLang->f_map) { 29 | if (it.first == code) { 30 | auto fmt = it.second.c_str(); 31 | int need = std::snprintf(nullptr, 0, fmt, ar...); 32 | if (need <= 0) return code; 33 | auto needsz = static_cast(need); 34 | std::unique_ptr b (new char[needsz + 1]); 35 | std::snprintf(b.get(), needsz + 1, fmt, ar...); 36 | return std::string{b.get(), needsz}; 37 | } 38 | } 39 | 40 | if (localLang != m_defaultLang) { 41 | localLang = m_defaultLang; 42 | goto lng_tryagain; 43 | } 44 | } 45 | 46 | return code; 47 | } 48 | 49 | std::string Get(std::string code); 50 | 51 | bool Load(std::string lngcode); 52 | 53 | void Change(std::string lng); 54 | 55 | LangsMap &GetMap(void); 56 | }; 57 | 58 | extern LangCtl Lng; 59 | -------------------------------------------------------------------------------- /src/lualang.cpp: -------------------------------------------------------------------------------- 1 | #include "lualang.hpp" 2 | #include "langctl.hpp" 3 | 4 | static int lang_install(lua_State *L) { 5 | luaL_checktype(L, 1, LUA_TTABLE); 6 | LangsMap &map = Lng.GetMap(); 7 | 8 | lua_pushnil(L); 9 | while (lua_next(L, 1)) { 10 | if (lua_istable(L, -1) && lua_isstring(L, -2)) { 11 | auto ln = map.find(lua_tostring(L, -2)); 12 | if (ln != map.end()) { 13 | lua_pushnil(L); 14 | while (lua_next(L, -2)) { 15 | if (lua_isstring(L, -1) && lua_isstring(L, -2)) 16 | ln->second.f_map[lua_tostring(L, -2)] = lua_tostring(L, -1); 17 | 18 | lua_pop(L, 1); 19 | } 20 | } 21 | } 22 | 23 | lua_pop(L, 1); 24 | } 25 | 26 | return 0; 27 | } 28 | 29 | static int lang_get(lua_State *L) { 30 | lua_pushstring(L, Lng.Get(luaL_checkstring(L, 1)).c_str()); 31 | return 1; 32 | } 33 | 34 | static const luaL_Reg langlib[] = { 35 | {"install", lang_install}, 36 | {"get", lang_get}, 37 | 38 | {NULL, NULL} 39 | }; 40 | 41 | int luaopen_lang(lua_State *L) { 42 | luaL_newlib(L, langlib); 43 | return 1; 44 | } 45 | -------------------------------------------------------------------------------- /src/lualang.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\LuaJIT\src\lua.hpp" 4 | 5 | #ifdef REDLUA_STANDALONE 6 | #define luaopen_lang luaopen_RedLua_lang 7 | 8 | extern "C" { 9 | __declspec(dllexport) 10 | #endif 11 | int luaopen_lang(lua_State *L); 12 | #ifdef REDLUA_STANDALONE 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /src/lualog.cpp: -------------------------------------------------------------------------------- 1 | #include "lualog.hpp" 2 | 3 | #include "thirdparty\easyloggingpp.h" 4 | #include 5 | 6 | static int tocppstring(lua_State *L, std::string &logstr) { 7 | char pointer[20]; 8 | lua_Number tempd; 9 | int tempi; 10 | 11 | for (int i = 1; i <= lua_gettop(L); i++) { 12 | switch (lua_type(L, i)) { 13 | case LUA_TNIL: 14 | logstr.append("nil"); 15 | break; 16 | case LUA_TBOOLEAN: 17 | logstr.append(lua_toboolean(L, i) ? "true" : "false"); 18 | break; 19 | case LUA_TNUMBER: 20 | tempd = lua_tonumber(L, i); 21 | tempi = (int)tempd; 22 | if (tempi == tempd) 23 | logstr.append(std::to_string(tempi)); 24 | else 25 | logstr.append(std::to_string(tempd)); 26 | break; 27 | case LUA_TSTRING: 28 | logstr.append(lua_tostring(L, i)); 29 | break; 30 | default: 31 | if (luaL_callmeta(L, i, "__tostring")) { 32 | if (!lua_isstring(L, -1)) 33 | luaL_error(L, "'__tostring' must return a string"); 34 | logstr.append(lua_tostring(L, -1)); 35 | lua_pop(L, 1); 36 | } else { 37 | logstr.append(luaL_typename(L, i)); 38 | std::snprintf(pointer, 20, ": %p", lua_topointer(L, i)); 39 | logstr.append(pointer); 40 | } 41 | break; 42 | } 43 | 44 | logstr.append(", "); 45 | } 46 | 47 | if (!logstr.empty()) 48 | (logstr.pop_back(), logstr.pop_back()); 49 | 50 | return 0; 51 | } 52 | 53 | static int log_info(lua_State *L) { 54 | std::string logstr; 55 | tocppstring(L, logstr); 56 | LOG(INFO) << logstr; 57 | return 1; 58 | } 59 | 60 | static int log_warn(lua_State *L) { 61 | std::string logstr; 62 | tocppstring(L, logstr); 63 | LOG(WARNING) << logstr; 64 | return 1; 65 | } 66 | 67 | static int log_debug(lua_State *L) { 68 | std::string logstr; 69 | tocppstring(L, logstr); 70 | LOG(DEBUG) << logstr; 71 | return 1; 72 | } 73 | 74 | static int log_error(lua_State *L) { 75 | std::string logstr; 76 | tocppstring(L, logstr); 77 | LOG(ERROR) << logstr; 78 | return 1; 79 | } 80 | 81 | const luaL_Reg loglib[] = { 82 | {"info", log_info}, 83 | {"warn", log_warn}, 84 | {"debug", log_debug}, 85 | {"error", log_error}, 86 | 87 | {NULL, NULL} 88 | }; 89 | 90 | int luaopen_log(lua_State *L) { 91 | luaL_newlib(L, loglib); 92 | lua_pushcfunction(L, log_info); 93 | lua_setglobal(L, "print"); 94 | return 1; 95 | } 96 | -------------------------------------------------------------------------------- /src/lualog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\LuaJIT\src\lua.hpp" 4 | #ifdef REDLUA_STANDALONE 5 | #define luaopen_log luaopen_RedLua_log 6 | 7 | extern "C" { 8 | __declspec(dllexport) 9 | #endif 10 | int luaopen_log(lua_State *L); 11 | #ifdef REDLUA_STANDALONE 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /src/luamenu.cpp: -------------------------------------------------------------------------------- 1 | #include "luamenu.hpp" 2 | #include "natives.hpp" 3 | #include "thirdparty\scriptmenu.h" 4 | 5 | class MenuLua : public MenuBase { 6 | MenuLua **m_self; lua_State *m_L; 7 | int m_depth; 8 | 9 | public: 10 | MenuLua(MenuItemTitle *title, MenuLua **self, lua_State *L, int depth) 11 | : MenuBase(title), m_self(self), m_L(L), m_depth(depth) {} 12 | 13 | ~MenuLua() { 14 | *m_self = nullptr; 15 | if (m_depth < 1 ) { 16 | lua_pushnil(m_L); 17 | lua_setfield(m_L, LUA_REGISTRYINDEX, "MY_MENU_CLASS"); 18 | } 19 | } 20 | 21 | lua_State *GetLState(void) { 22 | return m_L; 23 | } 24 | }; 25 | 26 | class MenuItemLua : public MenuItemDefault { 27 | 28 | public: 29 | MenuItemLua(std::string caption) 30 | : MenuItemDefault(caption) {} 31 | 32 | lua_State *GetLState(void) { 33 | return ((MenuLua *)GetMenu())->GetLState(); 34 | } 35 | }; 36 | 37 | class MenuItemLuaMenu : public MenuItemLua { 38 | MenuBase *m_menu; int m_menuref; 39 | 40 | void OnSelect(void) { 41 | if (auto parentMenu = GetMenu()) 42 | if (auto controller = parentMenu->GetController()) { 43 | controller->RegisterMenu(m_menu); 44 | controller->PushMenu(m_menu); 45 | } 46 | } 47 | 48 | public: 49 | MenuItemLuaMenu(std::string title, lua_State *L, MenuLua *menu) 50 | : MenuItemLua(title), m_menu(menu), m_menuref(luaL_ref(L, LUA_REGISTRYINDEX)) {} 51 | 52 | ~MenuItemLuaMenu(void) { luaL_unref(GetLState(), LUA_REGISTRYINDEX, m_menuref); } 53 | 54 | eMenuItemClass GetClass() { return eMenuItemClass::Menu; } 55 | }; 56 | 57 | class MenuItemLuaButton : public MenuItemLua { 58 | int m_func; 59 | 60 | void OnSelect() { 61 | if (m_func != LUA_REFNIL) { 62 | lua_rawgeti(GetLState(), LUA_REGISTRYINDEX, m_func); 63 | if (lua_pcall(GetLState(), 0, 0, 0) != 0) { 64 | NATIVES::NOTIFY(1, 1500, lua_tostring(GetLState(), -1)); 65 | lua_pop(GetLState(), 1); 66 | } 67 | } 68 | } 69 | 70 | public: 71 | MenuItemLuaButton(std::string title, int func) 72 | : MenuItemLua(title), m_func(func) {} 73 | 74 | ~MenuItemLuaButton() { luaL_unref(GetLState(), LUA_REGISTRYINDEX, m_func); } 75 | }; 76 | 77 | class MenuItemLuaSwitchable : public MenuItemSwitchable { 78 | int m_func; 79 | 80 | lua_State *GetLState(void) { 81 | return ((MenuLua *)GetMenu())->GetLState(); 82 | } 83 | 84 | void OnSelect() { 85 | if (m_func != LUA_REFNIL) { 86 | lua_rawgeti(GetLState(), LUA_REGISTRYINDEX, m_func); 87 | lua_pushboolean(GetLState(), GetState()); 88 | if (lua_pcall(GetLState(), 1, 1, 0) == 0) 89 | SetState(lua_toboolean(GetLState(), -1)); 90 | else 91 | NATIVES::NOTIFY(1, 1500, lua_tostring(GetLState(), -1)); 92 | 93 | lua_pop(GetLState(), 1); 94 | } 95 | } 96 | 97 | public: 98 | MenuItemLuaSwitchable(std::string title, int func, bool initial) 99 | : MenuItemSwitchable(title, initial), m_func(func) {} 100 | 101 | ~MenuItemLuaSwitchable() { luaL_unref(GetLState(), LUA_REGISTRYINDEX, m_func); } 102 | }; 103 | 104 | static int meta_gc(lua_State *L) { 105 | auto menu = (MenuBase **)luaL_checkudata(L, 1, "MenuBase"); 106 | if (*menu) delete *menu; 107 | return 0; 108 | } 109 | 110 | static luaL_Reg menumeta[] = { 111 | {"__gc", meta_gc}, 112 | 113 | {NULL, NULL} 114 | }; 115 | 116 | static MenuLua *gen_menu(lua_State *L, int idx); 117 | 118 | MenuLua *gen_menu(lua_State *L, int depth) { 119 | luaL_checktype(L, -1, LUA_TTABLE); 120 | lua_getfield(L, -1, "items"); 121 | if (!lua_istable(L, -1)) goto error; 122 | lua_getfield(L, -2, "islist"); 123 | lua_getfield(L, -3, "title"); 124 | if (!lua_isstring(L, -1)) goto error; 125 | const char *errobj = nullptr; // Строка объекта с ошибкой 126 | MenuItemTitle *menutitleclass = nullptr; 127 | if (lua_toboolean(L, -2)) // islist 128 | menutitleclass = new MenuItemListTitle(lua_tostring(L, -1)); 129 | else 130 | menutitleclass = new MenuItemTitle(lua_tostring(L, -1)); 131 | lua_pop(L, 2); // Удаляем из стека тайтл и булево значение 132 | 133 | auto menu = (MenuLua **)lua_newuserdata(L, sizeof(MenuLua *)); 134 | if (luaL_newmetatable(L, "MenuBase")) 135 | luaL_setfuncs(L, menumeta, 0); 136 | lua_setmetatable(L, -2); 137 | 138 | *menu = new MenuLua(menutitleclass, menu, L, depth); 139 | (*menu)->SetController(nullptr); 140 | 141 | int count = (int)lua_objlen(L, -2); 142 | for (int i = 1; i <= count; i++) { 143 | lua_rawgeti(L, -2, i); 144 | if (!lua_istable(L, -1)) goto error; 145 | lua_getfield(L, -1, "title"); 146 | if (!lua_isstring(L, -1)) goto error; 147 | auto title = errobj = lua_tostring(L, -1); 148 | lua_getfield(L, -2, "type"); 149 | int itype = lua_tointeger(L, -1); 150 | bool initial; // Для свитча 151 | switch (itype) { 152 | case 0: // Menu 153 | lua_getfield(L, -3, "menu"); 154 | if (!lua_istable(L, -1)) goto error; 155 | (*menu)->AddItem(new MenuItemLuaMenu(title, L, gen_menu(L, depth + 1))); 156 | lua_pop(L, 1); // Удаляем из стека таблицу menu 157 | break; 158 | case 1: // Button 159 | lua_getfield(L, -3, "onclick"); 160 | if (!lua_isfunction(L, -1) && !lua_isnil(L, -1)) goto error; 161 | (*menu)->AddItem(new MenuItemLuaButton(title, luaL_ref(L, LUA_REGISTRYINDEX))); 162 | break; 163 | case 2: // Switchable 164 | lua_getfield(L, -3, "initial"); 165 | initial = lua_toboolean(L, -1); 166 | lua_getfield(L, -4, "onclick"); 167 | if (!lua_isfunction(L, -1)) goto error; 168 | (*menu)->AddItem(new MenuItemLuaSwitchable(title, luaL_ref(L, LUA_REGISTRYINDEX), initial)); 169 | lua_pop(L, 1); // Удаляем из стека initial 170 | break; 171 | 172 | default: goto error; 173 | } 174 | lua_pop(L, 3); 175 | errobj = nullptr; 176 | } 177 | 178 | lua_remove(L, -2); // Удаляем из стека таблицу итемов 179 | return *menu; 180 | 181 | error: 182 | luaL_error(L, "Menu creation error (depth: %d, menu: \"%s\", object: \"%s\")", 183 | depth, menutitleclass ? menutitleclass->GetCaption().c_str() : "[no title]", 184 | errobj ? errobj : "[no title]"); 185 | return nullptr; 186 | } 187 | 188 | static int menu_set(lua_State *L) { 189 | int top = lua_gettop(L); 190 | (void)gen_menu(L, 0); 191 | lua_setfield(L, LUA_REGISTRYINDEX, "MY_MENU_CLASS"); 192 | return 0; 193 | } 194 | 195 | static int menu_remove(lua_State *L) { 196 | lua_pushnil(L); 197 | lua_setfield(L, LUA_REGISTRYINDEX, "MY_MENU_CLASS"); 198 | return 0; 199 | } 200 | 201 | static luaL_Reg menulib[] = { 202 | {"set", menu_set}, 203 | {"remove", menu_remove}, 204 | 205 | {NULL, NULL} 206 | }; 207 | 208 | int luaopen_menu(lua_State *L) { 209 | lua_pushinteger(L, 0); 210 | lua_setglobal(L, "LUAMENU_ITEM_MENU"); 211 | lua_pushinteger(L, 1); 212 | lua_setglobal(L, "LUAMENU_ITEM_BUTTON"); 213 | lua_pushinteger(L, 2); 214 | lua_setglobal(L, "LUAMENU_ITEM_SWITCHABLE"); 215 | 216 | luaL_newlib(L, menulib); 217 | return 1; 218 | } 219 | -------------------------------------------------------------------------------- /src/luamenu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\LuaJIT\src\lua.hpp" 4 | #ifdef REDLUA_STANDALONE 5 | #define luaopen_menu luaopen_RedLua_menu 6 | 7 | extern "C" { 8 | __declspec(dllexport) 9 | #endif 10 | int luaopen_menu(lua_State *L); 11 | #ifdef REDLUA_STANDALONE 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /src/luamisc.cpp: -------------------------------------------------------------------------------- 1 | #include "luascript.hpp" 2 | 3 | #ifndef REDLUA_STANDALONE 4 | #include "luamisc.hpp" 5 | #include "constants.hpp" 6 | 7 | #include "thirdparty\keyboard.h" 8 | #include "scripthook.hpp" 9 | 10 | static int misc_iskeydown(lua_State *L) { 11 | lua_pushboolean(L, IsKeyDown((DWORD)luaL_checkinteger(L, 1))); 12 | return 1; 13 | } 14 | 15 | static int misc_iskeydownlong(lua_State *L) { 16 | lua_pushboolean(L, IsKeyDownLong((DWORD)luaL_checkinteger(L, 1))); 17 | return 1; 18 | } 19 | 20 | static int misc_iskeyjustup(lua_State *L) { 21 | lua_pushboolean(L, IsKeyJustUp((DWORD)luaL_checkinteger(L, 1), lua_toboolean(L, 2))); 22 | return 1; 23 | } 24 | 25 | static int misc_resetkey(lua_State *L) { 26 | ResetKeyState((DWORD)luaL_checkinteger(L, 1)); 27 | return 0; 28 | } 29 | 30 | static int misc_gamever(lua_State *L) { 31 | lua_pushinteger(L, getGameVersion()); 32 | lua_pushstring(L, REDLUA_GAMECODE); 33 | return 2; 34 | } 35 | 36 | static int misc_libver(lua_State *L) { 37 | lua_pushinteger(L, REDLUA_VERSION_NUM); 38 | return 1; 39 | } 40 | 41 | static luaL_Reg misclib[] = { 42 | {"iskeydown", misc_iskeydown}, 43 | {"iskeydownlong", misc_iskeydownlong}, 44 | {"iskeyjustup", misc_iskeyjustup}, 45 | {"resetkey", misc_resetkey}, 46 | 47 | {"gamever", misc_gamever}, 48 | {"libver", misc_libver}, 49 | 50 | {NULL, NULL} 51 | }; 52 | 53 | int luaopen_misc(lua_State *L) { 54 | for (int i = 0; i < 255; i++) { 55 | if (KeyNames[i]) { 56 | lua_pushinteger(L, i); 57 | lua_setglobal(L, KeyNames[i]); 58 | } 59 | } 60 | 61 | luaL_newlib(L, misclib); 62 | return 1; 63 | } 64 | #else 65 | // nullsub 66 | int luaopen_misc(lua_State *L) {return 0;} 67 | #endif 68 | -------------------------------------------------------------------------------- /src/luamisc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\LuaJIT\src\lua.hpp" 4 | 5 | int luaopen_misc(lua_State *L); 6 | -------------------------------------------------------------------------------- /src/luanative.cpp: -------------------------------------------------------------------------------- 1 | #include "luanative.hpp" 2 | #include "constants.hpp" 3 | 4 | #include "native\object.hpp" 5 | #include "native\types.hpp" 6 | #include "native\typemap.hpp" 7 | #include "native\call.hpp" 8 | #include "nativedb.hpp" 9 | 10 | static int native_call(lua_State *L) { 11 | auto nspace = Natives.GetNamespace(luaL_checkstring(L, 1)); 12 | luaL_argcheck(L, nspace != nullptr, 1, "unknown namespace"); 13 | auto meth = Natives.GetMethod(nspace, luaL_checkstring(L, 2)); 14 | luaL_argcheck(L, meth != nullptr, 2, "unknown method"); 15 | 16 | native_prepare(L, meth, lua_gettop(L) - 2); 17 | return native_perform(L, meth); 18 | } 19 | 20 | static int native_info(lua_State *L) { 21 | auto nspace = Natives.GetNamespace(luaL_checkstring(L, 1)); 22 | if (nspace == nullptr) return 0; 23 | auto meth = Natives.GetMethod(nspace, luaL_checkstring(L, 2)); 24 | if (meth == nullptr) return 0; 25 | 26 | lua_createtable(L, 0, 4); 27 | lua_pushfstring(L, "%p", meth->hash); 28 | lua_setfield(L, -2, "hash"); 29 | lua_pushnumber(L, meth->firstSeen); 30 | lua_setfield(L, -2, "build"); 31 | lua_pushstring(L, get_type_info(meth->returns).name.c_str()); 32 | lua_setfield(L, -2, "returns"); 33 | 34 | int argc = (int)meth->params.size(); 35 | if (argc > 0) { 36 | lua_createtable(L, argc, 0); 37 | for (int i = 0; i < argc; i++) { 38 | NativeParam ¶m = meth->params[i]; 39 | lua_createtable(L, 0, 2); 40 | lua_pushstring(L, get_type_info(param.type).name.c_str()); 41 | lua_setfield(L, -2, "type"); 42 | lua_pushstring(L, param.name.c_str()); 43 | lua_setfield(L, -2, "name"); 44 | lua_rawseti(L, -2, i + 1); 45 | } 46 | lua_setfield(L, -2, "params"); 47 | } 48 | 49 | return 1; 50 | } 51 | 52 | static int native_new(lua_State *L) { 53 | lua_Integer count = luaL_checkinteger(L, 2); 54 | luaL_argcheck(L, count > 0, 2, "count must be > 0"); 55 | std::string stype = luaL_checkstring(L, 1); 56 | auto type = get_type(stype); 57 | luaL_argcheck(L, type != NTYPE_UNKNOWN, 1, "invalid type"); 58 | NativeData *ptr; 59 | switch (lua_type(L, 3)) { 60 | case 10/*LUA_TCDATA*/: 61 | case LUA_TLIGHTUSERDATA: 62 | ptr = (NativeData *)lua_topointer(L, 3); 63 | break; 64 | 65 | default: 66 | ptr = nullptr; 67 | break; 68 | } 69 | 70 | push_uncached_fullcopy(L, type, ptr, (uint)count); 71 | return 1; 72 | } 73 | 74 | #ifdef REDLUA_GTAV 75 | # define VTN(idx) (float)lua_tonumber(L, idx), 0 76 | #else 77 | # define VTN(idx) (float)lua_tonumber(L, idx) 78 | #endif 79 | 80 | static int native_vector(lua_State *L) { 81 | Vector3 pos = {VTN(1), VTN(2), VTN(3)}; 82 | push_uncached_fullcopy(L, NTYPE_VECTOR3, (NativeData *)&pos); 83 | return 1; 84 | } 85 | 86 | #ifndef REDLUA_STANDALONE 87 | # define WORLDGETALL(T, TN) { \ 88 | auto no = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); \ 89 | luaL_argcheck(L, no->hdr.type != T, 1, "not a " #TN " pool"); \ 90 | lua_pushinteger(L, worldGetAll##TN((int *)NATIVEOBJECT_GETPTR(no), no->hdr.count)); \ 91 | return 1; \ 92 | } 93 | #else 94 | # define WORLDGETALL(T, TN) {lua_pushinteger(L, 0); return 1;} 95 | # define getGlobalPtr rl_ptrnullsub 96 | # define getScriptHandleBaseAddress rl_ptrnullsub 97 | static PUINT64 rl_ptrnullsub(int) {return NULL;} 98 | #endif 99 | 100 | static int native_allobjs(lua_State *L) WORLDGETALL(NTYPE_OBJECT, Objects) 101 | static int native_allpeds(lua_State *L) WORLDGETALL(NTYPE_PED, Peds) 102 | static int native_allpick(lua_State *L) WORLDGETALL(NTYPE_PICKUP, Pickups) 103 | static int native_allvehs(lua_State *L) WORLDGETALL(NTYPE_VEHICLE, Vehicles) 104 | 105 | static int native_globalptr(lua_State *L) { 106 | push_uncached_lightptr(L, NTYPE_INT, getGlobalPtr((int)luaL_checkinteger(L, 1))); 107 | return 1; 108 | } 109 | 110 | static int native_scrbase(lua_State *L) { 111 | int handle = 0; 112 | switch (lua_type(L, 1)) { 113 | case LUA_TNUMBER: 114 | handle = (int)luaL_checkinteger(L, 1); 115 | break; 116 | case LUA_TUSERDATA: 117 | handle = *(int *)NATIVEOBJECT_GETPTR( 118 | native_check(L, 1, NTYPE_UNKNOWN) 119 | ); 120 | break; 121 | } 122 | 123 | push_uncached_lightptr(L, NTYPE_INT, (NativeData *)getScriptHandleBaseAddress(handle)); 124 | return 1; 125 | } 126 | 127 | static const luaL_Reg nativelib[] = { 128 | {"call", native_call}, 129 | {"info", native_info}, 130 | 131 | {"new", native_new}, 132 | {"vector", native_vector}, 133 | 134 | {"allobjects", native_allobjs}, 135 | {"allpeds", native_allpeds}, 136 | {"allpickups", native_allpick}, 137 | {"allvehicles", native_allvehs}, 138 | 139 | {"globalptr", native_globalptr}, 140 | {"scrbase", native_scrbase}, 141 | 142 | {NULL, NULL} 143 | }; 144 | 145 | int luaopen_native(lua_State *L) { 146 | call_init(L); 147 | nativeobj_init(L); 148 | luaL_newlib(L, nativelib); 149 | return 1; 150 | } 151 | -------------------------------------------------------------------------------- /src/luanative.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\LuaJIT\src\lua.hpp" 4 | 5 | #ifdef REDLUA_STANDALONE 6 | #define luaopen_native luaopen_RedLua_native 7 | 8 | extern "C" { 9 | __declspec(dllexport) 10 | #endif 11 | int luaopen_native(lua_State *L); 12 | #ifdef REDLUA_STANDALONE 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /src/luascript.cpp: -------------------------------------------------------------------------------- 1 | #include "luascript.hpp" 2 | #include "luanative.hpp" 3 | #include "luamisc.hpp" 4 | #include "luamenu.hpp" 5 | #include "lualang.hpp" 6 | #include "lualog.hpp" 7 | 8 | #include "thirdparty\easyloggingpp.h" 9 | #include "constants.hpp" 10 | 11 | static const luaL_Reg redlibs[] = { 12 | {"native", luaopen_native}, 13 | {"misc", luaopen_misc}, 14 | {"menu", luaopen_menu}, 15 | {"lang", luaopen_lang}, 16 | {"log", luaopen_log}, 17 | 18 | {NULL, NULL} 19 | }; 20 | 21 | LuaScript::LuaScript(std::string dir, std::string fname, bool dir_to_paths) 22 | : m_path(dir + "\\" + fname) { 23 | L = luaL_newstate(); 24 | luaL_openlibs(L); 25 | for (auto lib = redlibs; lib->name; lib++) { 26 | lua_pushcfunction(L, lib->func); 27 | lua_pushstring(L, lib->name); 28 | lua_call(L, 1, 1); 29 | lua_setglobal(L, lib->name); 30 | } 31 | 32 | lua_getglobal(L, "package"); 33 | if (!lua_isnil(L, -1)) { 34 | if (dir_to_paths) 35 | lua_pushfstring(L, "%s\\%s%s\\%s;", dir.c_str(), 36 | REDLUA_LPATH1, dir.c_str(), REDLUA_LPATH2); 37 | lua_pushstring(L, REDLUA_PATHS); 38 | if (dir_to_paths) lua_concat(L, 2); 39 | lua_setfield(L, -2, "path"); 40 | if (dir_to_paths) 41 | lua_pushfstring(L, "%s\\%s%s\\%s;", dir.c_str(), 42 | REDLUA_CPATH1, dir.c_str(), REDLUA_CPATH2); 43 | lua_pushstring(L, REDLUA_CPATHS); 44 | if (dir_to_paths) lua_concat(L, 2); 45 | lua_setfield(L, -2, "cpath"); 46 | } 47 | lua_pop(L, 1); 48 | } 49 | 50 | LuaScript::~LuaScript(void) { 51 | if (LookForFunc("OnStop")) 52 | CallFunc(0, 0); 53 | 54 | lua_close(L); 55 | } 56 | 57 | bool LuaScript::Load(void) { 58 | if (m_modRef != LUA_REFNIL) { 59 | if (LookForFunc("OnReload") && !CallFunc(0, 0, false)) 60 | return false; 61 | 62 | luaL_unref(L, LUA_REGISTRYINDEX, m_modRef); 63 | } 64 | 65 | if (luaL_loadfile(L, m_path.c_str()) != 0) 66 | return (LogLuaError(), false); 67 | if (CallFunc(0, 1)) { 68 | m_modRef = luaL_ref(L, LUA_REGISTRYINDEX); 69 | if (m_modRef == LUA_REFNIL) { 70 | m_enabled = false, m_hasError = true; 71 | LOG(ERROR) << m_path << " does not return the table"; 72 | return false; 73 | } 74 | m_enabled = true, m_hasError = false, m_firstTick = true; 75 | 76 | if (LookForFunc("OnLoad") && !CallFunc(0, 0)) 77 | return false; 78 | 79 | LOG(INFO) << m_path << " loaded"; 80 | return true; 81 | } 82 | 83 | return false; 84 | } 85 | 86 | bool LuaScript::LookForFunc(const char *name) { 87 | if (m_enabled && m_modRef != -1) { 88 | lua_rawgeti(L, LUA_REGISTRYINDEX, m_modRef); 89 | lua_getfield(L, -1, name); 90 | if (!lua_isfunction(L, -1)) { 91 | lua_pop(L, 2); 92 | return false; 93 | } 94 | 95 | lua_remove(L, -2); 96 | return true; 97 | } 98 | 99 | return false; 100 | } 101 | 102 | void LuaScript::LogLuaError(void) { 103 | LOG(ERROR) << "lua_State(" << L << ") error: " << lua_tostring(L, -1); 104 | lua_pop(L, 1); 105 | lua_gc(L, LUA_GCCOLLECT, 0); 106 | } 107 | 108 | bool LuaScript::CallFunc(int argn, int retn, bool stop_on_error) { 109 | if (lua_pcall(L, argn, retn, 0) == 0) return true; 110 | if (stop_on_error) m_enabled = false, m_hasError = true; 111 | LogLuaError(); 112 | return false; 113 | } 114 | 115 | void LuaScript::OnTick(bool rebuild_menu) { 116 | if (LookForFunc("OnTick")) { 117 | lua_pushboolean(L, m_firstTick || rebuild_menu); 118 | m_firstTick = false; 119 | CallFunc(1, 0); 120 | } 121 | } 122 | 123 | MenuBase *LuaScript::GetMyMenu(void) { 124 | lua_getfield(L, LUA_REGISTRYINDEX, "MY_MENU_CLASS"); 125 | auto menu = (MenuBase **)lua_topointer(L, -1); 126 | lua_pop(L, 1); 127 | return menu ? *menu : nullptr; 128 | } 129 | 130 | float LuaScript::GetMemoryUsage(void) { 131 | return lua_gc(L, LUA_GCCOUNTB, 0) / 1024.0f; 132 | } 133 | 134 | void LuaScript::SetEnabled(bool en) { 135 | lua_gc(L, LUA_GCCOLLECT, 0); 136 | if (en && m_modRef == LUA_REFNIL && !Load()) 137 | return; 138 | m_enabled = en; 139 | } 140 | 141 | bool LuaScript::IsEnabled(void) { 142 | return m_enabled; 143 | } 144 | 145 | bool LuaScript::HasError(void) { 146 | return m_hasError; 147 | } 148 | 149 | std::string LuaScript::GetPath(void) { 150 | return m_path; 151 | } 152 | -------------------------------------------------------------------------------- /src/luascript.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "thirdparty\LuaJIT\src\lua.hpp" 5 | #include "thirdparty\scriptmenu.h" 6 | 7 | class LuaScript { 8 | private: 9 | lua_State *L; 10 | std::string m_path; 11 | bool m_enabled = false, m_hasError = false, 12 | m_firstTick = false; 13 | int m_modRef = LUA_REFNIL; 14 | 15 | public: 16 | LuaScript(std::string dir, std::string fname, bool dir_to_paths = false); 17 | 18 | ~LuaScript(void); 19 | 20 | bool Load(void); 21 | 22 | bool LookForFunc(const char *name); 23 | 24 | void LogLuaError(void); 25 | 26 | bool CallFunc(int argn, int retn, bool stop_on_error = true); 27 | 28 | void OnTick(bool rebuild_menu); 29 | 30 | MenuBase *GetMyMenu(void); 31 | 32 | float GetMemoryUsage(void); 33 | 34 | void SetEnabled(bool en); 35 | 36 | bool IsEnabled(void); 37 | 38 | bool HasError(void); 39 | 40 | std::string GetPath(void); 41 | }; 42 | -------------------------------------------------------------------------------- /src/menus/about.cpp: -------------------------------------------------------------------------------- 1 | #include "menus\helpers.hpp" 2 | #include "menus\about.hpp" 3 | #include "constants.hpp" 4 | #include "langctl.hpp" 5 | 6 | MenuBase *CreateAbout(MenuController *controller) { 7 | auto menu = new MenuBase(new MenuItemTitle(Lng.Get("core.main.about"))); 8 | controller->RegisterMenu(menu); 9 | 10 | menu->AddItem(new MenuItemLink("RedLua: igor725", "https://github.com/igor725/RedLua")); 11 | menu->AddItem(new MenuItemLink("LuaJIT: Mike Pall", "https://github.com/LuaJIT/LuaJIT")); 12 | # ifdef REDLUA_GTAV 13 | menu->AddItem(new MenuItemLink("UI/ScriptHook: Alexander Blade", "http://www.dev-c.com/gtav/scripthookv/")); 14 | menu->AddItem(new MenuItemLink("NativeDB: alloc8or", "https://github.com/alloc8or/gta5-nativedb-data")); 15 | # else 16 | menu->AddItem(new MenuItemLink("UI/ScriptHook: Alexander Blade", "https://www.dev-c.com/rdr2/scripthookrdr2/")); 17 | menu->AddItem(new MenuItemLink("NativeDB: alloc8or", "https://github.com/alloc8or/rdr3-nativedb-data")); 18 | # endif 19 | menu->AddItem(new MenuItemLink("JSON parser: Niels Lohmann", "https://github.com/nlohmann/json")); 20 | menu->AddItem(new MenuItemLink("EasyLogging++: abumusamq", "https://github.com/amrayn/easyloggingpp")); 21 | 22 | return menu; 23 | } 24 | -------------------------------------------------------------------------------- /src/menus/about.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | 5 | MenuBase *CreateAbout(MenuController *controller); 6 | -------------------------------------------------------------------------------- /src/menus/helpers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "langctl.hpp" 4 | 5 | #include "thirdparty\scriptmenu.h" 6 | #include 7 | 8 | class MenuItemLink : public MenuItemDefault { 9 | std::string m_link; 10 | 11 | void OnSelect() { 12 | ShellExecute(0, 0, m_link.c_str(), 0, 0, SW_SHOW); 13 | SetStatusText(Lng.Get("core.nfy.redir")); 14 | } 15 | 16 | public: 17 | MenuItemLink(std::string caption, std::string link) 18 | : MenuItemDefault(caption), m_link(link) {} 19 | }; 20 | 21 | class MenuItemButton : public MenuItemDefault 22 | { 23 | void (*m_callback)(MenuController *); 24 | 25 | void OnSelect() { 26 | m_callback(this->GetMenu()->GetController()); 27 | } 28 | 29 | public: 30 | MenuItemButton(std::string caption, void (*callback)(MenuController *)) 31 | : MenuItemDefault(caption), m_callback(callback) {} 32 | }; 33 | 34 | class MenuTemporary : public MenuBase 35 | { 36 | void OnPop(void) { 37 | delete this; 38 | } 39 | 40 | public: 41 | MenuTemporary(MenuItemTitle *title) 42 | : MenuBase(title) {} 43 | }; 44 | -------------------------------------------------------------------------------- /src/menus/langlist.cpp: -------------------------------------------------------------------------------- 1 | #include "menus\langlist.hpp" 2 | #include "langctl.hpp" 3 | 4 | class MenuItemLang : public MenuItemDefault { 5 | std::string m_langCode; 6 | 7 | void OnSelect() { 8 | Lng.Change(m_langCode); 9 | Settings.Write("menu_language", m_langCode); 10 | SetStatusText(Lng.Get("core.langs.nfy.chsc")); 11 | GetMenu()->GetController()->RequestRebuild(); 12 | } 13 | 14 | public: 15 | MenuItemLang(std::string name, std::string code) 16 | : MenuItemDefault(name), m_langCode(code) {} 17 | }; 18 | 19 | MenuBase *CreateLangsMenu(MenuController *controller) { 20 | auto menu = new MenuBase(new MenuItemTitle(Lng.Get("core.setts.langs"))); 21 | controller->RegisterMenu(menu); 22 | 23 | menu->AddItem(new MenuItemLang(Lng.Get("core.langs.ingame"), "ingame")); 24 | 25 | for (auto &it : Lng.GetMap()) 26 | menu->AddItem(new MenuItemLang(it.second.f_locName, it.first)); 27 | 28 | return menu; 29 | } 30 | -------------------------------------------------------------------------------- /src/menus/langlist.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | 5 | MenuBase *CreateLangsMenu(MenuController *controller); 6 | -------------------------------------------------------------------------------- /src/menus/main.cpp: -------------------------------------------------------------------------------- 1 | #include "thirdparty\scriptmenu.h" 2 | #include "menus\settings.hpp" 3 | #include "menus\helpers.hpp" 4 | #include "menus\scripts.hpp" 5 | #include "menus\about.hpp" 6 | #include "constants.hpp" 7 | #include "base.hpp" 8 | #include "langctl.hpp" 9 | 10 | MenuBase *CreateMainMenu(MenuController *controller) { 11 | auto menu = new MenuBase(new MenuItemTitle(REDLUA_FULLNAME)); 12 | controller->RegisterMenu(menu); 13 | 14 | menu->AddItem(new MenuItemButton(Lng.Get("core.main.scripts"), [](auto ctl) { 15 | if(Scripts.size() > 0) 16 | ctl->PushMenu(CreateScriptsList(ctl)); 17 | else 18 | ctl->SetStatusText(Lng.Get("core.scripts.nf")); 19 | })); 20 | menu->AddItem(new MenuItemButton(Lng.Get("core.main.refr"), [](auto ctl) { 21 | if (RedLuaScanScripts()) 22 | ctl->SetStatusText(Lng.Get("core.setts.nfy.srlsc")); 23 | else 24 | ctl->SetStatusText(Lng.Get("core.setts.nfy.srlfl")); 25 | })); 26 | menu->AddItem(new MenuItemMenu(Lng.Get("core.main.setts"), CreateSettings(controller))); 27 | menu->AddItem(new MenuItemMenu(Lng.Get("core.main.about"), CreateAbout(controller))); 28 | 29 | return menu; 30 | } 31 | -------------------------------------------------------------------------------- /src/menus/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | 5 | MenuBase *CreateMainMenu(MenuController *controller); 6 | -------------------------------------------------------------------------------- /src/menus/position.cpp: -------------------------------------------------------------------------------- 1 | #include "position.hpp" 2 | #include "settingsctl.hpp" 3 | #include "langctl.hpp" 4 | 5 | class MenuItemPosition : public MenuItemDefault { 6 | void OnSelect() { 7 | GetMenu()->GetController()->SetCurrentPosition( 8 | Settings.Write("menu_position", m_position) 9 | ); 10 | } 11 | 12 | private: 13 | int m_position; 14 | 15 | public: 16 | MenuItemPosition(std::string name, int pos) 17 | : MenuItemDefault(name), m_position(pos) {} 18 | }; 19 | 20 | MenuBase *CreatePositionMenu(MenuController *controller) { 21 | auto menu = new MenuBase(new MenuItemTitle(Lng.Get("core.setts.pos"))); 22 | controller->RegisterMenu(menu); 23 | 24 | menu->AddItem(new MenuItemPosition(Lng.Get("core.pos.left"), 0)); 25 | menu->AddItem(new MenuItemPosition(Lng.Get("core.pos.center"), 1)); 26 | menu->AddItem(new MenuItemPosition(Lng.Get("core.pos.right"), 2)); 27 | 28 | return menu; 29 | } 30 | -------------------------------------------------------------------------------- /src/menus/position.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | 5 | MenuBase *CreatePositionMenu(MenuController *controller); 6 | -------------------------------------------------------------------------------- /src/menus/scripts.cpp: -------------------------------------------------------------------------------- 1 | #include "base.hpp" 2 | #include "luascript.hpp" 3 | #include "menus\scripts.hpp" 4 | 5 | class MenuScript : public MenuTemporary { 6 | private: 7 | LuaScript *m_script; 8 | 9 | public: 10 | MenuScript(MenuItemTitle *title, LuaScript *script) 11 | : MenuTemporary(title), m_script(script) {} 12 | 13 | LuaScript *GetScript(void) { 14 | return m_script; 15 | } 16 | }; 17 | 18 | class MenuItemGenLua : public MenuItemDefault { 19 | public: 20 | MenuItemGenLua(void) : MenuItemDefault("") {}; 21 | MenuItemGenLua(std::string caption) : MenuItemDefault(caption) {}; 22 | 23 | LuaScript *GetScript(void) { 24 | return dynamic_cast(GetMenu())->GetScript(); 25 | } 26 | }; 27 | 28 | class MenuItemStatus : public MenuItemGenLua { 29 | std::string GetCaption(void) { 30 | std::string state; 31 | auto scr = GetScript(); 32 | if (scr->IsEnabled()) 33 | state = Lng.Get("core.script.state1"); 34 | else if (scr->HasError()) 35 | state = Lng.Get("core.script.state2"); 36 | else 37 | state = Lng.Get("core.script.state3"); 38 | return Lng.Get("core.script.state", state.c_str()); 39 | } 40 | 41 | public: 42 | MenuItemStatus() 43 | : MenuItemGenLua() {} 44 | }; 45 | 46 | class MenuItemUsage : public MenuItemGenLua { 47 | std::string m_usage = Lng.Get("core.script.rvlusage"); 48 | 49 | std::string GetCaption(void) { 50 | return m_usage; 51 | } 52 | 53 | void OnSelect(void) { 54 | LuaScript *scr = ((MenuScript *)GetMenu())->GetScript(); 55 | m_usage = Lng.Get("core.script.usage", scr->GetMemoryUsage()); 56 | } 57 | 58 | public: 59 | MenuItemUsage() 60 | : MenuItemGenLua() {} 61 | }; 62 | 63 | class MenuItemReload : public MenuItemGenLua { 64 | void OnSelect(void) { 65 | if (GetScript()->Load()) 66 | SetStatusText(Lng.Get("core.script.nfy.relsc")); 67 | else 68 | SetStatusText(Lng.Get("core.script.nfy.relfl")); 69 | } 70 | 71 | public: 72 | MenuItemReload(std::string title) 73 | : MenuItemGenLua(title) {} 74 | }; 75 | 76 | class MenuItemUnload : public MenuItemGenLua { 77 | void OnSelect(void) { 78 | auto scr = GetScript(); 79 | for (auto it = Scripts.begin(); it != Scripts.end();) { 80 | if (it->second == scr) { 81 | it = Scripts.erase(it); 82 | } else { 83 | it++; 84 | } 85 | } 86 | delete scr; 87 | SetStatusText(Lng.Get("core.script.nfy.unlsucc")); 88 | GetMenu()->GetController()->PopMenu(2); 89 | } 90 | 91 | public: 92 | MenuItemUnload(std::string title) 93 | : MenuItemGenLua(title) {} 94 | }; 95 | 96 | class MenuItemToggle : public MenuItemGenLua { 97 | void OnSelect(void) { 98 | auto scr = GetScript(); 99 | scr->SetEnabled(!scr->IsEnabled()); 100 | } 101 | 102 | public: 103 | MenuItemToggle(std::string title) 104 | : MenuItemGenLua(title) {} 105 | }; 106 | 107 | class MenuItemScript : public MenuItemDefault { 108 | void OnSelect() { 109 | auto menu = new MenuScript(new MenuItemTitle(this->GetCaption()), script); 110 | auto controller = GetMenu()->GetController(); 111 | controller->RegisterMenu(menu); 112 | 113 | menu->AddItem(new MenuItemStatus()); 114 | menu->AddItem(new MenuItemUsage()); 115 | 116 | auto scrmenu = script->GetMyMenu(); 117 | if (scrmenu) { 118 | controller->RegisterMenu(scrmenu); 119 | menu->AddItem(new MenuItemMenu(Lng.Get("core.script.ownmenu"), scrmenu)); 120 | } 121 | 122 | menu->AddItem(new MenuItemToggle(Lng.Get("core.script.tgl"))); 123 | menu->AddItem(new MenuItemReload(Lng.Get("core.script.rel"))); 124 | menu->AddItem(new MenuItemUnload(Lng.Get("core.script.unl"))); 125 | 126 | controller->PushMenu(menu); 127 | } 128 | 129 | private: 130 | LuaScript *script; 131 | 132 | public: 133 | MenuItemScript(std::string name, LuaScript *script) 134 | : MenuItemDefault(name), script(script) {} 135 | }; 136 | 137 | MenuTemporary *CreateScriptsList(MenuController *controller) { 138 | auto menu = new MenuTemporary(new MenuItemListTitle(Lng.Get("core.main.scripts"))); 139 | controller->RegisterMenu(menu); 140 | for (auto &x : Scripts) 141 | menu->AddItem(new MenuItemScript(x.first, x.second)); 142 | return menu; 143 | } 144 | -------------------------------------------------------------------------------- /src/menus/scripts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | #include "menus\helpers.hpp" 5 | 6 | MenuTemporary *CreateScriptsList(MenuController *controller); 7 | -------------------------------------------------------------------------------- /src/menus/settings.cpp: -------------------------------------------------------------------------------- 1 | #include "thirdparty\scriptmenu.h" 2 | #include "thirdparty\keyboard.h" 3 | #include "menus\settings.hpp" 4 | #include "menus\position.hpp" 5 | #include "menus\updates.hpp" 6 | #include "menus\langlist.hpp" 7 | #include "menus\helpers.hpp" 8 | #include "settingsctl.hpp" 9 | #include "nativedb.hpp" 10 | #include "natives.hpp" 11 | #include "base.hpp" 12 | #include "langctl.hpp" 13 | 14 | class MenuItemSSwitch : public MenuItemSwitchable { 15 | std::string m_field; 16 | bool m_initial; 17 | 18 | void OnSelect() { 19 | SetState(Settings.Switch(m_field, m_initial)); 20 | } 21 | 22 | public: 23 | MenuItemSSwitch(string caption, std::string field, bool initial) 24 | : MenuItemSwitchable(caption, initial), 25 | m_field(field), m_initial(initial) {} 26 | }; 27 | 28 | class MenuItemToggleAll : public MenuItemSwitchable { 29 | void OnSelect() { 30 | bool state = !GetState(); SetState(state); 31 | SetStatusText(state ? Lng.Get("core.setts.nfy.runall") : Lng.Get("core.setts.nfy.stpall")); 32 | for (auto &s : Scripts) 33 | s.second->SetEnabled(state); 34 | } 35 | 36 | public: 37 | MenuItemToggleAll(string caption) 38 | : MenuItemSwitchable(caption, true) {} 39 | }; 40 | 41 | MenuBase *CreateSettings(MenuController *controller) { 42 | auto menu = new MenuBase(new MenuItemTitle(Lng.Get("core.main.setts"))); 43 | controller->RegisterMenu(menu); 44 | 45 | menu->AddItem(new MenuItemButton(Lng.Get("core.setts.hotkey"), [](auto ctl) { 46 | int kcode = Settings.Read("menu_hotkey", REDLUA_HOTKEY_DEFAULT); 47 | if (kcode < 0 || kcode >= 255) kcode = REDLUA_HOTKEY_DEFAULT; 48 | auto name = KeyNames[kcode]; if (!name) name = ""; 49 | NATIVES::SHOW_KEYBOARD( 50 | # ifdef REDLUA_GTAV 51 | 6, "FMMC_KEY_TIP8S", 52 | # else 53 | 5, "STABLE_RENAME_MOUNT_PROMPT", 54 | # endif 55 | KeyNames[kcode], 10 56 | ); 57 | int status; 58 | while ((status = NATIVES::UPDATE_KEYBOARD()) == 0) WAIT(0); 59 | auto out = NATIVES::RESULT_KEYBOARD(); 60 | if (!out) return; 61 | if (auto outi = std::atoi(out)) { 62 | if (outi < 255 && KeyNames[outi]) { 63 | Settings.Write("menu_hotkey", outi); 64 | ctl->SetStatusText(Lng.Get("core.setts.nfy.hksc", KeyNames[outi])); 65 | } else 66 | ctl->SetStatusText(Lng.Get("core.setts.nfy.hkin", outi)); 67 | return; 68 | } 69 | for (int i = 0; i < 255; i++) { 70 | if (auto key = KeyNames[i]) { 71 | auto iof = std::strstr(key, out) - key; 72 | auto lendiff = std::strlen(key) - std::strlen(out); 73 | if ((iof == 0 && lendiff == 0) || (iof == 3 && lendiff == 3)) { 74 | Settings.Write("menu_hotkey", i); 75 | ctl->SetStatusText(Lng.Get("core.setts.nfy.hksc", key)); 76 | return; 77 | } 78 | } 79 | } 80 | ctl->SetStatusText(Lng.Get("core.setts.nfy.hkfl")); 81 | })); 82 | menu->AddItem(new MenuItemSSwitch(Lng.Get("core.setts.autorun"), "autorun", Settings.Read("autorun", true))); 83 | menu->AddItem(new MenuItemSSwitch(Lng.Get("core.setts.updater"), "auto_updates", Settings.Read("auto_updates", false))); 84 | menu->AddItem(new MenuItemMenu(Lng.Get("core.setts.chkupd"), CreateUpdatesMenu(controller))); 85 | menu->AddItem(new MenuItemMenu(Lng.Get("core.setts.langs"), CreateLangsMenu(controller))); 86 | menu->AddItem(new MenuItemButton(Lng.Get("core.setts.rldndb"), [](auto ctl) { 87 | if (auto code = Natives.Load()) 88 | ctl->SetStatusText(Lng.Get("core.setts.nfy.nrlfl", code)); 89 | else 90 | ctl->SetStatusText(Lng.Get("core.setts.nfy.nrlsc")); 91 | })); 92 | menu->AddItem(new MenuItemMenu(Lng.Get("core.setts.pos"), CreatePositionMenu(controller))); 93 | menu->AddItem(new MenuItemToggleAll(Lng.Get("core.setts.toggall"))); 94 | menu->AddItem(new MenuItemButton(Lng.Get("core.setts.relall"), [](auto ctl) { 95 | ctl->SetStatusText(Lng.Get("core.setts.nfy.relall")); 96 | for (auto &s : Scripts) 97 | s.second->Load(); 98 | })); 99 | menu->AddItem(new MenuItemButton(Lng.Get("core.setts.unlall"), [](auto ctl) { 100 | ctl->SetStatusText(Lng.Get("core.setts.nfy.unlall")); 101 | for (auto it = Scripts.begin(); it != Scripts.end();) { 102 | delete it->second; 103 | it = Scripts.erase(it); 104 | } 105 | })); 106 | 107 | return menu; 108 | } 109 | -------------------------------------------------------------------------------- /src/menus/settings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | 5 | MenuBase *CreateSettings(MenuController *controller); 6 | -------------------------------------------------------------------------------- /src/menus/updalert.cpp: -------------------------------------------------------------------------------- 1 | #include "menus\helpers.hpp" 2 | #include "menus\updalert.hpp" 3 | #include "thirdparty\scriptmenu.h" 4 | #include "constants.hpp" 5 | #include "langctl.hpp" 6 | 7 | void CreateUpdateAlert(MenuController *controller, std::string &version) { 8 | auto menu = new MenuTemporary(new MenuItemTitle(Lng.Get("core.updalert.nfn", version.c_str()))); 9 | controller->RegisterMenu(menu); 10 | 11 | menu->AddItem(new MenuItemLink(Lng.Get("core.updalert.btn"), REDLUA_RELS_URL + version)); 12 | controller->PushMenu(menu); 13 | } 14 | -------------------------------------------------------------------------------- /src/menus/updalert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | #include 5 | 6 | void CreateUpdateAlert(MenuController *controller, std::string &title); 7 | -------------------------------------------------------------------------------- /src/menus/updates.cpp: -------------------------------------------------------------------------------- 1 | #include "menus\updates.hpp" 2 | #include "menus\updalert.hpp" 3 | #include "menus\helpers.hpp" 4 | #include "updatesctl.hpp" 5 | #include "constants.hpp" 6 | #include "nativedb.hpp" 7 | #include "thirdparty\keyboard.h" 8 | 9 | MenuBase *CreateUpdatesMenu(MenuController *controller) { 10 | auto menu = new MenuBase(new MenuItemTitle(Lng.Get("core.setts.chkupd"))); 11 | controller->RegisterMenu(menu); 12 | 13 | menu->AddItem(new MenuItemButton(Lng.Get("core.chkupd.rl"), [](auto ctl) { 14 | std::string newver; 15 | if (auto code = UpdatesCtl.CheckRedLua(newver)) 16 | if (code == UpdatesController::ERR_NO_UPDATES) 17 | ctl->SetStatusText(Lng.Get("core.chkupd.nfy.noup")); 18 | else 19 | ctl->SetStatusText(Lng.Get("core.chkupd.nfy.upfl", code)); 20 | else 21 | CreateUpdateAlert(ctl, newver); 22 | })); 23 | menu->AddItem(new MenuItemButton(Lng.Get("core.chkupd.ndb"), [](auto ctl) { 24 | if (auto code = UpdatesCtl.CheckNativeDB(IsKeyDown(VK_CONTROL))) { 25 | if (code == UpdatesController::ERR_NO_UPDATES) 26 | ctl->SetStatusText(Lng.Get("core.chkupd.nfy.noup")); 27 | else 28 | ctl->SetStatusText(Lng.Get("core.chkupd.nfy.upfl", code)); 29 | } else { 30 | if (auto ndbcode = Natives.Load()) 31 | ctl->SetStatusText(Lng.Get("core.setts.nfy.nrlfl", ndbcode)); 32 | else 33 | ctl->SetStatusText(Lng.Get("core.setts.nfy.nrlsc")); 34 | } 35 | })); 36 | 37 | return menu; 38 | } 39 | -------------------------------------------------------------------------------- /src/menus/updates.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\scriptmenu.h" 4 | 5 | MenuBase *CreateUpdatesMenu(MenuController *controller); 6 | -------------------------------------------------------------------------------- /src/native/cache.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thirdparty\LuaJIT\src\lua.hpp" 4 | #include "native\types.hpp" 5 | #include 6 | 7 | #define LUANATIVE_CACHE "NativeCache" 8 | #define GLOBAL_NATIVECACHE "REDLUA_CACHE" 9 | 10 | typedef std::map> NativeCacheMap; 11 | 12 | typedef struct _NativeCache { 13 | int ref; 14 | NativeCacheMap *map; 15 | } NativeCache; 16 | 17 | static NativeCache *get_native_cache(lua_State *L, int cache_ref) { 18 | if (cache_ref == -2) return nullptr; 19 | if (cache_ref == -1) lua_getfield(L, LUA_REGISTRYINDEX, GLOBAL_NATIVECACHE); 20 | else lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref); 21 | auto nc = (NativeCache *)luaL_checkudata(L, -1, LUANATIVE_CACHE); 22 | lua_pop(L, 2); 23 | return nc; 24 | } 25 | 26 | static int from_cache(NativeCacheMap &map, NativeType type, NativeCacheField id) { 27 | for (auto &mty : map) { 28 | if (mty.first == type) { 29 | for (auto &mid : mty.second) 30 | if (mid.first == id) return mid.second; 31 | break; 32 | } 33 | } 34 | 35 | return 0; 36 | } 37 | 38 | static bool search_in_cache 39 | ( 40 | lua_State *L, NativeCache *cache_obj, NativeCacheField *cache_id, 41 | NativeType type, NativeData id, int *cached 42 | ) { 43 | if (cache_obj) { 44 | *cache_id = *cache_id == NATIVEDATA_INVAL ? id : *cache_id; 45 | lua_rawgeti(L, LUA_REGISTRYINDEX, cache_obj->ref); 46 | if ((*cached = from_cache(*cache_obj->map, type, *cache_id)) > 0) { 47 | lua_rawgeti(L, -1, *cached); 48 | if (!lua_isnil(L, -1)) { 49 | lua_remove(L, -2); 50 | return true; 51 | } 52 | 53 | // Промах кеша, в мапе есть значение, а Lua его уже удалила 54 | // TODO: Переиндексация кеша, мейби? 55 | lua_pop(L, 1); 56 | } 57 | } 58 | 59 | return false; 60 | } 61 | 62 | static void save_to_cache(lua_State *L, NativeCache *cache_obj, int cache_id, NativeType type, NativeData id, int cached) { 63 | if (cache_obj) { 64 | // Сохраняем в кеш 65 | lua_pushvalue(L, -1); 66 | // Если cached установлена в ненулевое значение, значит был промах кеша, 67 | // заполняем дыру в Lua кеше новой юзердатой с теми же параметрами 68 | if (!cached) cached = (int)lua_objlen(L, -3) + 1; 69 | lua_rawseti(L, -3, cached); 70 | (*cache_obj->map)[type][cache_id] = cached; 71 | } 72 | } 73 | 74 | static int ncache_gc(lua_State *L) { 75 | auto nc = (NativeCache *)luaL_checkudata(L, 1, LUANATIVE_CACHE); 76 | if (nc->map) delete nc->map; 77 | return 0; 78 | } 79 | 80 | static void create_luacache(lua_State *L) { 81 | lua_createtable(L, 0, 0); 82 | lua_createtable(L, 0, 1); 83 | lua_pushstring(L, "v"); 84 | lua_setfield(L, -2, "__mode"); 85 | lua_setmetatable(L, -2); 86 | 87 | int ref = luaL_ref(L, LUA_REGISTRYINDEX); 88 | auto nc = (NativeCache *)lua_newuserdata(L, sizeof(NativeCache)); 89 | if (luaL_newmetatable(L, LUANATIVE_CACHE)) { 90 | lua_pushcfunction(L, ncache_gc); 91 | lua_setfield(L, -2, "__gc"); 92 | } 93 | 94 | lua_setmetatable(L, -2); 95 | nc->map = new NativeCacheMap(); 96 | nc->ref = ref; 97 | } 98 | -------------------------------------------------------------------------------- /src/native/call.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scripthook.hpp" 4 | 5 | #include "nativedb.hpp" 6 | #include "native\cache.hpp" 7 | #include "native\object.hpp" 8 | 9 | static int RedLua_NameSpace = -1; 10 | 11 | static int native_prepare_arg_error(lua_State *L, NativeParam *param, int idx) { 12 | static std::string ptr[] = {"", "*"}; 13 | auto tname = get_type_info(param ? param->type : NTYPE_UNKNOWN).name; 14 | auto gty = luaL_typename(L, idx); 15 | if (auto gno = (NativeObject*)luaL_testudata(L, idx, LUANATIVE_OBJECT)) 16 | gty = get_type_info(gno->hdr.type).name.c_str(); 17 | 18 | auto msg = lua_pushfstring(L, "%s expected, got %s", (tname + ptr[param->isPointer]).c_str(), gty); 19 | return luaL_argerror(L, idx - 1, msg); 20 | } 21 | 22 | static int native_prepare_nobj(lua_State *L, NativeParam *param, bool vector_allowed, int idx) { 23 | auto no = (NativeObject *)luaL_checkudata(L, idx, LUANATIVE_OBJECT); 24 | 25 | if (vector_allowed && no->hdr.type == NTYPE_VECTOR3) { 26 | auto vec = (float *)NATIVEOBJECT_GETPTR(no); 27 | for (int i = 0; i < 3; i++) 28 | nativePush(vec[i * 2]); 29 | return 3; 30 | } 31 | 32 | NativeTypeInfo &nti_obj = get_type_info(no->hdr.type), 33 | &nti_param = get_type_info(param->type); 34 | 35 | if (!param || IS_NATIVETYPES_EQU(param->type, nti_param, no->hdr.type, nti_obj)) { 36 | if (param ? (param->isPointer == no->hdr.isPointer) : no->hdr.isPointer) 37 | return (nativePush(no->content.nd), 1); 38 | else if (!param || param->isPointer) 39 | return (nativePush(&no->content.nd), 1); 40 | else if (no->hdr.isPointer) 41 | return (nativePush(*no->content.pp), 1); 42 | } 43 | 44 | return native_prepare_arg_error(L, param, idx); 45 | } 46 | 47 | static int native_prepare_arg(lua_State *L, NativeParam *param, bool vector_allowed, int idx) { 48 | float temp; 49 | 50 | switch (lua_type(L, idx)) { 51 | case LUA_TNIL: 52 | if (!param || param->isPointer) 53 | return (nativePush(nullptr), 1); 54 | break; 55 | case LUA_TBOOLEAN: 56 | if (!param || (param->type == NTYPE_BOOL && !param->isPointer)) 57 | return (nativePush(lua_toboolean(L, idx)), 1); 58 | 59 | break; 60 | case LUA_TNUMBER: 61 | if (!param || !param->isPointer) { 62 | temp = (float)lua_tonumber(L, idx); 63 | if (param->type == NTYPE_FLOAT) 64 | return (nativePush(temp), 1); 65 | else if (param->type == NTYPE_INT || param->type == NTYPE_HASH) 66 | return (nativePush((int)temp), 1); 67 | } 68 | break; 69 | case LUA_TSTRING: 70 | if ((!param || param->type == NTYPE_CHAR && param->isPointer)) 71 | return (nativePush(lua_tostring(L, idx)), 1); 72 | break; 73 | case LUA_TUSERDATA: 74 | return native_prepare_nobj(L, param, vector_allowed, idx); 75 | case 10/*LUA_TCDATA*/: 76 | case LUA_TLIGHTUSERDATA: 77 | if (!param || param->isPointer) 78 | return (nativePush(lua_topointer(L, idx)), 1); 79 | break; 80 | } 81 | 82 | return native_prepare_arg_error(L, param, idx); 83 | } 84 | 85 | static void native_prepare(lua_State *L, NativeMeth *meth, int nargs) { 86 | int methargs = (int)meth->params.size(), idx = 3; 87 | int iend = (meth->isVararg && (nargs > methargs)) ? nargs : methargs; 88 | nativeInit(meth->hash); 89 | for (int i = 0; i < iend; idx++) { 90 | if ((idx - nargs) == 3) luaL_error(L, "insufficient arguments (%d expected, got %d)", iend, i); 91 | bool vector_allowed = (methargs - i >= 3) && meth->params[i].type == NTYPE_FLOAT && 92 | meth->params[i + 1].type == NTYPE_FLOAT && meth->params[i + 2].type == NTYPE_FLOAT; 93 | i += native_prepare_arg(L, (i < methargs ? &meth->params[i] : nullptr), vector_allowed, idx); 94 | } 95 | } 96 | 97 | static int native_perform(lua_State *L, NativeMeth *meth) { 98 | PUINT64 ret = nativeCall(); 99 | if (meth->returns == NTYPE_VOID || meth->returns == NTYPE_UNKNOWN) 100 | return 0; 101 | 102 | if (!meth->isRetPtr) { 103 | switch (meth->returns) { 104 | case NTYPE_INT: 105 | case NTYPE_ANY: 106 | case NTYPE_HASH: 107 | lua_pushinteger(L, *ret); 108 | break; 109 | 110 | case NTYPE_FLOAT: 111 | lua_pushnumber(L, *(float *)ret); 112 | break; 113 | 114 | case NTYPE_BOOL: 115 | lua_pushboolean(L, *(int *)ret); 116 | break; 117 | 118 | case NTYPE_CHAR: 119 | lua_pushlstring(L, (char *)ret, 1); 120 | break; 121 | 122 | case NTYPE_VECTOR3: 123 | push_uncached_fullcopy(L, NTYPE_VECTOR3, ret, 1); 124 | break; 125 | 126 | default: 127 | push_cached_fullobject(L, meth->returns, (NativeData)*ret); 128 | break; 129 | } 130 | } else 131 | push_uncached_lightobjectcopy(L, meth->returns, ret); 132 | 133 | return 1; 134 | } 135 | 136 | static int generic_newindex(lua_State *L) { 137 | luaL_error(L, "attempt to modify readonly object"); 138 | return 0; 139 | } 140 | 141 | static int meth_call(lua_State *L) { 142 | int nargs = lua_gettop(L); 143 | lua_rawgeti(L, LUA_REGISTRYINDEX, RedLua_NameSpace); 144 | int mt_top = (int)lua_objlen(L, 1); 145 | lua_rawgeti(L, 1, mt_top); 146 | lua_pushnil(L); 147 | lua_rawseti(L, 1, mt_top); 148 | auto meth = (NativeMeth *)lua_touserdata(L, -1); 149 | native_prepare(L, meth, nargs - 2); 150 | return native_perform(L, meth); 151 | } 152 | 153 | static const luaL_Reg methmeta[] = { 154 | {"__newindex", generic_newindex}, 155 | {"__call", meth_call}, 156 | 157 | {NULL, NULL} 158 | }; 159 | 160 | static int nspace_index(lua_State *L) { 161 | if (lua_type(L, 2) != LUA_TSTRING) 162 | luaL_error(L, "index must be a string"); 163 | lua_rawgeti(L, 1, 1); // Снова вытаскиваем таблицу методов 164 | int mt_top = (int)lua_objlen(L, -1) + 1; 165 | lua_rawgeti(L, 1, 2); // А теперь и стек 166 | lua_rawgeti(L, -1, mt_top); // Получаем из стека неймспейсов тот, что нужен для текущего метода 167 | auto meth = Natives.GetMethod( 168 | (NativeNamespace *)lua_touserdata(L, -1), 169 | lua_tostring(L, 2) 170 | ); 171 | if (meth == nullptr) 172 | luaL_error(L, "unknown method"); 173 | lua_pushlightuserdata(L, meth); 174 | lua_rawseti(L, -4, mt_top); 175 | lua_pop(L, 2); 176 | return 1; 177 | } 178 | 179 | static const luaL_Reg nspacemeta[] = { 180 | {"__newindex", generic_newindex}, 181 | {"__index", nspace_index}, 182 | 183 | {NULL, NULL} 184 | }; 185 | 186 | static int global_index(lua_State *L) { 187 | if (lua_type(L, 2) != LUA_TSTRING) 188 | return 0; 189 | 190 | auto nspace = Natives.GetNamespace(lua_tostring(L, 2)); 191 | if (nspace == nullptr) return 0; 192 | 193 | lua_rawgeti(L, LUA_REGISTRYINDEX, RedLua_NameSpace); 194 | lua_rawgeti(L, -1, 1); // Вытаскиваем из таблицы неймспейса таблицу метода 195 | int mt_top = (int)lua_objlen(L, -1) + 1; // Свободная ячейка стека неймспейсов 196 | lua_rawgeti(L, -2, 2); // Получаем стек неймспейсов 197 | lua_pushlightuserdata(L, nspace); 198 | lua_rawseti(L, -2, mt_top); // Сохраняем указатель на неймспейс в свободную ячейку стека 199 | lua_pop(L, 2); 200 | return 1; 201 | } 202 | 203 | static void call_init(lua_State *L) { 204 | lua_createtable(L, 1, 0); // Создаём таблицу для неймспейса 205 | lua_createtable(L, 0, 0); // Создаём стек неймспейсов 206 | lua_rawseti(L, -2, 2); // Сохраняем стек неймспейса в таблицу под индексом 2 207 | lua_createtable(L, 0, 0); // Создаём таблицу для нативного метода 208 | lua_createtable(L, 0, 3); // Создаём метатаблицу для нативного метода 209 | lua_pushstring(L, "method"); 210 | lua_setfield(L, -2, "__metatable"); 211 | luaL_setfuncs(L, methmeta, 0); 212 | lua_setmetatable(L, -2); // Присваиваем метатаблицу таблице нативного метода 213 | lua_rawseti(L, -2, 1); // Сохраняем таблицу нативного метода в таблицу неймспейса под индексом 1 214 | lua_createtable(L, 0, 3); // Создаём метатаблицу для неймспейса 215 | lua_pushstring(L, "namespace"); 216 | lua_setfield(L, -2, "__metatable"); 217 | luaL_setfuncs(L, nspacemeta, 0); 218 | lua_setmetatable(L, -2); // Присваиваем метатаблицу таблице для нейспейса 219 | RedLua_NameSpace = luaL_ref(L, LUA_REGISTRYINDEX); 220 | 221 | /* 222 | Дальше идёт весёлый хак, который к 223 | глобальной таблице цепляет метатаблицу. 224 | Эта штука нужна для красивых обращений к 225 | нативным функциям через псевдоглобальные 226 | переменные. 227 | */ 228 | lua_createtable(L, 0, 1); 229 | lua_pushcfunction(L, global_index); 230 | lua_setfield(L, -2, "__index"); 231 | lua_setmetatable(L, LUA_GLOBALSINDEX); 232 | } 233 | -------------------------------------------------------------------------------- /src/native/object.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "native\types.hpp" 4 | #include "native\cache.hpp" 5 | 6 | #define LUANATIVE_OBJECT "NativeObject" 7 | 8 | static NativeObject *push_uncached_lightptr 9 | ( 10 | lua_State *L, 11 | NativeType type, NativeData *ptr, 12 | uint count = NOBJCOUNT_UNKNOWN 13 | ) { 14 | auto no = (NativeObject *)lua_newuserdata(L, sizeof(NativeObject)); 15 | NATIVEOBJECT_INIT(no, type, true, false, 1, ptr); 16 | luaL_setmetatable(L, LUANATIVE_OBJECT); 17 | return no; 18 | } 19 | 20 | static NativeObject *push_uncached_lightobjectcopy 21 | ( 22 | lua_State *L, 23 | NativeType type, NativeData *ptr, 24 | uint count = NOBJCOUNT_UNKNOWN 25 | ) { 26 | auto no = (NativeObject *)lua_newuserdata(L, sizeof(NativeObject)); 27 | NATIVEOBJECT_INITLIGHT(no, type, false, count, *ptr); 28 | luaL_setmetatable(L, LUANATIVE_OBJECT); 29 | 30 | return no; 31 | } 32 | 33 | static NativeObject *push_uncached_fullcopy 34 | ( 35 | lua_State *L, 36 | NativeType type, NativeData *ptr, 37 | uint count = 1 38 | ) { 39 | NativeTypeInfo nti = get_type_info(type); 40 | // Считаем размер структуры NativeObjectHeader вместе с выравниванием 41 | auto no = (NativeObject *)lua_newuserdata(L, NATIVEOBJECT_HDRSIZE + nti.size * count); 42 | NATIVEOBJECT_INIT(no, type, false, false, count, 0); 43 | luaL_setmetatable(L, LUANATIVE_OBJECT); 44 | if (ptr) memcpy(&no->content, ptr, nti.size * count); 45 | else memset(&no->content, 0, nti.size * count); 46 | 47 | return no; 48 | } 49 | 50 | static NativeObject *push_cached_lightobjectlink 51 | ( 52 | lua_State *L, 53 | NativeType type, NativeData *ptr, 54 | int cache_ref = -1, NativeCacheField cache_id = NATIVEDATA_INVAL 55 | ) { 56 | int cached = 0; 57 | NativeCache *cache = get_native_cache(L, cache_ref); 58 | if (search_in_cache(L, cache, &cache_id, type, -1, &cached)) 59 | return (NativeObject *)lua_touserdata(L, -1); 60 | 61 | auto no = (NativeObject *)lua_newuserdata(L, sizeof(NativeObject)); 62 | NATIVEOBJECT_INITLIGHT(no, type, false, 1, *ptr); 63 | luaL_setmetatable(L, LUANATIVE_OBJECT); 64 | 65 | save_to_cache(L, cache, cache_id, type, -1, cached); 66 | return no; 67 | } 68 | 69 | static NativeObject *push_cached_fullobject 70 | ( 71 | lua_State *L, 72 | NativeType type, NativeData id, 73 | int cache_ref = -1, NativeCacheField cache_id = NATIVEDATA_INVAL 74 | ) { 75 | int cached = 0; 76 | NativeCache *cache = get_native_cache(L, cache_ref); 77 | if (search_in_cache(L, cache, &cache_id, type, id, &cached)) 78 | return (NativeObject *)lua_touserdata(L, -1); 79 | 80 | auto no = (NativeObject *)lua_newuserdata(L, sizeof(NativeObject)); 81 | NATIVEOBJECT_INIT(no, type, false, cache_ref == -1, 1, id); 82 | luaL_setmetatable(L, LUANATIVE_OBJECT); 83 | 84 | save_to_cache(L, cache, cache_id, type, id, cached); 85 | return no; 86 | } 87 | 88 | static NativeObject *native_check(lua_State *L, int idx, NativeType type) { 89 | auto no = (NativeObject *)luaL_checkudata(L, idx, LUANATIVE_OBJECT); 90 | if (type != NTYPE_UNKNOWN) { 91 | NativeTypeInfo &nti_got = get_type_info(no->hdr.type), 92 | &nti_exp = get_type_info(type); 93 | if (!IS_NATIVETYPES_EQU(type, nti_exp, no->hdr.type, nti_got)) { 94 | auto msg = lua_pushfstring(L, "%s expected, got %s", 95 | nti_exp.name.c_str(), nti_got.name.c_str()); 96 | luaL_argerror(L, idx, msg); 97 | return nullptr; 98 | } 99 | } 100 | 101 | return no; 102 | } 103 | 104 | static int vector_tostring(lua_State *L, NativeObject *no) { 105 | auto nv = (Vector3 *)NATIVEOBJECT_GETPTR(no); 106 | lua_pushfstring(L, "Vector3: %.3f, %.3f, %.3f", 107 | nv->x, nv->y, nv->z); 108 | return 1; 109 | } 110 | 111 | static int vector_newindex(lua_State *L, NativeObject *no, char idx) { 112 | auto nv = (Vector3 *)NATIVEOBJECT_GETPTR(no); 113 | 114 | switch (idx) { 115 | case 'x': 116 | case 'X': 117 | nv->x = (float)luaL_checknumber(L, 3); 118 | break; 119 | 120 | case 'y': 121 | case 'Y': 122 | nv->y = (float)luaL_checknumber(L, 3); 123 | break; 124 | 125 | case 'z': 126 | case 'Z': 127 | nv->z = (float)luaL_checknumber(L, 3); 128 | break; 129 | } 130 | 131 | return 0; 132 | } 133 | 134 | static int vector_index(lua_State *L, NativeObject *no, char idx) { 135 | if (no->hdr.type != NTYPE_VECTOR3) return 0; 136 | auto nv = (Vector3 *)NATIVEOBJECT_GETPTR(no); 137 | 138 | switch (idx) { 139 | case 'x': 140 | case 'X': 141 | lua_pushnumber(L, nv->x); 142 | return 1; 143 | 144 | case 'y': 145 | case 'Y': 146 | lua_pushnumber(L, nv->y); 147 | return 1; 148 | 149 | case 'z': 150 | case 'Z': 151 | lua_pushnumber(L, nv->z); 152 | return 1; 153 | } 154 | 155 | return 0; 156 | } 157 | 158 | static int native_topointer(lua_State *L) { 159 | auto no = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); 160 | lua_pushlightuserdata(L, NATIVEOBJECT_GETPTR(no)); 161 | return 1; 162 | } 163 | 164 | static int native_tostring(lua_State *L) { 165 | auto no = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); 166 | if (no->hdr.type == NTYPE_VECTOR3 && no->hdr.count == 1) 167 | return vector_tostring(L, no); 168 | NativeTypeInfo &nti = get_type_info(no->hdr.type); 169 | 170 | if (no->hdr.count != NOBJCOUNT_UNKNOWN && no->hdr.count > 1) 171 | lua_pushfstring(L, "%s[%d]: %p", nti.name.c_str(), no->hdr.count, &no->content); 172 | else if (no->hdr.isPointer) 173 | lua_pushfstring(L, "%s*: %p", nti.name.c_str(), no->content.p); 174 | else 175 | lua_pushfstring(L, "%s: %d", nti.name.c_str(), no->content.i32); 176 | 177 | return 1; 178 | } 179 | 180 | static int native_newindex(lua_State *L) { 181 | auto no = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); 182 | if (no->hdr.isReadOnly) 183 | luaL_error(L, "attempt to modify readonly object"); 184 | if (lua_type(L, 2) == LUA_TSTRING && no->hdr.type == NTYPE_VECTOR3) 185 | return vector_newindex(L, no, *lua_tostring(L, 2)); 186 | uint idx = (uint)luaL_checkinteger(L, 2); 187 | if (idx >= no->hdr.count) 188 | luaL_error(L, "array index out of bounds"); 189 | NativeTypeInfo &nti = get_type_info(no->hdr.type); 190 | auto ptr = &(((char *)(&no->content))[idx * nti.size]); 191 | 192 | switch (no->hdr.type) { 193 | case NTYPE_INT: 194 | case NTYPE_HASH: 195 | case NTYPE_ANY: 196 | *(int *)ptr = (int)luaL_checkinteger(L, 3); 197 | break; 198 | 199 | default: 200 | switch (lua_type(L, 3)) { 201 | case LUA_TUSERDATA: 202 | memcpy(ptr, NATIVEOBJECT_GETPTR(native_check(L, 3, no->hdr.type)), nti.size); 203 | break; 204 | case 10/*LUA_TCDATA*/: 205 | memcpy(ptr, lua_topointer(L, 3), nti.size); 206 | break; 207 | default: 208 | luaL_error(L, 209 | "invalid object passed (%s expected, got %s)", 210 | (nti.name + "or cdata").c_str(), 211 | luaL_typename(L, 3) 212 | ); 213 | break; 214 | } 215 | break; 216 | } 217 | 218 | return 0; 219 | } 220 | 221 | static int native_index(lua_State *L) { 222 | auto no = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); 223 | if (lua_type(L, 2) == LUA_TSTRING) { 224 | const char *str = lua_tostring(L, 2); 225 | return vector_index(L, no, *str) || luaL_getmetafield(L, 1, lua_tostring(L, 2)); 226 | } 227 | uint idx = (uint)luaL_checkinteger(L, 2); 228 | if (idx >= no->hdr.count) 229 | luaL_error(L, "array index out of bounds"); 230 | NativeTypeInfo &nti = get_type_info(no->hdr.type); 231 | auto ptr = (NativeData)&(((char *)(&no->content))[idx * nti.size]); 232 | switch (no->hdr.type) { 233 | case NTYPE_INT: 234 | case NTYPE_HASH: 235 | case NTYPE_ANY: 236 | lua_pushinteger(L, *(int *)ptr); 237 | return 1; 238 | case NTYPE_FLOAT: 239 | lua_pushnumber(L, *(float *)ptr); 240 | return 1; 241 | case NTYPE_BOOL: 242 | lua_toboolean(L, *(int *)ptr); 243 | return 1; 244 | case NTYPE_CHAR: 245 | lua_pushlstring(L, (char *)ptr, 1); 246 | return 1; 247 | default: 248 | if (no->hdr.ownCache == 0) { 249 | create_luacache(L); 250 | no->hdr.ownCache = luaL_ref(L, LUA_REGISTRYINDEX); 251 | } 252 | push_cached_lightobjectlink(L, no->hdr.type, &ptr, 253 | no->hdr.ownCache, idx); 254 | return 1; 255 | } 256 | 257 | return 0; 258 | } 259 | 260 | static int vector_unm(lua_State *L, NativeObject *no) { 261 | auto nv = (Vector3 *)NATIVEOBJECT_GETPTR(push_uncached_fullcopy( 262 | L, NTYPE_VECTOR3, &no->content.nd, 1 263 | )); 264 | nv->x = -nv->x, nv->y = -nv->y, nv->z = -nv->z; 265 | return 1; 266 | } 267 | 268 | static inline int vector_mul_vector(Vector3 *dst, Vector3 *v1, Vector3 *v2) { 269 | dst->x = v1->x * v2->x, dst->y = v1->y * v2->y, dst->z = v1->z * v2->z; 270 | return 1; 271 | } 272 | 273 | static inline int vector_mul_num(Vector3 *v, float num) { 274 | v->x *= num, v->y *= num, v->z *= num; 275 | return 1; 276 | } 277 | 278 | static int vector_mul(lua_State *L, NativeObject *no) { 279 | switch (lua_type(L, 2)) { 280 | case LUA_TUSERDATA: 281 | return vector_mul_vector( 282 | (Vector3 *)NATIVEOBJECT_GETPTR(push_uncached_fullcopy( 283 | L, NTYPE_VECTOR3, &no->content.nd, 1 284 | )), 285 | (Vector3 *)NATIVEOBJECT_GETPTR(no), 286 | (Vector3 *)NATIVEOBJECT_GETPTR(native_check(L, 2, NTYPE_VECTOR3)) 287 | ); 288 | case LUA_TNUMBER: 289 | return vector_mul_num( 290 | (Vector3 *)NATIVEOBJECT_GETPTR(push_uncached_fullcopy( 291 | L, NTYPE_VECTOR3, &no->content.nd, 1 292 | )), 293 | (float)lua_tonumber(L, 2)); 294 | default: 295 | return luaL_typerror(L, 2, "NativeObject/number"); 296 | } 297 | 298 | return 0; 299 | } 300 | 301 | static int native_arith_error(lua_State *L, NativeType type) { 302 | return luaL_error(L, "attempt to perform arithmetic on a %s value", 303 | get_type_info(type).name.c_str()); 304 | } 305 | 306 | static int native_mul(lua_State *L) { 307 | auto no = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); 308 | switch (no->hdr.type) { 309 | case NTYPE_VECTOR3: 310 | return vector_mul(L, no); 311 | default: 312 | return native_arith_error(L, no->hdr.type); 313 | } 314 | 315 | return 0; 316 | } 317 | 318 | static int native_unm(lua_State *L) { 319 | auto no = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); 320 | switch (no->hdr.type) { 321 | case NTYPE_VECTOR3: 322 | return vector_unm(L, no); 323 | default: 324 | return native_arith_error(L, no->hdr.type); 325 | } 326 | 327 | return 0; 328 | } 329 | 330 | static int native_eq(lua_State *L) { 331 | auto no1 = (NativeObject *)luaL_checkudata(L, 1, LUANATIVE_OBJECT); 332 | auto no2 = (NativeObject *)luaL_testudata(L, 2, LUANATIVE_OBJECT); 333 | NativeTypeInfo &nti1 = get_type_info(no1->hdr.type), 334 | &nti2 = get_type_info(no2->hdr.type); 335 | lua_pushboolean(L, IS_NATIVETYPES_EQU( 336 | no1->hdr.type, nti1, no2->hdr.type, nti2 337 | ) && 338 | memcmp( 339 | no1->hdr.isPointer ? no1->content.p : &no1->content, 340 | no2->hdr.isPointer ? no2->content.p : &no2->content, 341 | get_type_info(no1->hdr.type).size 342 | ) == 0 343 | ); 344 | return 1; 345 | } 346 | 347 | static const luaL_Reg nativeobj[] = { 348 | {"topointer", native_topointer}, 349 | 350 | {"__tostring", native_tostring}, 351 | {"__newindex", native_newindex}, 352 | {"__index", native_index}, 353 | {"__mul", native_mul}, 354 | {"__unm", native_unm}, 355 | {"__eq", native_eq}, 356 | 357 | {NULL, NULL} 358 | }; 359 | 360 | static void nativeobj_init(lua_State *L) { 361 | luaL_newmetatable(L, LUANATIVE_OBJECT); 362 | lua_pushstring(L, "none of your business"); 363 | lua_setfield(L, -2, "__metatable"); 364 | luaL_setfuncs(L, nativeobj, 0); 365 | 366 | create_luacache(L); 367 | lua_setfield(L, LUA_REGISTRYINDEX, GLOBAL_NATIVECACHE); 368 | } 369 | -------------------------------------------------------------------------------- /src/native/typemap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "native\types.hpp" 4 | 5 | NativeTypeMap NativeTypes { 6 | { NTYPE_UNKNOWN, {NTYPE_UNKNOWN, 0, "Unknown" }}, 7 | { NTYPE_VOID, { NTYPE_VOID, 0, "void" }}, 8 | { NTYPE_INT, { NTYPE_VOID, 4, "int" }}, 9 | { NTYPE_FLOAT, { NTYPE_VOID, 4, "float" }}, 10 | { NTYPE_BOOL, { NTYPE_INT, 4, "BOOL" }}, 11 | { NTYPE_CHAR, { NTYPE_VOID, 1, "char" }}, 12 | { NTYPE_ANY, { NTYPE_INT, 0, "Any" }}, 13 | { NTYPE_BLIP, { NTYPE_VOID, 4, "Blip" }}, 14 | { NTYPE_CAM, { NTYPE_VOID, 4, "Cam" }}, 15 | { NTYPE_ENTITY, { NTYPE_VOID, 4, "Entity" }}, 16 | { NTYPE_FIREID, { NTYPE_VOID, 4, "FireId" }}, 17 | { NTYPE_HASH, { NTYPE_INT, 4, "Hash" }}, 18 | { NTYPE_INTERIOR, { NTYPE_VOID, 4, "Interior" }}, 19 | { NTYPE_ITEMSET, { NTYPE_VOID, 4, "ItemSet" }}, 20 | { NTYPE_OBJECT, { NTYPE_ENTITY, 4, "Object" }}, 21 | { NTYPE_PED, { NTYPE_ENTITY, 4, "Ped" }}, 22 | { NTYPE_PICKUP, { NTYPE_VOID, 4, "Pickup" }}, 23 | { NTYPE_PLAYER, { NTYPE_VOID, 4, "Player" }}, 24 | {NTYPE_SCRHANDLE, { NTYPE_VOID, 4, "ScrHandle"}}, 25 | { NTYPE_VECTOR3, { NTYPE_VOID, 24, "Vector3" }}, 26 | { NTYPE_VEHICLE, { NTYPE_ENTITY, 4, "Vehicle" }}, 27 | {NTYPE_ANIMSCENE, { NTYPE_VOID, 4, "AnimScene"}}, 28 | { NTYPE_PERSCHAR, { NTYPE_VOID, 4, "PersChar" }}, 29 | { NTYPE_POPZONE, { NTYPE_VOID, 4, "PopZone" }}, 30 | { NTYPE_PROMPT, { NTYPE_VOID, 4, "Prompt" }}, 31 | { NTYPE_PROPSET, { NTYPE_VOID, 4, "PropSet" }}, 32 | { NTYPE_VOLUME, { NTYPE_VOID, 4, "Volume" }} 33 | }; 34 | -------------------------------------------------------------------------------- /src/native/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scripthook.hpp" 4 | #include 5 | #include 6 | 7 | typedef enum _NativeType : int { 8 | NTYPE_UNKNOWN = -1, 9 | NTYPE_VOID, 10 | NTYPE_INT, 11 | NTYPE_FLOAT, 12 | NTYPE_BOOL, 13 | NTYPE_CHAR, 14 | NTYPE_ANY, 15 | NTYPE_BLIP, 16 | NTYPE_CAM, 17 | NTYPE_ENTITY, 18 | NTYPE_FIREID, 19 | NTYPE_HASH, 20 | NTYPE_INTERIOR, 21 | NTYPE_ITEMSET, 22 | NTYPE_OBJECT, 23 | NTYPE_PED, 24 | NTYPE_PICKUP, 25 | NTYPE_PLAYER, 26 | NTYPE_SCRHANDLE, 27 | NTYPE_VECTOR3, 28 | NTYPE_VEHICLE, 29 | NTYPE_ANIMSCENE, 30 | NTYPE_PERSCHAR, 31 | NTYPE_POPZONE, 32 | NTYPE_PROMPT, 33 | NTYPE_PROPSET, 34 | NTYPE_VOLUME 35 | } NativeType; 36 | 37 | typedef long long NativeCacheField; 38 | typedef unsigned long long NativeData; 39 | #define NATIVEDATA_INVAL ((NativeCacheField)-1) 40 | #define NATIVECACHE_DISABLE ((int)-2) 41 | #define NATIVECACHE_NODATA ((NativeData)-1) 42 | 43 | typedef struct _NativeParam { 44 | NativeType type; 45 | std::string name; 46 | bool isPointer; 47 | } NativeParam; 48 | 49 | typedef std::map NativeParamMap; 50 | 51 | typedef struct _NativeMeth { 52 | UINT64 hash; 53 | NativeType returns; 54 | NativeParamMap params; 55 | unsigned long firstSeen; 56 | bool isVararg; 57 | bool isRetPtr; 58 | bool isRetConst; 59 | } NativeMeth; 60 | 61 | typedef struct _NativeTypeInfo { 62 | NativeType superType; 63 | uint size; 64 | std::string name; 65 | } NativeTypeInfo; 66 | 67 | typedef std::map NativeNamespace; 68 | typedef std::map NativeNamespaces; 69 | typedef std::map NativeTypeMap; 70 | 71 | extern NativeTypeMap NativeTypes; 72 | 73 | static NativeTypeInfo &get_type_info(NativeType id) { 74 | for (auto &it : NativeTypes) 75 | if (it.first == id) return it.second; 76 | return NativeTypes[NTYPE_UNKNOWN]; 77 | } 78 | 79 | static NativeType get_type(std::string &name) { 80 | for (auto &it : NativeTypes) { 81 | if (it.second.name == name) 82 | return it.first; 83 | } 84 | 85 | return NTYPE_UNKNOWN; 86 | } 87 | 88 | typedef struct _NativeObjectHeader { 89 | NativeType type; 90 | bool isPointer; 91 | bool isReadOnly; 92 | int ownCache; 93 | uint count; 94 | } NativeObjectHeader; 95 | 96 | typedef struct _NativeObject { 97 | NativeObjectHeader hdr; 98 | union _NativeContent { 99 | int i32; 100 | void *p; 101 | void **pp; 102 | NativeData nd; 103 | } content; 104 | } NativeObject; 105 | 106 | typedef struct _NativeVector { 107 | NativeObjectHeader hdr; 108 | Vector3 content; 109 | } NativeVector; 110 | 111 | #define NATIVEOBJECT_INIT(X, T, P, R, C, D) (X)->hdr.type = (T), \ 112 | (X)->hdr.isPointer = (P), (X)->hdr.isReadOnly = (R), \ 113 | (X)->hdr.count = (C), (X)->content.nd = (NativeData)(D), \ 114 | (X)->hdr.ownCache = 0 115 | 116 | #define NATIVEOBJECT_INITLIGHT(X, T, R, C, D) (X)->hdr.type = (T), \ 117 | (X)->hdr.isPointer = true, (X)->hdr.isReadOnly = (R), \ 118 | (X)->hdr.count = (C), (X)->content.nd = (D), \ 119 | (X)->hdr.ownCache = 0 120 | 121 | #define NATIVEOBJECT_GETPTR(X) ((X)->hdr.isPointer ? (X)->content.p : &(X)->content.i32) 122 | #define NATIVEOBJECT_HDRSIZE (sizeof(NativeObject) - sizeof(NativeData)) 123 | #define IS_NATIVETYPES_EQU(AT, AI, BT, BI) ( \ 124 | ((AT) == (BT)) || \ 125 | ((AT) == (BI).superType) || \ 126 | ((AI).superType == (BT)) \ 127 | ) 128 | 129 | #define NOBJCOUNT_UNKNOWN ((uint)-1) 130 | -------------------------------------------------------------------------------- /src/nativedb.cpp: -------------------------------------------------------------------------------- 1 | #include "nativedb.hpp" 2 | #include "constants.hpp" 3 | #include "json.hpp" 4 | 5 | #include 6 | 7 | NativeDB Natives (REDLUA_NATIVES_FILE); 8 | 9 | static inline bool jstring(json &obj, std::string &dst) { 10 | if (!obj.is_string()) return false; 11 | obj.get_to(dst); 12 | return true; 13 | } 14 | 15 | NativeDB::Returns NativeDB::Load(void) { 16 | std::ifstream fnatives(m_path); 17 | if (!fnatives.is_open()) return ERR_OPEN_FILE; 18 | json jnatives = json::parse(fnatives, nullptr, false); 19 | if (jnatives.is_discarded() || !jnatives.is_object()) 20 | return ERR_MALFORMED_FILE; 21 | fnatives.close(); 22 | 23 | for (auto &ijnspace : jnatives.items()) { 24 | json &jnspace = ijnspace.value(); 25 | if (!jnspace.is_object()) return ERR_NAMESPACE_NONOBJECT; 26 | NativeNamespace &methods = m_db[ijnspace.key()]; 27 | for (auto &ijnmeth : jnspace.items()) { 28 | json &jnmeth = ijnmeth.value(); 29 | if (!jnmeth.is_object()) 30 | return ERR_METHOD_NONOBJECT; 31 | if (!(jnmeth["name"]).is_string()) 32 | return ERR_METHOD_NONSTRING_NAME; 33 | std::string temp; 34 | NativeMeth &meth = methods[jnmeth["name"].get_to(temp)]; 35 | m_nummethods++; 36 | if (jstring(jnmeth["return_type"], temp)) { 37 | if (meth.isRetPtr = (temp.find("*") != std::string::npos)) 38 | temp.pop_back(); 39 | if (meth.isRetConst = (temp.find("const ") == 0)) 40 | temp.erase(0, 6); 41 | if ((meth.returns = get_type(temp)) == NTYPE_UNKNOWN) 42 | return ERR_METHOD_INVALID_RETURN_TYPE; 43 | } else return ERR_METHOD_NONSTRING_RETURN_TYPE; 44 | 45 | if (jstring(jnmeth["build"], temp)) 46 | meth.firstSeen = std::stoul(temp); 47 | 48 | json &jnmethparams = jnmeth["params"]; 49 | if (!jnmethparams.is_array()) 50 | return ERR_METHOD_PARAMS_NONARRAY; 51 | int argn = 0; 52 | for (auto &inmparam : jnmeth["params"]) { 53 | if (!inmparam.is_object()) return ERR_METHOD_PARAM_NONOBJECT; 54 | if (!jstring(inmparam["type"], temp)) 55 | return ERR_METHOD_PARAM_NONSTRING_TYPE; 56 | if (temp.size() == 0) { 57 | meth.isVararg = true; 58 | break; 59 | } 60 | NativeParam &methparam = meth.params[argn++]; 61 | if (methparam.isPointer = (temp.find("*") != std::string::npos)) 62 | temp.pop_back(); 63 | if (temp.find("const ") == 0) 64 | temp.erase(0, 6); 65 | if ((methparam.type = get_type(temp)) == NTYPE_UNKNOWN) 66 | return ERR_METHOD_PARAM_INVALID_TYPE; 67 | if (!jstring(inmparam["name"], methparam.name)) 68 | return ERR_METHOD_PARAM_NONSTRING_NAME; 69 | } 70 | 71 | meth.hash = std::stoull(ijnmeth.key().c_str(), nullptr, 16); 72 | } 73 | } 74 | 75 | return OK; 76 | } 77 | -------------------------------------------------------------------------------- /src/nativedb.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "native\types.hpp" 4 | 5 | class NativeDB { 6 | std::string m_path; 7 | NativeNamespaces m_db; 8 | int m_nummethods = 0; 9 | 10 | public: 11 | enum Returns { 12 | OK, 13 | ERR_OPEN_FILE, 14 | ERR_MALFORMED_FILE, 15 | ERR_NAMESPACE_NONOBJECT, 16 | ERR_METHOD_NONOBJECT, 17 | ERR_METHOD_NONSTRING_NAME, 18 | ERR_METHOD_INVALID_RETURN_TYPE, 19 | ERR_METHOD_NONSTRING_RETURN_TYPE, 20 | ERR_METHOD_PARAM_INVALID_TYPE, 21 | ERR_METHOD_PARAM_NONSTRING_TYPE, 22 | ERR_METHOD_PARAM_NONSTRING_NAME, 23 | ERR_METHOD_PARAM_NONOBJECT, 24 | ERR_METHOD_PARAMS_NONARRAY, 25 | }; 26 | 27 | virtual Returns Load(void); 28 | 29 | NativeNamespace *GetNamespace(std::string nspace) { 30 | for (auto &it : m_db) 31 | if (it.first == nspace) return &it.second; 32 | return nullptr; 33 | } 34 | 35 | NativeMeth *GetMethod(NativeNamespace *nspace, std::string method) { 36 | for (auto &it : (*nspace)) 37 | if (it.first == method) return &it.second; 38 | return nullptr; 39 | } 40 | 41 | uint GetMethodCount(void) { 42 | return m_nummethods; 43 | } 44 | 45 | NativeDB(std::string path) : m_path(path) {} 46 | }; 47 | 48 | 49 | extern NativeDB Natives; 50 | -------------------------------------------------------------------------------- /src/natives.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "scripthook.hpp" 4 | 5 | namespace NATIVES { 6 | #if defined(REDLUA_GTAV) 7 | static const char* CREATE_STRING(int flags, const char* textTemplate, const char *string) {(void)flags; (void)textTemplate; return string; } 8 | static Hash GET_HASH_KEY(char* string) { return invoke(0xD24D37CC275948CC, string); } 9 | static void PLAY_SOUND_FRONTEND(char* audioName, char* audioRef, BOOL p3) { invoke(0x67C540AA08E4A6F5, -1, audioName, audioRef, p3); } 10 | static void DRAW_TEXT(const char* text, float x, float y) { 11 | invoke(0x25FBB336DF1804CB, "STRING"); 12 | invoke(0x6C188BE134E074AA, text); 13 | invoke(0xCD015E5BB0D96A57, x, y); 14 | } 15 | static void SET_TEXT_SCALE(float p0, float scale) { invoke(0x07C837F9A01C34C9, p0, scale); } 16 | static void SET_TEXT_COLOR_RGBA(int r, int g, int b, int a) { invoke(0xBE6B23FFA53FB442, r, g, b, a); } 17 | static void SET_TEXT_CENTRE(BOOL align) { invoke(0xC02F4DBFB51D988B, align); } 18 | static void SET_TEXT_DROPSHADOW(int distance, int r, int g, int b, int a) { invoke(0x465C84BC39F1C351, distance, r, g, b, a); } 19 | static void DRAW_RECT(float x, float y, float width, float height, int r, int g, int b, int a, BOOL p8, BOOL p9) { (void)p8; (void)p9; invoke(0x3A618A217E5154F0, x, y, width, height, r, g, b, a); } 20 | static int GET_LOCALE(void) { return invoke(0x2BDD44CC428A7EAE); } 21 | 22 | static void SHOW_KEYBOARD(int type, const char *title, const char *def, int maxLen) { invoke(0x00DC833F2568DBF6, type, title, "", def, "", "", "", maxLen); } 23 | static int UPDATE_KEYBOARD() { return invoke(0x0CF2B696BBF945AE); } 24 | static void CANCEL_KEYBOARD() { invoke(0x58A39BE597CE99CD); } 25 | static const char *RESULT_KEYBOARD() { return invoke(0x8362B09B91893647); } 26 | 27 | static void NOTIFY(int type, int duration, const char *message) { 28 | (void)type; (void)duration; 29 | invoke(0x202709F4C58A0424, "STRING"); 30 | invoke(0x6C188BE134E074AA, message); 31 | invoke(0x2ED7843F8F801023, true, true); 32 | } 33 | #elif defined(REDLUA_RDR3) 34 | template 35 | static const char* CREATE_STRING(int flags, const char* textTemplate, Args... args) { return invoke(0xFA925AC00EB830B9, flags, textTemplate, args...); } 36 | static Hash GET_HASH_KEY(char* string) { return invoke(0xFD340785ADF8CFB7, string); } 37 | static void PLAY_SOUND_FRONTEND(char* audioName, char* audioRef, BOOL p3, BOOL p4) { invoke(0x67C540AA08E4A6F5, audioName, audioRef, p3, p4); } 38 | static void STOP_SOUND_FRONTEND(char* audioName, char* audioRef) { invoke(0x0F2A2175734926D8, audioName, audioRef); } 39 | static void DRAW_TEXT(const char* text, float x, float y) { invoke(0xD79334A4BB99BAD1, text, x, y); } 40 | static void SET_TEXT_SCALE(float p0, float scale) { invoke(0x4170B650590B3B00, p0, scale); } 41 | static void SET_TEXT_COLOR_RGBA(int r, int g, int b, int a) { invoke(0x50A41AD966910F03, r, g, b, a); } 42 | static void SET_TEXT_CENTRE(BOOL align) { invoke(0xBE5261939FBECB8C, align); } 43 | static void SET_TEXT_DROPSHADOW(int distance, int r, int g, int b, int a) { invoke(0x1BE39DBAA7263CA5, distance, r, g, b, a); } 44 | static void DRAW_RECT(float x, float y, float width, float height, int r, int g, int b, int a, BOOL p8, BOOL p9) { invoke(0x405224591DF02025, x, y, width, height, r, g, b, a, p8, p9); } 45 | static int GET_LOCALE(void) { return invoke(0xDB917DA5C6835FCC); } 46 | 47 | static void SHOW_KEYBOARD(int type, const char *title, const char *def, int maxLen) { invoke(0x044131118D8DB3CD, type, title, "", def, "", "", "", maxLen); } 48 | static int UPDATE_KEYBOARD() { return invoke(0x37DF360F235A3893); } 49 | static void CANCEL_KEYBOARD() { invoke(0x58A39BE597CE99CD); } 50 | static const char *RESULT_KEYBOARD() { return invoke(0xAFB4CF58A4A292B1); } 51 | 52 | static void NOTIFY(int type, int duration, const char *message) { 53 | struct { 54 | int val; 55 | int _padding[14]; 56 | } dur = {duration}; 57 | 58 | struct { 59 | void *padding; 60 | const char *msg; 61 | } dat = {{0}, CREATE_STRING(10, "LITERAL_STRING", message)}; 62 | 63 | switch (type) { 64 | case 0 /*Tooltip*/: 65 | invoke(0x049D5C615BD38BAD, &dur, &dat, true); 66 | break; 67 | case 1/*Objective*/: 68 | invoke(0xCEDBF17EFCC0E4A4, &dur, &dat, true); 69 | break; 70 | } 71 | } 72 | #endif 73 | } 74 | -------------------------------------------------------------------------------- /src/scripthook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(REDLUA_GTAV) && !defined(REDLUA_RDR3) 4 | # define REDLUA_RDR3 5 | #endif 6 | 7 | #if defined(REDLUA_GTAV) 8 | # include "thirdparty\ScriptHookV\inc\main.h" 9 | # ifdef REDLUA_STANDALONE 10 | # include "emu\native.hpp" 11 | # endif 12 | 13 | # include "thirdparty\ScriptHookV\inc\nativeCaller.h" 14 | # include "thirdparty\ScriptHookV\inc\types.h" 15 | #elif defined(REDLUA_RDR3) 16 | # include "thirdparty\ScriptHookRDR2\inc\main.h" 17 | # ifdef REDLUA_STANDALONE 18 | # include "emu\native.hpp" 19 | # endif 20 | 21 | # include "thirdparty\ScriptHookRDR2\inc\nativeCaller.h" 22 | # include "thirdparty\ScriptHookRDR2\inc\types.h" 23 | #endif 24 | -------------------------------------------------------------------------------- /src/settingsctl.cpp: -------------------------------------------------------------------------------- 1 | #include "settingsctl.hpp" 2 | #include "constants.hpp" 3 | #include 4 | 5 | SettingsController Settings (REDLUA_SETTINGS_FILE); 6 | 7 | bool SettingsController::Load(void) { 8 | std::ifstream jfile(m_file); 9 | if (jfile.is_open()) { 10 | if (!(m_data = json::parse(jfile, nullptr, false)).is_discarded()) 11 | if (m_modified = !m_data.is_object()) goto resetcfg; 12 | jfile.close(); 13 | 14 | return true; 15 | } 16 | 17 | resetcfg: 18 | m_data = { 19 | {"menu_hotkey", REDLUA_HOTKEY_DEFAULT}, 20 | {"menu_language", "ingame"}, 21 | {"menu_position", 0}, 22 | {"auto_updates", false}, 23 | {"autorun", false}, 24 | }; // Очищаем невалидный конфиг 25 | return false; 26 | } 27 | 28 | std::string &SettingsController::Read(std::string name, std::string &def) { 29 | if (m_data[name].is_string()) 30 | m_data[name].get_to(def); 31 | else 32 | Write(name, def); 33 | return def; 34 | } 35 | 36 | int SettingsController::Read(std::string name, int def) { 37 | if (m_data[name].is_number()) 38 | m_data[name].get_to(def); 39 | else 40 | Write(name, def); 41 | return def; 42 | } 43 | 44 | bool SettingsController::Read(std::string name, bool def) { 45 | if (m_data[name].is_boolean()) 46 | m_data[name].get_to(def); 47 | else 48 | Write(name, def); 49 | return def; 50 | } 51 | 52 | bool SettingsController::Switch(std::string name, bool def) { 53 | bool state = Read(name, def); 54 | return Write(name, !state); 55 | } 56 | 57 | bool SettingsController::Save(void) { 58 | if (!m_modified) return true; 59 | std::ofstream jfile(m_file); 60 | if (jfile.is_open()) { 61 | jfile << std::setw(4) << m_data << std::endl; 62 | jfile.close(); 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | -------------------------------------------------------------------------------- /src/settingsctl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "constants.hpp" 4 | #include "json.hpp" 5 | 6 | #include 7 | 8 | class SettingsController { 9 | std::string m_file; 10 | json m_data; 11 | bool m_modified = true; 12 | 13 | public: 14 | SettingsController(std::string file) 15 | : m_file(file) { if (!Load()) Save(); }; 16 | 17 | bool Load(void); 18 | 19 | std::string &Read(std::string name, std::string &def); 20 | int Read(std::string name, int def); 21 | bool Read(std::string name, bool def); 22 | 23 | bool Switch(std::string name, bool def); 24 | 25 | template 26 | T Write(std::string name, T val) { 27 | m_data[name] = val; 28 | m_modified = true; 29 | return val; 30 | } 31 | 32 | bool Save(void); 33 | }; 34 | 35 | extern SettingsController Settings; 36 | -------------------------------------------------------------------------------- /src/thirdparty/keyboard.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | THIS FILE IS A PART OF RDR 2 SCRIPT HOOK SDK 3 | http://dev-c.com 4 | (C) Alexander Blade 2019 5 | */ 6 | 7 | #include "keyboard.h" 8 | 9 | const int KEYS_SIZE = 255; 10 | 11 | struct { 12 | DWORD time; 13 | BOOL isWithAlt; 14 | BOOL wasDownBefore; 15 | BOOL isUpNow; 16 | } keyStates[KEYS_SIZE]; 17 | 18 | const char *KeyNames[KEYS_SIZE] = { 19 | NULL, "VK_LBUTTON", "VK_RBUTTON", "VK_CANCEL", "VK_MBUTTON", 20 | "VK_XBUTTON1", "VK_XBUTTON2", NULL, "VK_BACK", "VK_TAB", 21 | NULL, NULL, "VK_CLEAR", "VK_RETURN", NULL, NULL, 22 | "VK_SHIFT", "VK_CONTROL", "VK_MENU", "VK_PAUSE", "VK_CAPITAL", 23 | "VK_KANA", NULL, "VK_JUNJA", "VK_FINAL", "VK_KANJI", NULL, 24 | "VK_ESCAPE", "VK_CONVERT", "VK_NONCONVERT", "VK_ACCEPT", 25 | "VK_MODECHANGE", "VK_SPACE", "VK_PRIOR", "VK_NEXT", "VK_END", 26 | "VK_HOME", "VK_LEFT", "VK_UP", "VK_RIGHT", "VK_DOWN", 27 | "VK_SELECT", "VK_PRINT", "VK_EXECUTE", "VK_SNAPSHOT", 28 | "VK_INSERT", "VK_DELETE", "VK_HELP", "VK_0", "VK_1", "VK_2", 29 | "VK_3", "VK_4", "VK_5", "VK_6", "VK_7", "VK_8", "VK_9", 30 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, "VK_A", "VK_B", 31 | "VK_C", "VK_D", "VK_E", "VK_F", "VK_G", "VK_H", "VK_I", 32 | "VK_J", "VK_K", "VK_L", "VK_M", "VK_N", "VK_O", "VK_P", 33 | "VK_Q", "VK_R", "VK_S", "VK_T", "VK_U", "VK_V", "VK_W", 34 | "VK_X", "VK_Y", "VK_Z", "VK_LWIN", "VK_RWIN", "VK_APPS", 35 | NULL, "VK_SLEEP", "VK_NUMPAD0", "VK_NUMPAD1", "VK_NUMPAD2", 36 | "VK_NUMPAD3", "VK_NUMPAD4", "VK_NUMPAD5", "VK_NUMPAD6", 37 | "VK_NUMPAD7", "VK_NUMPAD8", "VK_NUMPAD9", "VK_MULTIPLY", 38 | "VK_ADD", "VK_SEPARATOR", "VK_SUBTRACT", "VK_DECIMAL", 39 | "VK_DIVIDE", "VK_F1", "VK_F2", "VK_F3", "VK_F4", "VK_F5", 40 | "VK_F6", "VK_F7", "VK_F8", "VK_F9", "VK_F10", "VK_F11", 41 | "VK_F12", "VK_F13", "VK_F14", "VK_F15", "VK_F16", "VK_F17", 42 | "VK_F18", "VK_F19", "VK_F20", "VK_F21", "VK_F22", "VK_F23", 43 | "VK_F24", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 44 | "VK_NUMLOCK", "VK_SCROLL", NULL, NULL, NULL, NULL, NULL, 45 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 46 | "VK_LSHIFT", "VK_RSHIFT", "VK_LCONTROL", "VK_RCONTROL", 47 | "VK_LMENU", "VK_RMENU", "VK_BROWSER_BACK", "VK_BROWSER_FORWARD", 48 | "VK_BROWSER_REFRESH", "VK_BROWSER_STOP", "VK_BROWSER_SEARCH", 49 | "VK_BROWSER_FAVORITES", "VK_BROWSER_HOME", "VK_VOLUME_MUTE", 50 | "VK_VOLUME_DOWN", "VK_VOLUME_UP", "VK_MEDIA_NEXT_TRACK", 51 | "VK_MEDIA_PREV_TRACK", "VK_MEDIA_STOP", "VK_MEDIA_PLAY_PAUSE", 52 | "VK_LAUNCH_MAIL", "VK_MEDIA_SELECT", "VK_LAUNCH_APP1", 53 | "VK_LAUNCH_APP2", NULL, NULL, "VK_OEM_1", "VK_OEM_PLUS", 54 | "VK_OEM_COMMA", "VK_OEM_MINUS", "VK_OEM_PERIOD", "VK_OEM_2", 55 | "VK_OEM_3", "VK_ABNT_C1", "VK_ABNT_C2", NULL, NULL, NULL, NULL, 56 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 57 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "VK_OEM_4", 58 | "VK_OEM_5", "VK_OEM_6", "VK_OEM_7", "VK_OEM_8", NULL, NULL, 59 | "VK_OEM_102", NULL, NULL, "VK_PROCESSKEY", NULL, "VK_PACKET", NULL, 60 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 61 | NULL, NULL, "VK_ATTN", "VK_CRSEL", "VK_EXSEL", "VK_EREOF", "VK_PLAY", 62 | "VK_ZOOM", "VK_NONAME", "VK_PA1", "VK_OEM_CLEAR" 63 | }; 64 | 65 | void OnKeyboardMessage(DWORD key, WORD repeats, BYTE scanCode, BOOL isExtended, BOOL isWithAlt, BOOL wasDownBefore, BOOL isUpNow) 66 | { 67 | if (key < KEYS_SIZE) 68 | { 69 | keyStates[key].time = GetTickCount(); 70 | keyStates[key].isWithAlt = isWithAlt; 71 | keyStates[key].wasDownBefore = wasDownBefore; 72 | keyStates[key].isUpNow = isUpNow; 73 | } 74 | } 75 | 76 | const int NOW_PERIOD = 100, MAX_DOWN = 5000, MAX_DOWN_LONG = 30000; // ms 77 | 78 | bool IsKeyDown(DWORD key) 79 | { 80 | return (key < KEYS_SIZE) ? ((GetTickCount() < keyStates[key].time + MAX_DOWN) && !keyStates[key].isUpNow) : false; 81 | } 82 | 83 | bool IsKeyDownLong(DWORD key) 84 | { 85 | return (key < KEYS_SIZE) ? ((GetTickCount() < keyStates[key].time + MAX_DOWN_LONG) && !keyStates[key].isUpNow) : false; 86 | } 87 | 88 | bool IsKeyJustUp(DWORD key, bool exclusive) 89 | { 90 | bool b = (key < KEYS_SIZE) ? (GetTickCount() < keyStates[key].time + NOW_PERIOD && keyStates[key].isUpNow) : false; 91 | if (b && exclusive) 92 | ResetKeyState(key); 93 | return b; 94 | } 95 | 96 | void ResetKeyState(DWORD key) 97 | { 98 | if (key < KEYS_SIZE) 99 | memset(&keyStates[key], 0, sizeof(keyStates[0])); 100 | } 101 | -------------------------------------------------------------------------------- /src/thirdparty/keyboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | THIS FILE IS A PART OF RDR 2 SCRIPT HOOK SDK 3 | http://dev-c.com 4 | (C) Alexander Blade 2019 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | extern const char *KeyNames[]; 12 | 13 | void OnKeyboardMessage(DWORD key, WORD repeats, BYTE scanCode, BOOL isExtended, BOOL isWithAlt, BOOL wasDownBefore, BOOL isUpNow); 14 | 15 | bool IsKeyDown(DWORD key); 16 | bool IsKeyDownLong(DWORD key); 17 | bool IsKeyJustUp(DWORD key, bool exclusive = true); 18 | void ResetKeyState(DWORD key); 19 | -------------------------------------------------------------------------------- /src/thirdparty/scriptmenu.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | THIS FILE IS A PART OF RDR 2 SCRIPT HOOK SDK 3 | http://dev-c.com 4 | (C) Alexander Blade 2019 5 | */ 6 | 7 | #include "scriptmenu.h" 8 | 9 | void DrawString(float x, float y, char *str) 10 | { 11 | NATIVES::DRAW_TEXT(NATIVES::CREATE_STRING(10, "LITERAL_STRING", str), x, y); 12 | } 13 | 14 | void DrawRect(float lineLeft, float lineTop, float lineWidth, float lineHeight, int r, int g, int b, int a) 15 | { 16 | NATIVES::DRAW_RECT((lineLeft + (lineWidth * 0.5f)), (lineTop + (lineHeight * 0.5f)), lineWidth, lineHeight, r, g, b, a, 0, 0); 17 | } 18 | 19 | void MenuItemBase::WaitAndDraw(int ms) 20 | { 21 | DWORD time = GetTickCount() + ms; 22 | bool waited = false; 23 | while (GetTickCount() < time || !waited) 24 | { 25 | WAIT(0); 26 | waited = true; 27 | if (auto menu = GetMenu()) 28 | menu->OnDraw(); 29 | } 30 | } 31 | 32 | void MenuItemBase::SetStatusText(string text, int ms) 33 | { 34 | MenuController *controller; 35 | if (m_menu && (controller = m_menu->GetController())) 36 | controller->SetStatusText(text, ms); 37 | } 38 | 39 | void MenuItemBase::OnDraw(float lineTop, float lineLeft, bool active) 40 | { 41 | // text 42 | ColorRgba color = active ? m_colorTextActive : m_colorText; 43 | NATIVES::SET_TEXT_SCALE(0.0, m_lineHeight * 8.0f); 44 | NATIVES::SET_TEXT_COLOR_RGBA(color.r, color.g, color.b, color.a); 45 | NATIVES::SET_TEXT_CENTRE(0); 46 | NATIVES::SET_TEXT_DROPSHADOW(0, 0, 0, 0, 0); 47 | DrawString(lineLeft + m_textLeft, lineTop + m_lineHeight / 4.5f, const_cast(GetCaption().c_str())); 48 | // rect 49 | color = active ? m_colorRectActive : m_colorRect; 50 | DrawRect(lineLeft, lineTop, m_lineWidth, m_lineHeight, color.r, color.g, color.b, color.a); 51 | } 52 | 53 | void MenuItemSwitchable::OnDraw(float lineTop, float lineLeft, bool active) 54 | { 55 | MenuItemDefault::OnDraw(lineTop, lineLeft, active); 56 | float lineWidth = GetLineWidth(); 57 | float lineHeight = GetLineHeight(); 58 | ColorRgba color = active ? GetColorTextActive() : GetColorText(); 59 | NATIVES::SET_TEXT_SCALE(0.0, lineHeight * 8.0f); 60 | NATIVES::SET_TEXT_COLOR_RGBA(color.r, color.g, color.b, static_cast(color.a / 1.1f)); 61 | NATIVES::SET_TEXT_CENTRE(0); 62 | NATIVES::SET_TEXT_DROPSHADOW(0, 0, 0, 0, 0); 63 | DrawString(lineLeft + lineWidth - lineWidth / 6.35f, lineTop + lineHeight / 4.8f, GetState() ? "[Y]" : "[N]"); 64 | } 65 | 66 | void MenuItemMenu::OnDraw(float lineTop, float lineLeft, bool active) 67 | { 68 | MenuItemDefault::OnDraw(lineTop, lineLeft, active); 69 | float lineWidth = GetLineWidth(); 70 | float lineHeight = GetLineHeight(); 71 | ColorRgba color = active ? GetColorTextActive() : GetColorText(); 72 | NATIVES::SET_TEXT_SCALE(0.0, lineHeight * 8.0f); 73 | NATIVES::SET_TEXT_COLOR_RGBA(color.r, color.g, color.b, color.a / 2); 74 | NATIVES::SET_TEXT_CENTRE(0); 75 | NATIVES::SET_TEXT_DROPSHADOW(0, 0, 0, 0, 0); 76 | DrawString(lineLeft + lineWidth - lineWidth / 8, lineTop + lineHeight / 3.5f, "*"); 77 | } 78 | 79 | void MenuItemMenu::OnSelect() 80 | { 81 | if (auto parentMenu = GetMenu()) 82 | if (auto controller = parentMenu->GetController()) 83 | controller->PushMenu(m_menu); 84 | } 85 | 86 | void MenuBase::OnDraw() 87 | { 88 | float lineTop = MenuBase_menuTop; 89 | float lineLeft = MenuBase_menuLeft[m_controller->GetCurrentPosition()]; 90 | if (m_itemTitle->GetClass() == eMenuItemClass::ListTitle) 91 | reinterpret_cast(m_itemTitle)-> 92 | SetCurrentItemInfo(GetActiveItemIndex() + 1, static_cast(m_items.size())); 93 | m_itemTitle->OnDraw(lineTop, lineLeft, false); 94 | lineTop += m_itemTitle->GetLineHeight(); 95 | for (int i = 0; i < MenuBase_linesPerScreen; i++) 96 | { 97 | int itemIndex = m_activeScreenIndex * MenuBase_linesPerScreen + i; 98 | if (itemIndex == m_items.size()) 99 | break; 100 | MenuItemBase *item = m_items[itemIndex]; 101 | item->OnDraw(lineTop, lineLeft, m_activeLineIndex == i); 102 | lineTop += item->GetLineHeight() - item->GetLineHeight() * MenuBase_lineOverlap; 103 | } 104 | } 105 | 106 | int MenuBase::OnInput() 107 | { 108 | const int itemCount = static_cast(m_items.size()); 109 | const int itemsLeft = itemCount % MenuBase_linesPerScreen; 110 | const int screenCount = itemCount / MenuBase_linesPerScreen + (itemsLeft ? 1 : 0); 111 | const int lineCountLastScreen = itemsLeft ? itemsLeft : MenuBase_linesPerScreen; 112 | 113 | auto buttons = MenuInput::GetButtonState(); 114 | 115 | int waitTime = 0; 116 | 117 | if (buttons.a || buttons.b || buttons.up || buttons.down) 118 | { 119 | MenuInput::MenuInputBeep(); 120 | waitTime = buttons.b ? 200 : 150; 121 | } 122 | 123 | if (buttons.a) 124 | { 125 | int activeItemIndex = GetActiveItemIndex(); 126 | m_items[activeItemIndex]->OnSelect(); 127 | } else 128 | if (buttons.b) 129 | { 130 | if (auto controller = GetController()) 131 | controller->PopMenu(); 132 | } else 133 | if (buttons.up) 134 | { 135 | if (m_activeLineIndex-- == 0) 136 | { 137 | if (m_activeScreenIndex == 0) 138 | { 139 | m_activeScreenIndex = screenCount - 1; 140 | m_activeLineIndex = lineCountLastScreen - 1; 141 | } else 142 | { 143 | m_activeScreenIndex--; 144 | m_activeLineIndex = MenuBase_linesPerScreen - 1; 145 | } 146 | } 147 | } else 148 | if (buttons.down) 149 | { 150 | m_activeLineIndex++; 151 | if (m_activeLineIndex == ((m_activeScreenIndex == (screenCount - 1)) ? lineCountLastScreen : MenuBase_linesPerScreen)) 152 | { 153 | if (m_activeScreenIndex == screenCount - 1) 154 | m_activeScreenIndex = 0; 155 | else 156 | m_activeScreenIndex++; 157 | m_activeLineIndex = 0; 158 | } 159 | } 160 | 161 | return waitTime; 162 | } 163 | 164 | MenuBase::~MenuBase() 165 | { 166 | if (m_controller) 167 | m_controller->UnregisterMenu(this); 168 | for each (auto item in m_items) 169 | delete item; 170 | } 171 | -------------------------------------------------------------------------------- /src/thirdparty/scriptmenu.h: -------------------------------------------------------------------------------- 1 | /* 2 | THIS FILE IS A PART OF RDR 2 SCRIPT HOOK SDK 3 | http://dev-c.com 4 | (C) Alexander Blade 2019 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "scripthook.hpp" 10 | #include "constants.hpp" 11 | 12 | #include "natives.hpp" 13 | #include "settingsctl.hpp" 14 | 15 | #include "keyboard.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace std; 22 | 23 | class MenuBase; 24 | class MenuController; 25 | 26 | struct ColorRgba 27 | { 28 | UCHAR r, g, b, a; 29 | }; 30 | 31 | enum eMenuItemClass 32 | { 33 | Base, 34 | Title, 35 | ListTitle, 36 | Default, 37 | Switchable, 38 | Menu 39 | }; 40 | 41 | class MenuItemBase 42 | { 43 | float m_lineWidth; 44 | float m_lineHeight; 45 | float m_textLeft; 46 | ColorRgba m_colorRect; 47 | ColorRgba m_colorText; 48 | ColorRgba m_colorRectActive; 49 | ColorRgba m_colorTextActive; 50 | 51 | MenuBase * m_menu; 52 | protected: 53 | MenuItemBase( 54 | float lineWidth, float lineHeight, float textLeft, 55 | ColorRgba colorRect, ColorRgba colorText, 56 | ColorRgba colorRectActive = {}, ColorRgba colorTextActive = {}) 57 | : m_lineWidth(lineWidth), m_lineHeight(lineHeight), m_textLeft(textLeft), 58 | m_colorRect(colorRect), m_colorText(colorText), 59 | m_colorRectActive(colorRectActive), m_colorTextActive(colorTextActive) {} 60 | void WaitAndDraw(int ms); 61 | void SetStatusText(string text, int ms = 2500); 62 | public: 63 | virtual ~MenuItemBase() {} 64 | 65 | virtual eMenuItemClass GetClass() { return eMenuItemClass::Base; } 66 | virtual void OnDraw(float lineTop, float lineLeft, bool active); 67 | virtual void OnSelect() {} 68 | virtual void OnFrame() {} 69 | virtual string GetCaption() { return ""; } 70 | 71 | float GetLineWidth() { return m_lineWidth; } 72 | float GetLineHeight() { return m_lineHeight; } 73 | 74 | ColorRgba GetColorRect() { return m_colorRect; } 75 | ColorRgba GetColorText() { return m_colorText; } 76 | 77 | ColorRgba GetColorRectActive() { return m_colorRectActive; } 78 | ColorRgba GetColorTextActive() { return m_colorTextActive; } 79 | 80 | void SetMenu(MenuBase *menu) { m_menu = menu; }; 81 | MenuBase *GetMenu() { return m_menu; }; 82 | }; 83 | 84 | const float 85 | MenuItemTitle_lineWidth = 0.22f, 86 | MenuItemTitle_lineHeight = 0.06f, 87 | MenuItemTitle_textLeft = 0.01f; 88 | 89 | const ColorRgba 90 | MenuItemTitle_colorRect { 0, 0, 0, 255 }, 91 | MenuItemTitle_colorText { 187, 50, 50, 200 }; 92 | 93 | class MenuItemTitle : public MenuItemBase 94 | { 95 | string m_caption; 96 | public: 97 | MenuItemTitle(string caption) 98 | : MenuItemBase( 99 | MenuItemTitle_lineWidth, MenuItemTitle_lineHeight, MenuItemTitle_textLeft, 100 | MenuItemTitle_colorRect, MenuItemTitle_colorText 101 | ), 102 | m_caption(caption) {} 103 | virtual eMenuItemClass GetClass() { return eMenuItemClass::Title; } 104 | virtual string GetCaption() { return m_caption; } 105 | }; 106 | 107 | class MenuItemListTitle : public MenuItemTitle 108 | { 109 | int m_currentItemIndex; 110 | int m_itemsTotal; 111 | public: 112 | MenuItemListTitle(string caption) 113 | : MenuItemTitle(caption), 114 | m_currentItemIndex(0), m_itemsTotal(0) {} 115 | virtual eMenuItemClass GetClass() { return eMenuItemClass::ListTitle; } 116 | virtual string GetCaption() { return MenuItemTitle::GetCaption() + " " + to_string(m_currentItemIndex) + "/" + to_string(m_itemsTotal); } 117 | void SetCurrentItemInfo(int index, int total) { m_currentItemIndex = index, m_itemsTotal = total; } 118 | }; 119 | 120 | const float 121 | MenuItemDefault_lineWidth = 0.22f, 122 | MenuItemDefault_lineHeight = 0.05f, 123 | MenuItemDefault_textLeft = 0.01f; 124 | 125 | const ColorRgba 126 | MenuItemDefault_colorRect { 70, 70, 70, 150 }, 127 | MenuItemDefault_colorText { 255, 255, 255, 150 }, 128 | MenuItemDefault_colorRectActive { 120, 95, 95, 200 }, 129 | MenuItemDefault_colorTextActive { 0, 0, 0, 200 }; 130 | 131 | class MenuItemDefault : public MenuItemBase 132 | { 133 | string m_caption; 134 | public: 135 | MenuItemDefault(string caption) 136 | : MenuItemBase( 137 | MenuItemDefault_lineWidth, MenuItemDefault_lineHeight, MenuItemDefault_textLeft, 138 | MenuItemDefault_colorRect, MenuItemDefault_colorText, MenuItemDefault_colorRectActive, MenuItemDefault_colorTextActive 139 | ), 140 | m_caption(caption) {} 141 | virtual eMenuItemClass GetClass() { return eMenuItemClass::Default; } 142 | virtual string GetCaption() { return m_caption; } 143 | }; 144 | 145 | class MenuItemSwitchable : public MenuItemDefault 146 | { 147 | bool m_state; 148 | public: 149 | MenuItemSwitchable(string caption, bool initial = false) 150 | : MenuItemDefault(caption), 151 | m_state(initial) {} 152 | virtual eMenuItemClass GetClass() { return eMenuItemClass::Switchable; } 153 | virtual void OnDraw(float lineTop, float lineLeft, bool active); 154 | virtual void OnSelect() { m_state = !m_state; } 155 | void SetState(bool state) { m_state = state; } 156 | bool GetState() { return m_state; } 157 | }; 158 | 159 | class MenuItemMenu : public MenuItemDefault 160 | { 161 | MenuBase * m_menu; 162 | public: 163 | MenuItemMenu(string caption, MenuBase *menu) 164 | : MenuItemDefault(caption), 165 | m_menu(menu) {} 166 | virtual eMenuItemClass GetClass() { return eMenuItemClass::Menu; } 167 | virtual void OnDraw(float lineTop, float lineLeft, bool active); 168 | virtual void OnSelect(); 169 | }; 170 | 171 | const int 172 | MenuBase_linesPerScreen = 11; 173 | 174 | const float 175 | MenuBase_menuTop = 0.05f, 176 | MenuBase_menuLeft[] = {0.00f, 0.39f, 0.78f}, 177 | MenuBase_lineOverlap = 1.0f / 40.0f; 178 | 179 | class MenuBase 180 | { 181 | MenuItemTitle * m_itemTitle; 182 | vector m_items; 183 | 184 | int m_activeLineIndex; 185 | int m_activeScreenIndex; 186 | 187 | MenuController * m_controller; 188 | public: 189 | MenuBase(MenuItemTitle *itemTitle) 190 | : m_itemTitle(itemTitle), 191 | m_activeLineIndex(0), 192 | m_activeScreenIndex(0) {} 193 | virtual ~MenuBase(); 194 | 195 | void AddItem(MenuItemBase *item) { item->SetMenu(this); m_items.push_back(item); } 196 | int GetActiveItemIndex() { return m_activeScreenIndex * MenuBase_linesPerScreen + m_activeLineIndex; } 197 | void OnDraw(); 198 | int OnInput(); 199 | void OnFrame() 200 | { 201 | for (size_t i = 0; i < m_items.size(); i++) 202 | m_items[i]->OnFrame(); 203 | } 204 | virtual void OnPop() {} 205 | void SetController(MenuController *controller) { m_controller = controller; } 206 | MenuController *GetController() { return m_controller; } 207 | }; 208 | 209 | struct MenuInputButtonState 210 | { 211 | bool a, b, up, down, l, r; 212 | }; 213 | 214 | class MenuInput 215 | { 216 | public: 217 | static bool MenuSwitchPressed() 218 | { 219 | return IsKeyJustUp(Settings.Read("menu_hotkey", REDLUA_HOTKEY_DEFAULT)); 220 | } 221 | static MenuInputButtonState GetButtonState() 222 | { 223 | return { 224 | IsKeyDown(VK_NUMPAD5) || (IsKeyDownLong(VK_CONTROL) && IsKeyDown(VK_RETURN)), 225 | IsKeyDown(VK_NUMPAD0) || IsKeyDown(VK_BACK), 226 | IsKeyDown(VK_NUMPAD8) || (IsKeyDownLong(VK_CONTROL) && IsKeyDown(VK_UP)), 227 | IsKeyDown(VK_NUMPAD2) || (IsKeyDownLong(VK_CONTROL) && IsKeyDown(VK_DOWN)), 228 | IsKeyDown(VK_NUMPAD6) || (IsKeyDownLong(VK_CONTROL) && IsKeyDown(VK_RIGHT)), 229 | IsKeyDown(VK_NUMPAD4) || (IsKeyDownLong(VK_CONTROL) && IsKeyDown(VK_LEFT)) 230 | }; 231 | } 232 | static void MenuInputBeep() 233 | { 234 | # ifdef REDLUA_GTAV 235 | NATIVES::PLAY_SOUND_FRONTEND("NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", 0); 236 | #else 237 | NATIVES::STOP_SOUND_FRONTEND("NAV_RIGHT", "HUD_SHOP_SOUNDSET"); 238 | NATIVES::PLAY_SOUND_FRONTEND("NAV_RIGHT", "HUD_SHOP_SOUNDSET", 1, 0); 239 | # endif 240 | } 241 | }; 242 | 243 | class MenuController 244 | { 245 | vector m_menuList; 246 | vector m_menuStack; 247 | 248 | DWORD m_inputTurnOnTime; 249 | DWORD m_currentMenuPosition; 250 | 251 | bool m_rebuildRequested; 252 | 253 | void InputWait(int ms) { m_inputTurnOnTime = GetTickCount() + ms; } 254 | bool InputIsOnWait() { return m_inputTurnOnTime > GetTickCount(); } 255 | MenuBase *GetActiveMenu() { return m_menuStack.size() ? m_menuStack[m_menuStack.size() - 1] : NULL; } 256 | void OnDraw() 257 | { 258 | if (auto menu = GetActiveMenu()) 259 | menu->OnDraw(); 260 | } 261 | void OnInput() 262 | { 263 | if (InputIsOnWait()) 264 | return; 265 | if (auto menu = GetActiveMenu()) 266 | if (int waitTime = menu->OnInput()) 267 | InputWait(waitTime); 268 | } 269 | void OnFrame() 270 | { 271 | for (auto i = 0; i < m_menuList.size(); i++) 272 | m_menuList[i]->OnFrame(); 273 | } 274 | public: 275 | MenuController() 276 | : m_inputTurnOnTime(0), 277 | m_currentMenuPosition(0), 278 | m_rebuildRequested(true) {} 279 | ~MenuController() 280 | { 281 | for each (auto menu in m_menuList) 282 | delete menu; 283 | } 284 | DWORD GetCurrentPosition() { return m_currentMenuPosition; } 285 | void SetCurrentPosition(DWORD v) { m_currentMenuPosition = v % 3; } 286 | bool HasActiveMenu() { return m_menuStack.size() > 0; } 287 | void PushMenu(MenuBase *menu) { if (IsMenuRegistered(menu)) m_menuStack.push_back(menu); } 288 | void PopMenu() { if (m_menuStack.size()) { m_menuStack.back()->OnPop(); m_menuStack.pop_back(); } } 289 | void PopMenu(size_t count) { if (count == 0) count = m_menuStack.size(); for (size_t i = 0; i < count; i++) PopMenu(); } 290 | void SetStatusText(string text, int ms = 2500) { NATIVES::NOTIFY(1, ms, text.c_str()); } 291 | bool IsRebuildRequested() { return m_rebuildRequested; } 292 | void RequestRebuild() { m_rebuildRequested = true; } 293 | void RebuildDone() { m_rebuildRequested = false; } 294 | bool IsMenuRegistered(MenuBase *menu) 295 | { 296 | for (size_t i = 0; i < m_menuList.size(); i++) 297 | if (m_menuList[i] == menu) 298 | return true; 299 | return false; 300 | } 301 | void RegisterMenu(MenuBase *menu) 302 | { 303 | if (!IsMenuRegistered(menu)) 304 | { 305 | menu->SetController(this); 306 | m_menuList.push_back(menu); 307 | } 308 | } 309 | void UnregisterMenu(MenuBase *menu) 310 | { 311 | for (size_t i = 0; i < m_menuList.size(); i++) { 312 | if (m_menuList[i] == menu) 313 | { 314 | m_menuList.erase(m_menuList.begin() + i); 315 | size_t stackSize = m_menuStack.size(); 316 | if (stackSize > 0) { 317 | for (size_t j = stackSize - 2; j > 0; j--) 318 | if (!IsMenuRegistered(m_menuStack[j])) 319 | m_menuStack.erase(m_menuStack.begin() + j); 320 | } 321 | break; 322 | } 323 | } 324 | } 325 | void UnregisterAll() 326 | { 327 | m_menuStack.clear(); 328 | for (size_t i = 0; i < m_menuList.size(); i++) { 329 | delete m_menuList.back(); 330 | m_menuList.pop_back(); 331 | } 332 | } 333 | void Update() 334 | { 335 | OnDraw(); 336 | OnInput(); 337 | OnFrame(); 338 | } 339 | }; 340 | 341 | -------------------------------------------------------------------------------- /src/updatesctl.cpp: -------------------------------------------------------------------------------- 1 | #include "updatesctl.hpp" 2 | #include "constants.hpp" 3 | #include "settingsctl.hpp" 4 | #include "langctl.hpp" 5 | #include "json.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | UpdatesController UpdatesCtl; 12 | 13 | static HINTERNET hInternet = NULL; 14 | 15 | bool UpdatesController::Prepare(void) { 16 | if (!hInternet) 17 | hInternet = InternetOpen(REDLUA_NAME "/" REDLUA_VERSION, INTERNET_OPEN_TYPE_PRECONFIG, 18 | NULL, NULL, 0); 19 | return hInternet != NULL; 20 | } 21 | 22 | void UpdatesController::Stop(void) { 23 | if (hInternet) InternetCloseHandle(hInternet); 24 | } 25 | 26 | static HINTERNET openRequest(std::string url, std::string headers) { 27 | return InternetOpenUrl(hInternet, url.c_str(), headers.c_str(), headers.length(), 28 | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_PRAGMA_NOCACHE, 0); 29 | } 30 | 31 | UpdatesController::Returns UpdatesController::CheckRedLua(std::string &vername) { 32 | if (!Prepare()) return ERR_WININET_INIT; 33 | 34 | vername = ""; 35 | Returns code = ERR_NO_UPDATES; 36 | std::string temp; 37 | static const DWORD BUFSIZE = 1024; 38 | BYTE buf[BUFSIZE]; 39 | DWORD read = 0; 40 | 41 | HINTERNET hRequest; 42 | do { 43 | if ((hRequest = openRequest(REDLUA_TAGS_URL, "Accept: application/json")) == NULL) { 44 | code = ERR_OPEN_REQUEST; 45 | break; 46 | } 47 | 48 | do { 49 | if (!InternetReadFile(hRequest, buf, BUFSIZE, &read)) { 50 | code = ERR_READ_RESPONSE; 51 | break; 52 | } 53 | 54 | temp.append((const char *)buf, read); 55 | } while (read > 0); 56 | if (code != ERR_NO_UPDATES) break; 57 | 58 | json jdata = json::parse(temp, nullptr, false); 59 | if (jdata.is_discarded()) { 60 | code = ERR_MALFORMED_JSON; 61 | break; 62 | } 63 | 64 | int curr_rel = -1; 65 | if (jdata.is_array()) { 66 | for (auto &x : jdata.items()) { 67 | json &jname = x.value()["name"]; 68 | if (jname.is_string()) { 69 | int vidx = std::strtoul(x.key().c_str(), nullptr, 10); 70 | if (jname.get_to(temp) == REDLUA_VERSION) 71 | curr_rel = vidx; 72 | if (vidx == 0) 73 | vername = temp; 74 | } 75 | } 76 | } else if (jdata.is_object() && jdata["message"].is_string()) { 77 | code = ERR_MALFORMED_JSON; 78 | break; 79 | } 80 | 81 | if (curr_rel != 0 && vername != "") 82 | code = OK; 83 | else if (curr_rel == 0) 84 | code = ERR_NO_UPDATES; 85 | 86 | } while (0); 87 | 88 | InternetCloseHandle(hRequest); 89 | return code; 90 | } 91 | 92 | UpdatesController::Returns UpdatesController::CheckNativeDB(bool force_update) { 93 | if (!Prepare()) return ERR_WININET_INIT; 94 | 95 | Returns code = ERR_NO_UPDATES; 96 | FILE *file = NULL; 97 | HINTERNET hRequest; 98 | 99 | do { 100 | static const DWORD BUFSIZE = 1024; 101 | BYTE buf[BUFSIZE]; 102 | DWORD read = 0; 103 | std::string etag = "W/\"\"", 104 | headers = "Accept: application/json"; 105 | 106 | if (!force_update) 107 | headers.append("\r\nIf-None-Match: " + Settings.Read("nativedb_etag", etag)); 108 | if ((hRequest = openRequest(REDLUA_NATIVEDB_URL, headers)) == NULL) { 109 | code = ERR_OPEN_REQUEST; 110 | break; 111 | } 112 | 113 | DWORD status = 0; DWORD statuslen = sizeof(DWORD); 114 | if (!HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &statuslen, NULL)) { 115 | code = ERR_QUERY_INFO; 116 | break; 117 | } 118 | 119 | if (status == 304) 120 | break; 121 | 122 | if (status != 200) { 123 | code = ERR_UNKNOWN_RESPONSE; 124 | break; 125 | } 126 | 127 | if ((file = fopen(REDLUA_NATIVES_FILE, "w")) == NULL) { 128 | code = ERR_IO_ISSUE; 129 | break; 130 | } 131 | 132 | DWORD bufsize = BUFSIZE; 133 | if (!HttpQueryInfoA(hRequest, HTTP_QUERY_ETAG, buf, &bufsize, NULL)) { 134 | code = ERR_QUERY_INFO; 135 | break; 136 | } 137 | 138 | etag = std::string{(const char *)buf, bufsize}; 139 | Settings.Write("nativedb_etag", etag); 140 | int errorcode = 0; 141 | do { 142 | if (!InternetReadFile(hRequest, buf, BUFSIZE, &read)) { 143 | code = ERR_READ_RESPONSE; 144 | break; 145 | } 146 | 147 | if (fwrite(buf, 1, read, file) != read) { 148 | code = ERR_IO_ISSUE; 149 | break; 150 | } 151 | } while (read > 0); 152 | } while (0); 153 | 154 | InternetCloseHandle(hRequest); 155 | if (file) fclose(file); 156 | return code; 157 | } 158 | -------------------------------------------------------------------------------- /src/updatesctl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class UpdatesController { 6 | bool Prepare(void); 7 | 8 | public: 9 | enum Returns { 10 | OK, 11 | ERR_WININET_INIT, 12 | ERR_OPEN_REQUEST, 13 | ERR_QUERY_INFO, 14 | ERR_UNKNOWN_RESPONSE, 15 | ERR_IO_ISSUE, 16 | ERR_READ_RESPONSE, 17 | ERR_MALFORMED_JSON, 18 | ERR_NO_UPDATES 19 | }; 20 | 21 | Returns CheckRedLua(std::string &vername); 22 | Returns CheckNativeDB(bool force_update = false); 23 | void Stop(void); 24 | }; 25 | 26 | extern UpdatesController UpdatesCtl; 27 | --------------------------------------------------------------------------------