├── .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