├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── Example.lua ├── README.md ├── minifier ├── .gitignore ├── Input.lua ├── main.js ├── minify.js ├── package-lock.json ├── package.json └── util.js ├── src ├── main.rs ├── obfuscation_settings.rs └── obfuscator │ ├── encryption │ ├── constant_encryption.rs │ └── mod.rs │ ├── mod.rs │ ├── obfuscation_context.rs │ ├── serializer.rs │ ├── vm │ ├── mod.rs │ ├── opcode_strings.rs │ └── vm_strings.rs │ └── vm_generator.rs └── vm ├── custom_vm.lua ├── out.lst └── vm.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /target 3 | /temp 4 | /build 5 | 6 | # Files used for input and output when testing 7 | Input.lua 8 | Out.lua -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/net6.0/LuaObfuscator.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "rust-lang.rust-analyzer", 3 | "[lua]": { 4 | "editor.defaultFormatter": "Koihik.vscode-lua-format" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/LuaObfuscator.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/LuaObfuscator.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/LuaObfuscator.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.7" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 49 | dependencies = [ 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys", 61 | ] 62 | 63 | [[package]] 64 | name = "cfg-if" 65 | version = "1.0.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 68 | 69 | [[package]] 70 | name = "clap" 71 | version = "4.5.7" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" 74 | dependencies = [ 75 | "clap_builder", 76 | "clap_derive", 77 | ] 78 | 79 | [[package]] 80 | name = "clap_builder" 81 | version = "4.5.7" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" 84 | dependencies = [ 85 | "anstream", 86 | "anstyle", 87 | "clap_lex", 88 | "strsim", 89 | ] 90 | 91 | [[package]] 92 | name = "clap_derive" 93 | version = "4.5.5" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" 96 | dependencies = [ 97 | "heck", 98 | "proc-macro2", 99 | "quote", 100 | "syn", 101 | ] 102 | 103 | [[package]] 104 | name = "clap_lex" 105 | version = "0.7.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 108 | 109 | [[package]] 110 | name = "colorchoice" 111 | version = "1.0.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 114 | 115 | [[package]] 116 | name = "getrandom" 117 | version = "0.2.8" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 120 | dependencies = [ 121 | "cfg-if", 122 | "libc", 123 | "wasi", 124 | ] 125 | 126 | [[package]] 127 | name = "heck" 128 | version = "0.5.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 131 | 132 | [[package]] 133 | name = "is_terminal_polyfill" 134 | version = "1.70.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 137 | 138 | [[package]] 139 | name = "libc" 140 | version = "0.2.139" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 143 | 144 | [[package]] 145 | name = "lua_deserializer" 146 | version = "0.1.1" 147 | source = "git+https://github.com/PY44N/LuaDeserializer#f21aef6f6fe76b7a2af6a15bf8dc9b802d7927d8" 148 | 149 | [[package]] 150 | name = "lua_obfuscator" 151 | version = "0.1.1" 152 | dependencies = [ 153 | "clap", 154 | "lua_deserializer", 155 | "rand", 156 | "regex", 157 | ] 158 | 159 | [[package]] 160 | name = "memchr" 161 | version = "2.5.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 164 | 165 | [[package]] 166 | name = "ppv-lite86" 167 | version = "0.2.17" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 170 | 171 | [[package]] 172 | name = "proc-macro2" 173 | version = "1.0.85" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 176 | dependencies = [ 177 | "unicode-ident", 178 | ] 179 | 180 | [[package]] 181 | name = "quote" 182 | version = "1.0.36" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 185 | dependencies = [ 186 | "proc-macro2", 187 | ] 188 | 189 | [[package]] 190 | name = "rand" 191 | version = "0.8.5" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 194 | dependencies = [ 195 | "libc", 196 | "rand_chacha", 197 | "rand_core", 198 | ] 199 | 200 | [[package]] 201 | name = "rand_chacha" 202 | version = "0.3.1" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 205 | dependencies = [ 206 | "ppv-lite86", 207 | "rand_core", 208 | ] 209 | 210 | [[package]] 211 | name = "rand_core" 212 | version = "0.6.4" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 215 | dependencies = [ 216 | "getrandom", 217 | ] 218 | 219 | [[package]] 220 | name = "regex" 221 | version = "1.8.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" 224 | dependencies = [ 225 | "aho-corasick", 226 | "memchr", 227 | "regex-syntax", 228 | ] 229 | 230 | [[package]] 231 | name = "regex-syntax" 232 | version = "0.7.1" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" 235 | 236 | [[package]] 237 | name = "strsim" 238 | version = "0.11.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 241 | 242 | [[package]] 243 | name = "syn" 244 | version = "2.0.66" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 247 | dependencies = [ 248 | "proc-macro2", 249 | "quote", 250 | "unicode-ident", 251 | ] 252 | 253 | [[package]] 254 | name = "unicode-ident" 255 | version = "1.0.12" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 258 | 259 | [[package]] 260 | name = "utf8parse" 261 | version = "0.2.2" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 264 | 265 | [[package]] 266 | name = "wasi" 267 | version = "0.11.0+wasi-snapshot-preview1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 270 | 271 | [[package]] 272 | name = "windows-sys" 273 | version = "0.52.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 276 | dependencies = [ 277 | "windows-targets", 278 | ] 279 | 280 | [[package]] 281 | name = "windows-targets" 282 | version = "0.52.5" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 285 | dependencies = [ 286 | "windows_aarch64_gnullvm", 287 | "windows_aarch64_msvc", 288 | "windows_i686_gnu", 289 | "windows_i686_gnullvm", 290 | "windows_i686_msvc", 291 | "windows_x86_64_gnu", 292 | "windows_x86_64_gnullvm", 293 | "windows_x86_64_msvc", 294 | ] 295 | 296 | [[package]] 297 | name = "windows_aarch64_gnullvm" 298 | version = "0.52.5" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 301 | 302 | [[package]] 303 | name = "windows_aarch64_msvc" 304 | version = "0.52.5" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 307 | 308 | [[package]] 309 | name = "windows_i686_gnu" 310 | version = "0.52.5" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 313 | 314 | [[package]] 315 | name = "windows_i686_gnullvm" 316 | version = "0.52.5" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 319 | 320 | [[package]] 321 | name = "windows_i686_msvc" 322 | version = "0.52.5" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 325 | 326 | [[package]] 327 | name = "windows_x86_64_gnu" 328 | version = "0.52.5" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 331 | 332 | [[package]] 333 | name = "windows_x86_64_gnullvm" 334 | version = "0.52.5" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 337 | 338 | [[package]] 339 | name = "windows_x86_64_msvc" 340 | version = "0.52.5" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 343 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lua_obfuscator" 3 | version = "0.1.1" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = "0.8.5" 10 | regex = "1.7.3" 11 | lua_deserializer = { git = "https://github.com/PY44N/LuaDeserializer", version = "0.1.1"} 12 | clap = { version = "4.5.7", features = ["derive"] } 13 | -------------------------------------------------------------------------------- /Example.lua: -------------------------------------------------------------------------------- 1 | local a=function(b,c)local d=string;local e=d.char;local f=d.byte;local g=d.sub;local h=d.reverse;local i=d.find;local j=function(k,l)local m,n=i(k,l)return m-b.a end;local o=function(...)local k=c.a;local p={...}for q=b.a,#p do k=k..p[q]end;return k end;local r=select;local s=table;local t=math;local u=error;local v=pairs;local w=ipairs;local x=s.concat;local y=s.insert;local function z(A,q,B)B=B or#A;q=q or b.a;if B>=b.b then return A[q],A[q+b.a],A[q+b.c],A[q+b.d],A[q+b.e],A[q+b.f],A[q+b.g],A[q+b.h],A[q+b.i],A[q+b.j],z(A,q+b.b,B-b.b)end;if q<=#A then return A[q],z(A,q+b.a,B-b.a)end end;local C=function(D)return{z({},b.a,D or b.a)}end;local E=function(...)return{n=r(e(b.k),...),...}end;local F=function(G,H,I,J,K)for q=b.l,I-H do K[J+q]=G[H+q]end end;local L=function(...)local M={}local N={...}for q=b.a,#N do for O=b.a,#N[q]do y(M,N[q][O])end end;return M end;local P=getfenv;local Q=t.floor;local R=t.max;local S=pcall;local T=t.abs;local U=tonumber;local V=function(W,X,Y)Y=Y or b.a;local Z=X and W or b.a;X=X or W;local m={}for q=Z,X,Y do y(m,q)end;return m end;local _=function()local function _0(_1,...)if(_1 or b.l)==b.l then return...end;return _0(Q(_1/b.c),_1%b.c,...)end;local function _2(_1)if _1==b.l then return{b.l}end;return{_0(_1)}end;local function _3(_4)local function _5(_1,_6,...)if not _6 then return _1 end;_1,_6=_2(_1),_2(_6)local _7,_8=#_1,#_6;local _9,_a={},R(_7,_8)for q=b.l,_a-b.a do local _b,_c=_1[_7-q],_6[_8-q]if not(_b or _c)then break end;_9[_a-q]=_4((_b or b.l)~=b.l,(_c or b.l)~=b.l)and b.a or b.l end;return _5(U(x(_9),b.c),...)end;return _5 end;local _d=_3(function(m,_e)return m and _e end)local function _f(_1,_g)return Q(_1)*b.c^_g end;local function _h(_1,_g)return Q(Q(_1)/b.c^_g)end;return _d,_h,_f end;local _i,_j,_k=_()local _l;local _m;local _n;local function _o(G,_p,_q,_r)local _s=b.l;for q=_p,_q,_r do local _t=b.m^T(q-_p)_s=_s+_t*f(G,q,q)end;return _s end;local function _u(_v,_w,_x,_y,_z,_A,_B,_C)local _D=(-b.a)^_j(_C,b.h)local _E=_k(_i(_C,b.n),b.e)+_j(_B,b.e)local _F=_i(_B,b.o)*b.c^b.p;local _G=b.a;_F=_F+_A*b.c^b.q+_z*b.c^b.r+_y*b.c^b.s+_x*b.c^b.t+_w*b.c^b.i+_v;if _E==b.l then if _F==b.l then return _D*b.l else _G=b.l;_E=b.a end elseif _E==b.u then if _F==b.l then return _D*b.a/b.l else return _D*b.l/b.l end end;return _D*b.c^(_E-b.v)*(_G+_F/b.c^b.w)end;local function _H(G,_p,_q)return _o(G,_p,_q-b.a,b.a)end;local function _I(G,_p)return _u(f(G,_p,_p+b.h))end;local function _J(_K)local _L=_K[b.a]local _M=f(_K[b.c],_L,_L)_K[b.a]=_L+b.a;return _M end;local function _N(_K,D)local _O=_K[b.a]+D;local k=g(_K[b.c],_K[b.a],_O-b.a)_K[b.a]=_O;return k end;local function _P(_K)local _O=_K[b.a]+b.c;local _Q=_H(_K[b.c],_K[b.a],_O)_K[b.a]=_O;return _Q end;local function _R(_K)local _O=_K[b.a]+b.e;local _Q=_H(_K[b.c],_K[b.a],_O)_K[b.a]=_O;return _Q end;local function _S(_K)local _O=_K[b.a]+b.i;local _Q=_H(_K[b.c],_K[b.a],_O)_K[b.a]=_O;return _Q end;local function _T(_K)local _U=_I(_K[b.c],_K[b.a])_K[b.a]=_K[b.a]+b.i;return _U end;local function _V(_K)local D=_S(_K)local k;if D~=b.l then k=g(_N(_K,D),b.a,-b.c)end;return k end;local function _W(_K)local D=_S(_K)local _X=C(D)for q=b.a,D do local _Y=_P(_K)local _Z=_i(_j(_Y,b.e),b.x)local __=_i(_j(_Y,b.c),b.d)local _00=_i(_j(_Y,b.a),b.a)==b.a;local _01=_i(_Y,b.a)==b.a;local _02={}_02[b.j]=_Z;_02[b.y]=_J(_K)if __==b.a then _02[b.z]=_P(_K)_02[b.f]=_P(_K)_02[b.i]=_00 and _02[b.z]>b.ab;_02[b.bb]=_01 and _02[b.f]>b.ab elseif __==b.c then _02[b.z]=_R(_K)_02[b.t]=_00 elseif __==b.d then _02[b.z]=_R(_K)-b.cb end;_X[q]=_02 end;return _X end;local function _03(_K,G)local D=_S(_K)local _X=C(D)for q=b.a,D do _X[q]=_n(_K,G)end;return _X end;local function _04(_K)local D=_S(_K)local _X=C(D)for q=b.a,D do local _05=_J(_K)local _06;if _05==b.l then _06=_J(_K)~=b.l elseif _05==b.c then _06=_T(_K)elseif _05==b.d then _06=_V(_K)end;_X[q]=_06 end;return _X end;function _n(_07,_08)local G=_V(_07)or _08;local _09={}_09[b.db]=G;_09[b.b]=_J(_07)_09[b.h]=_J(_07)_09[b.o]=_03(_07,G)_09[b.d]=_W(_07)_09[b.c]=_04(_07)for n,_0a in w(_09[b.d])do if _0a[b.t]then _0a[b.e]=_09[b.c][_0a[b.z]+b.a]else if _0a[b.i]then _0a[b.g]=_09[b.c][_0a[b.z]-b.ab]end;if _0a[b.bb]then _0a[b.eb]=_09[b.c][_0a[b.f]-b.ab]end end end;return _09 end;function _l(G)local _07={b.a,G}return _n(_07,c.a)end;local function _0b(_X,_0c)for q,_0d in v(_X)do if _0d[b.a]>=_0c then _X[q]=nil end end end;local function _0e(_X,_0c,_0f)local _0g=_X[_0c]if not _0g then _0g={_0c,_0f}_X[_0c]=_0g end;return _0g end;local function _0h(_0i,_0j)local G=_0i[b.c]local _0k=b.l;u(o(G,c.b,_0k,c.b,_0j),b.l)end;local function _0l(_0m,_0n,_0o)local _0p=_0m[b.d]local _0q=_0m[b.e]local _0r=_0m[b.a]local _0s=-b.a;local _0t={}local _0f=_0m[b.c]local _0u=_0m[b.f]local function _0v(_0w)return _0w[b.i]and _0w[b.g]or _0f[_0w[b.z]]end;local function _0x(_0w)return _0w[b.bb]and _0w[b.eb]or _0f[_0w[b.f]]end;while true do local _0w=_0p[_0u]local _Z=_0w[b.j]_0u=_0u+b.a;if _Z==b.l then local _0y=_0w[b.y]local _0z=_0w[b.z]local D;if _0z==b.l then D=_0s-_0y+b.a else D=_0z-b.a end;_0b(_0t,b.l)return z(_0f,_0y,_0y+D-b.a)elseif _Z==b.a then _0f[_0w[b.y]]=_0n[_0w[b.e]]elseif _Z==b.c then _0f[_0w[b.y]]=_0w[b.e]elseif _Z==b.d then local _0y=_0w[b.y]local _0z=_0w[b.z]local _0A=_0w[b.f]local _0B;if _0z==b.l then _0B=_0s-_0y else _0B=_0z-b.a end;local _0C=E(_0f[_0y](z(_0f,_0y+b.a,_0y+_0B)))local _0D=_0C.n;if _0A==b.l then _0s=_0y+_0D-b.a else _0D=_0A-b.a end;F(_0C,b.a,_0D,_0y,_0f)end;_0m[b.f]=_0u end end;function _m(_09,_0n,_0E)_0n=_0n or P(b.l)local function _0F(...)local _0G=E(...)local _0f=C()local _0r={b.l,{}}F(_0G,b.a,_09[b.h],b.l,_0f)if _09[b.h]<_0G.n then local Z=_09[b.h]+b.a;local D=_0G.n-_09[b.h]_0r[b.a]=D;F(_0G,Z,Z+D-b.a,b.a,_0r[b.c])end;local _0m={_0r,_0f,_09[b.d],_09[b.o],b.a}local _0H=E(S(_0l,_0m,_0n,_0E))if _0H[b.a]then return z(_0H,b.c,_0H.n)else local _0i={_0m[b.f],_09[b.db]}_0h(_0i,_0H[b.c])return end end;return _0F end;local _0I=e(z(L(V(b.p,b.fb),V(b.gb,b.hb))))local function _0J(_0K)local _s,k=b.l,h(_0K)for q=b.a,#k do _s=_s+j(_0I,g(k,q,q))*b.ib^(q-b.a)end;return _s end;local function _0L(_0M)local _0N,_0O,_0P,_0Q,_06={},b.m,c.a,e(_0M[b.a])local _0H={_0Q}for q=b.l,b.ab do _0N[q]=e(q)end;for q=b.c,#_0M do _06=_0M[q]if _0N[_06]then _0P=_0N[_06]elseif _06==_0O then _0P=_0Q..g(_0Q,b.a,b.a)else return nil,q end;y(_0H,_0P)_0N[_0O]=_0Q..g(_0P,b.a,b.a)_0O=_0O+b.a;_0Q=_0P end;return x(_0H)end;local function _0R(_0S)local _0T={}local q=b.a;while q<=#_0S do local D=_0J(g(_0S,q,q))q=q+b.a;y(_0T,_0J(g(_0S,q,q+D-b.a)))q=q+D end;return _0L(_0T)end;_m(_l(_0R(c.c)))()end;a({eb=17,f=5,o=15,t=16,bb=13,k=35,d=3,g=6,r=32,m=256,fb=57,j=9,y=11,i=8,ib=36,v=1023,x=63,a=1,p=48,ab=255,h=7,l=0,b=10,q=40,z=12,cb=131071,db=14,e=4,hb=90,c=2,s=24,u=2047,gb=65,w=52,n=127},{a=[[]],b=[[:]],c=[[1B102752761021S23822T23123421E21A23023922P27727J2751427K101Q27K21610111127621G2751227S1027M27Z27X27N131627N23423622X23223810131C27N22022T2302302331W22F23323623022S10]]}) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua Obfuscator V2 2 | 3 | This project is still a work in progress, so things may not work 4 | 5 | Please download the latest release if you are trying to run the code 6 | 7 | [Roadmap](https://pyan.notion.site/014c3553be6b45d1989e1e133ec2c424?v=acc453043e2844728d3db628693c100d) 8 | 9 | ## Example 10 | Input 11 | ```lua 12 | print("Hello World") 13 | ``` 14 | 15 | [Output](https://raw.githubusercontent.com/PY44N/LuaObfuscatorV2/master/Example.lua) 16 | 17 | ## How to use 18 | ### Required programs 19 | - [Nodejs](https://nodejs.org/en) 20 | - Lua 5.1 [windows](https://github.com/rjpcomputing/luaforwindows/releases/), [macos (with homebrew)](https://formulae.brew.sh/formula/lua@5.1#default), Linux (lua5.1 on most package managers) 21 | 22 | ### Running the latest release (windows only) 23 | 1) The [Latest Release](https://github.com/PY44N/LuaObfuscatorV2/releases/) 24 | 25 | 2) Open the terminal in the unzipped directory 26 | 27 | 3) Install the required nodejs packages 28 | ``` 29 | cd minifier && npm i && cd .. 30 | ``` 31 | 32 | 4) Put the code you wish to obfuscate into a file 33 | 34 | 5) Run the executable 35 | ``` 36 | ./lua_obfuscator.exe ./YOURFILE.lua 37 | ``` 38 | 39 | ### Building from source (may not work) 40 | 1) Download [Rust](https://www.rust-lang.org/) 41 | 42 | 2) Clone the repo 43 | ``` 44 | git clone https://github.com/PY44N/LuaObfuscator/ 45 | ``` 46 | 47 | 3) Enter the directory 48 | ``` 49 | cd LuaObfuscator 50 | ``` 51 | 52 | 4) Install the required nodejs packages 53 | ``` 54 | cd minifier && npm i && cd .. 55 | ``` 56 | 57 | 5) Put the code you wish to obfuscate into a file 58 | 59 | 6) Run the project using cargo 60 | ``` 61 | cargo run -- --file YOURFILE.lua 62 | ``` 63 | 64 | ## Related Repos 65 | [Lua Deserializer](https://github.com/PY44N/LuaDeserializer/) - A library for reading in a serialized Lua binary written for this project 66 | 67 | [luamin](https://github.com/mathiasbynens/luamin) (by Mathias Bynens) - A Lua minifier written in Javascript that is being used as a temporary solution until the [minification rework](https://pyan.notion.site/014c3553be6b45d1989e1e133ec2c424?v=acc453043e2844728d3db628693c100d&p=597187d43f014c02b3f61fb70aaed968&pm=s) 68 | 69 | [FiOne](https://github.com/Rerumu/FiOne/blob/master/source.lua) (by Rerumu) - Lua bytecode interpreter 70 | -------------------------------------------------------------------------------- /minifier/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /minifier/Input.lua: -------------------------------------------------------------------------------- 1 | local a = {"Hello", "World"} 2 | 3 | local i = 1 4 | while i <= #a do 5 | if a ~= "Hello" then 6 | print("World") 7 | else 8 | print(a[i]) 9 | end 10 | i = i + 1 11 | end 12 | -------------------------------------------------------------------------------- /minifier/main.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const luaparse = require("luaparse"); 3 | const minifier = require("./minify"); 4 | const { scan, generateVariable } = require("./util"); 5 | 6 | let ast = luaparse.parse(fs.readFileSync("../temp/temp3.lua", "utf8")); 7 | // let ast = luaparse.parse(fs.readFileSync("Input.lua", "utf8")); 8 | 9 | // fs.writeFileSync("ast.json", JSON.stringify(ast, null, 2)); 10 | 11 | let numerics = []; 12 | 13 | scan(ast, "NumericLiteral", (numeric) => { 14 | if (!numerics.includes(numeric.value)) { 15 | numerics.push(numeric.value); 16 | } 17 | numeric.raw = `numericsList.${generateVariable( 18 | numerics.indexOf(numeric.value) 19 | )}`; 20 | }); 21 | 22 | let numericCombinations = [] 23 | 24 | for (let i in numerics) { 25 | numericCombinations.push(`${generateVariable(i)} = ${numerics[i]}`) 26 | } 27 | 28 | numericCombinations.sort(() => Math.random() - 0.5) 29 | 30 | let numericString = "{"; 31 | 32 | for (let i in numericCombinations) { 33 | numericString += `${i != 0 ? "," : ""}${numericCombinations[i]}`; 34 | } 35 | 36 | numericString += "}"; 37 | 38 | let strings = []; 39 | 40 | scan(ast, "StringLiteral", (string) => { 41 | const value = 42 | string.raw.charAt(0) == "[" 43 | ? string.raw.slice(2, -2) 44 | : string.raw.slice(1, -1); 45 | 46 | if (!strings.includes(value)) { 47 | strings.push(value); 48 | } 49 | string.raw = `stringsList.${generateVariable(strings.indexOf(value))}`; 50 | }); 51 | 52 | let stringString = "{"; 53 | 54 | for (let i in strings) { 55 | stringString += `${i != 0 ? "," : ""}${generateVariable(i)} = [[${ 56 | strings[i] 57 | }]]`; 58 | } 59 | 60 | stringString += "}"; 61 | 62 | const stringMap = { 63 | ["numericsList"]: numericString, 64 | ["stringsList"]: stringString, 65 | }; 66 | 67 | let funcArgNames = ["numericsList", "stringsList"].sort(() => Math.random() - 0.5) 68 | 69 | fs.writeFileSync( 70 | "../temp/temp4.lua", 71 | minifier.minify( 72 | `local main = function(${funcArgNames.toString(",")}) ${minifier.minify( 73 | ast 74 | )} end main(${funcArgNames.map((val) => stringMap[val])})` 75 | ) 76 | ); 77 | -------------------------------------------------------------------------------- /minifier/minify.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/luamin v1.0.4 by @mathias */ 2 | ;(function(root) { 3 | 4 | // Detect free variables `exports` 5 | var freeExports = typeof exports == 'object' && exports; 6 | 7 | // Detect free variable `module` 8 | var freeModule = typeof module == 'object' && module && 9 | module.exports == freeExports && module; 10 | 11 | // Detect free variable `global`, from Node.js or Browserified code, 12 | // and use it as `root` 13 | var freeGlobal = typeof global == 'object' && global; 14 | if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { 15 | root = freeGlobal; 16 | } 17 | 18 | /*--------------------------------------------------------------------------*/ 19 | 20 | var luaparse = root.luaparse || require('luaparse'); 21 | luaparse.defaultOptions.comments = false; 22 | luaparse.defaultOptions.scope = true; 23 | var parse = luaparse.parse; 24 | 25 | var regexAlphaUnderscore = /[a-zA-Z_]/; 26 | var regexAlphaNumUnderscore = /[a-zA-Z0-9_]/; 27 | var regexDigits = /[0-9]/; 28 | 29 | // http://www.lua.org/manual/5.2/manual.html#3.4.7 30 | // http://www.lua.org/source/5.2/lparser.c.html#priority 31 | var PRECEDENCE = { 32 | 'or': 1, 33 | 'and': 2, 34 | '<': 3, '>': 3, '<=': 3, '>=': 3, '~=': 3, '==': 3, 35 | '..': 5, 36 | '+': 6, '-': 6, // binary - 37 | '*': 7, '/': 7, '%': 7, 38 | 'unarynot': 8, 'unary#': 8, 'unary-': 8, // unary - 39 | '^': 10 40 | }; 41 | 42 | var IDENTIFIER_PARTS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 43 | 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 44 | 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 45 | 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 46 | 'U', 'V', 'W', 'X', 'Y', 'Z', '_']; // yes yes 47 | var IDENTIFIER_PARTS_MAX = IDENTIFIER_PARTS.length - 1; 48 | 49 | var each = function(array, fn) { 50 | var index = -1; 51 | var length = array.length; 52 | var max = length - 1; 53 | while (++index < length) { 54 | fn(array[index], index < max); 55 | } 56 | }; 57 | 58 | var indexOf = function(array, value) { 59 | var index = -1; 60 | var length = array.length; 61 | while (++index < length) { 62 | if (array[index] == value) { 63 | return index; 64 | } 65 | } 66 | }; 67 | 68 | var hasOwnProperty = {}.hasOwnProperty; 69 | var extend = function(destination, source) { 70 | var key; 71 | if (source) { 72 | for (key in source) { 73 | if (hasOwnProperty.call(source, key)) { 74 | destination[key] = source[key]; 75 | } 76 | } 77 | } 78 | return destination; 79 | }; 80 | 81 | var generateZeroes = function(length) { 82 | // length = length + 10 83 | var zero = '0'; 84 | var result = ''; 85 | if (length < 1) { 86 | return result; 87 | } 88 | if (length == 1) { 89 | return zero; 90 | } 91 | while (length) { 92 | if (length & 1) { 93 | result += zero; 94 | } 95 | if (length >>= 1) { 96 | zero += zero; 97 | } 98 | } 99 | return result; 100 | }; 101 | 102 | // http://www.lua.org/manual/5.2/manual.html#3.1 103 | function isKeyword(id) { 104 | switch (id.length) { 105 | case 2: 106 | return 'do' == id || 'if' == id || 'in' == id || 'or' == id; 107 | case 3: 108 | return 'and' == id || 'end' == id || 'for' == id || 'nil' == id || 109 | 'not' == id; 110 | case 4: 111 | return 'else' == id || 'goto' == id || 'then' == id || 'true' == id; 112 | case 5: 113 | return 'break' == id || 'false' == id || 'local' == id || 114 | 'until' == id || 'while' == id; 115 | case 6: 116 | return 'elseif' == id || 'repeat' == id || 'return' == id; 117 | case 8: 118 | return 'function' == id; 119 | } 120 | return false; 121 | } 122 | 123 | var currentIdentifier; 124 | var identifierMap; 125 | var identifiersInUse; 126 | var generateIdentifier = function(originalName) { 127 | // Preserve `self` in methods 128 | if (originalName == 'self') { 129 | return originalName; 130 | } 131 | 132 | if (hasOwnProperty.call(identifierMap, originalName)) { 133 | return identifierMap[originalName]; 134 | } 135 | var length = currentIdentifier.length; 136 | var position = length - 1; 137 | var character; 138 | var index; 139 | while (position >= 0) { 140 | character = currentIdentifier.charAt(position); 141 | index = indexOf(IDENTIFIER_PARTS, character); 142 | if (index != IDENTIFIER_PARTS_MAX) { 143 | currentIdentifier = currentIdentifier.substring(0, position) + 144 | IDENTIFIER_PARTS[index + 1] + generateZeroes(length - (position + 1)); 145 | if ( 146 | isKeyword(currentIdentifier) || 147 | indexOf(identifiersInUse, currentIdentifier) > -1 148 | ) { 149 | return generateIdentifier(originalName); 150 | } 151 | identifierMap[originalName] = currentIdentifier; 152 | return currentIdentifier; 153 | } 154 | --position; 155 | } 156 | currentIdentifier = '_' + generateZeroes(length); 157 | if (indexOf(identifiersInUse, currentIdentifier) > -1) { 158 | return generateIdentifier(originalName); 159 | } 160 | identifierMap[originalName] = currentIdentifier; 161 | return currentIdentifier; 162 | }; 163 | 164 | /*--------------------------------------------------------------------------*/ 165 | 166 | var joinStatements = function(a, b, separator) { 167 | separator || (separator = ' '); 168 | 169 | var lastCharA = a.slice(-1); 170 | var firstCharB = b.charAt(0); 171 | 172 | if (lastCharA == '' || firstCharB == '') { 173 | return a + b; 174 | } 175 | if (regexAlphaUnderscore.test(lastCharA)) { 176 | if (regexAlphaNumUnderscore.test(firstCharB)) { 177 | // e.g. `while` + `1` 178 | // e.g. `local a` + `local b` 179 | return a + separator + b; 180 | } else { 181 | // e.g. `not` + `(2>3 or 3<2)` 182 | // e.g. `x` + `^` 183 | return a + b; 184 | } 185 | } 186 | if (regexDigits.test(lastCharA)) { 187 | if ( 188 | firstCharB == '(' || 189 | !(firstCharB == '.' || 190 | regexAlphaUnderscore.test(firstCharB)) 191 | ) { 192 | // e.g. `1` + `+` 193 | // e.g. `1` + `==` 194 | return a + b; 195 | } else { 196 | // e.g. `1` + `..` 197 | // e.g. `1` + `and` 198 | return a + separator + b; 199 | } 200 | } 201 | if (lastCharA == firstCharB && lastCharA == '-') { 202 | // e.g. `1-` + `-2` 203 | return a + separator + b; 204 | } 205 | var secondLastCharA = a.slice(-2, -1); 206 | if (lastCharA == '.' && secondLastCharA != '.' && regexAlphaNumUnderscore.test(firstCharB)) { 207 | // e.g. `1.` + `print` 208 | return a + separator + b; 209 | } 210 | return a + b; 211 | }; 212 | 213 | var formatBase = function(base) { 214 | var result = ''; 215 | var type = base.type; 216 | var needsParens = base.inParens && ( 217 | type == 'CallExpression' || 218 | type == 'BinaryExpression' || 219 | type == 'FunctionDeclaration' || 220 | type == 'TableConstructorExpression' || 221 | type == 'LogicalExpression' || 222 | type == 'StringLiteral' 223 | ); 224 | if (needsParens) { 225 | result += '('; 226 | } 227 | result += formatExpression(base); 228 | if (needsParens) { 229 | result += ')'; 230 | } 231 | return result; 232 | }; 233 | 234 | var formatExpression = function(expression, options) { 235 | 236 | options = extend({ 237 | 'precedence': 0, 238 | 'preserveIdentifiers': false 239 | }, options); 240 | 241 | var result = ''; 242 | var currentPrecedence; 243 | var associativity; 244 | var operator; 245 | 246 | var expressionType = expression.type; 247 | 248 | if (expressionType == 'Identifier') { 249 | 250 | result = expression.isLocal && !options.preserveIdentifiers 251 | ? generateIdentifier(expression.name) 252 | : expression.name; 253 | 254 | } else if ( 255 | expressionType == 'StringLiteral' || 256 | expressionType == 'NumericLiteral' || 257 | expressionType == 'BooleanLiteral' || 258 | expressionType == 'NilLiteral' || 259 | expressionType == 'VarargLiteral' 260 | ) { 261 | 262 | result = expression.raw; 263 | 264 | } else if ( 265 | expressionType == 'LogicalExpression' || 266 | expressionType == 'BinaryExpression' 267 | ) { 268 | 269 | // If an expression with precedence x 270 | // contains an expression with precedence < x, 271 | // the inner expression must be wrapped in parens. 272 | operator = expression.operator; 273 | currentPrecedence = PRECEDENCE[operator]; 274 | associativity = 'left'; 275 | 276 | result = formatExpression(expression.left, { 277 | 'precedence': currentPrecedence, 278 | 'direction': 'left', 279 | 'parent': operator 280 | }); 281 | result = joinStatements(result, operator); 282 | result = joinStatements(result, formatExpression(expression.right, { 283 | 'precedence': currentPrecedence, 284 | 'direction': 'right', 285 | 'parent': operator 286 | })); 287 | 288 | if (operator == '^' || operator == '..') { 289 | associativity = "right"; 290 | } 291 | 292 | if ( 293 | currentPrecedence < options.precedence || 294 | ( 295 | currentPrecedence == options.precedence && 296 | associativity != options.direction && 297 | options.parent != '+' && 298 | !(options.parent == '*' && (operator == '/' || operator == '*')) 299 | ) 300 | ) { 301 | // The most simple case here is that of 302 | // protecting the parentheses on the RHS of 303 | // `1 - (2 - 3)` but deleting them from `(1 - 2) - 3`. 304 | // This is generally the right thing to do. The 305 | // semantics of `+` are special however: `1 + (2 - 3)` 306 | // == `1 + 2 - 3`. `-` and `+` are the only two operators 307 | // who share their precedence level. `*` also can 308 | // commute in such a way with `/`, but not with `%` 309 | // (all three share a precedence). So we test for 310 | // all of these conditions and avoid emitting 311 | // parentheses in the cases where we don’t have to. 312 | result = '(' + result + ')'; 313 | } 314 | 315 | } else if (expressionType == 'UnaryExpression') { 316 | 317 | operator = expression.operator; 318 | currentPrecedence = PRECEDENCE['unary' + operator]; 319 | 320 | result = joinStatements( 321 | operator, 322 | formatExpression(expression.argument, { 323 | 'precedence': currentPrecedence 324 | }) 325 | ); 326 | 327 | if ( 328 | currentPrecedence < options.precedence && 329 | // In principle, we should parenthesize the RHS of an 330 | // expression like `3^-2`, because `^` has higher precedence 331 | // than unary `-` according to the manual. But that is 332 | // misleading on the RHS of `^`, since the parser will 333 | // always try to find a unary operator regardless of 334 | // precedence. 335 | !( 336 | (options.parent == '^') && 337 | options.direction == 'right' 338 | ) 339 | ) { 340 | result = '(' + result + ')'; 341 | } 342 | 343 | } else if (expressionType == 'CallExpression') { 344 | 345 | result = formatBase(expression.base) + '('; 346 | 347 | each(expression.arguments, function(argument, needsComma) { 348 | result += formatExpression(argument); 349 | if (needsComma) { 350 | result += ','; 351 | } 352 | }); 353 | result += ')'; 354 | 355 | } else if (expressionType == 'TableCallExpression') { 356 | 357 | result = formatExpression(expression.base) + 358 | formatExpression(expression.arguments); 359 | 360 | } else if (expressionType == 'StringCallExpression') { 361 | 362 | result = formatExpression(expression.base) + 363 | formatExpression(expression.argument); 364 | 365 | } else if (expressionType == 'IndexExpression') { 366 | 367 | result = formatBase(expression.base) + '[' + 368 | formatExpression(expression.index) + ']'; 369 | 370 | } else if (expressionType == 'MemberExpression') { 371 | 372 | result = formatBase(expression.base) + expression.indexer + 373 | formatExpression(expression.identifier, { 374 | 'preserveIdentifiers': true 375 | }); 376 | 377 | } else if (expressionType == 'FunctionDeclaration') { 378 | 379 | result = 'function('; 380 | if (expression.parameters.length) { 381 | each(expression.parameters, function(parameter, needsComma) { 382 | // `Identifier`s have a `name`, `VarargLiteral`s have a `value` 383 | result += parameter.name 384 | ? generateIdentifier(parameter.name) 385 | : parameter.value; 386 | if (needsComma) { 387 | result += ','; 388 | } 389 | }); 390 | } 391 | result += ')'; 392 | result = joinStatements(result, formatStatementList(expression.body)); 393 | result = joinStatements(result, 'end'); 394 | 395 | } else if (expressionType == 'TableConstructorExpression') { 396 | 397 | result = '{'; 398 | 399 | each(expression.fields, function(field, needsComma) { 400 | if (field.type == 'TableKey') { 401 | result += '[' + formatExpression(field.key) + ']=' + 402 | formatExpression(field.value); 403 | } else if (field.type == 'TableValue') { 404 | result += formatExpression(field.value); 405 | } else { // at this point, `field.type == 'TableKeyString'` 406 | result += formatExpression(field.key, { 407 | // TODO: keep track of nested scopes (#18) 408 | 'preserveIdentifiers': true 409 | }) + '=' + formatExpression(field.value); 410 | } 411 | if (needsComma) { 412 | result += ','; 413 | } 414 | }); 415 | 416 | result += '}'; 417 | 418 | } else { 419 | 420 | throw TypeError('Unknown expression type: `' + expressionType + '`'); 421 | 422 | } 423 | 424 | return result; 425 | }; 426 | 427 | var formatStatementList = function(body) { 428 | var result = ''; 429 | each(body, function(statement) { 430 | result = joinStatements(result, formatStatement(statement), ';'); 431 | }); 432 | return result; 433 | }; 434 | 435 | var formatStatement = function(statement) { 436 | var result = ''; 437 | var statementType = statement.type; 438 | 439 | if (statementType == 'AssignmentStatement') { 440 | 441 | // left-hand side 442 | each(statement.variables, function(variable, needsComma) { 443 | result += formatExpression(variable); 444 | if (needsComma) { 445 | result += ','; 446 | } 447 | }); 448 | 449 | // right-hand side 450 | result += '='; 451 | each(statement.init, function(init, needsComma) { 452 | result += formatExpression(init); 453 | if (needsComma) { 454 | result += ','; 455 | } 456 | }); 457 | 458 | } else if (statementType == 'LocalStatement') { 459 | 460 | result = 'local '; 461 | 462 | // left-hand side 463 | each(statement.variables, function(variable, needsComma) { 464 | // Variables in a `LocalStatement` are always local, duh 465 | result += generateIdentifier(variable.name); 466 | if (needsComma) { 467 | result += ','; 468 | } 469 | }); 470 | 471 | // right-hand side 472 | if (statement.init.length) { 473 | result += '='; 474 | each(statement.init, function(init, needsComma) { 475 | result += formatExpression(init); 476 | if (needsComma) { 477 | result += ','; 478 | } 479 | }); 480 | } 481 | 482 | } else if (statementType == 'CallStatement') { 483 | 484 | result = formatExpression(statement.expression); 485 | 486 | } else if (statementType == 'IfStatement') { 487 | 488 | result = joinStatements( 489 | 'if', 490 | formatExpression(statement.clauses[0].condition) 491 | ); 492 | result = joinStatements(result, 'then'); 493 | result = joinStatements( 494 | result, 495 | formatStatementList(statement.clauses[0].body) 496 | ); 497 | each(statement.clauses.slice(1), function(clause) { 498 | if (clause.condition) { 499 | result = joinStatements(result, 'elseif'); 500 | result = joinStatements(result, formatExpression(clause.condition)); 501 | result = joinStatements(result, 'then'); 502 | } else { 503 | result = joinStatements(result, 'else'); 504 | } 505 | result = joinStatements(result, formatStatementList(clause.body)); 506 | }); 507 | result = joinStatements(result, 'end'); 508 | 509 | } else if (statementType == 'WhileStatement') { 510 | 511 | result = joinStatements('while', formatExpression(statement.condition)); 512 | result = joinStatements(result, 'do'); 513 | result = joinStatements(result, formatStatementList(statement.body)); 514 | result = joinStatements(result, 'end'); 515 | 516 | } else if (statementType == 'DoStatement') { 517 | 518 | result = joinStatements('do', formatStatementList(statement.body)); 519 | result = joinStatements(result, 'end'); 520 | 521 | } else if (statementType == 'ReturnStatement') { 522 | 523 | result = 'return'; 524 | 525 | each(statement.arguments, function(argument, needsComma) { 526 | result = joinStatements(result, formatExpression(argument)); 527 | if (needsComma) { 528 | result += ','; 529 | } 530 | }); 531 | 532 | } else if (statementType == 'BreakStatement') { 533 | 534 | result = 'break'; 535 | 536 | } else if (statementType == 'RepeatStatement') { 537 | 538 | result = joinStatements('repeat', formatStatementList(statement.body)); 539 | result = joinStatements(result, 'until'); 540 | result = joinStatements(result, formatExpression(statement.condition)) 541 | 542 | } else if (statementType == 'FunctionDeclaration') { 543 | 544 | result = (statement.isLocal ? 'local ' : '') + 'function '; 545 | result += formatExpression(statement.identifier); 546 | result += '('; 547 | 548 | if (statement.parameters.length) { 549 | each(statement.parameters, function(parameter, needsComma) { 550 | // `Identifier`s have a `name`, `VarargLiteral`s have a `value` 551 | result += parameter.name 552 | ? generateIdentifier(parameter.name) 553 | : parameter.value; 554 | if (needsComma) { 555 | result += ','; 556 | } 557 | }); 558 | } 559 | 560 | result += ')'; 561 | result = joinStatements(result, formatStatementList(statement.body)); 562 | result = joinStatements(result, 'end'); 563 | 564 | } else if (statementType == 'ForGenericStatement') { 565 | // see also `ForNumericStatement` 566 | 567 | result = 'for '; 568 | 569 | each(statement.variables, function(variable, needsComma) { 570 | // The variables in a `ForGenericStatement` are always local 571 | result += generateIdentifier(variable.name); 572 | if (needsComma) { 573 | result += ','; 574 | } 575 | }); 576 | 577 | result += ' in'; 578 | 579 | each(statement.iterators, function(iterator, needsComma) { 580 | result = joinStatements(result, formatExpression(iterator)); 581 | if (needsComma) { 582 | result += ','; 583 | } 584 | }); 585 | 586 | result = joinStatements(result, 'do'); 587 | result = joinStatements(result, formatStatementList(statement.body)); 588 | result = joinStatements(result, 'end'); 589 | 590 | } else if (statementType == 'ForNumericStatement') { 591 | 592 | // The variables in a `ForNumericStatement` are always local 593 | result = 'for ' + generateIdentifier(statement.variable.name) + '='; 594 | result += formatExpression(statement.start) + ',' + 595 | formatExpression(statement.end); 596 | 597 | if (statement.step) { 598 | result += ',' + formatExpression(statement.step); 599 | } 600 | 601 | result = joinStatements(result, 'do'); 602 | result = joinStatements(result, formatStatementList(statement.body)); 603 | result = joinStatements(result, 'end'); 604 | 605 | } else if (statementType == 'LabelStatement') { 606 | 607 | // The identifier names in a `LabelStatement` can safely be renamed 608 | result = '::' + generateIdentifier(statement.label.name) + '::'; 609 | 610 | } else if (statementType == 'GotoStatement') { 611 | 612 | // The identifier names in a `GotoStatement` can safely be renamed 613 | result = 'goto ' + generateIdentifier(statement.label.name); 614 | 615 | } else { 616 | 617 | throw TypeError('Unknown statement type: `' + statementType + '`'); 618 | 619 | } 620 | 621 | return result; 622 | }; 623 | 624 | var minify = function(argument) { 625 | // `argument` can be a Lua code snippet (string) 626 | // or a luaparse-compatible AST (object) 627 | var ast = typeof argument == 'string' 628 | ? parse(argument) 629 | : argument; 630 | 631 | // (Re)set temporary identifier values 632 | identifierMap = {}; 633 | identifiersInUse = []; 634 | // This is a shortcut to help generate the first identifier (`a`) faster 635 | currentIdentifier = '9'; 636 | 637 | // Make sure global variable names aren't renamed 638 | if (ast.globals) { 639 | each(ast.globals, function(object) { 640 | var name = object.name; 641 | identifierMap[name] = name; 642 | identifiersInUse.push(name); 643 | }); 644 | } else { 645 | throw Error('Missing required AST property: `globals`'); 646 | } 647 | 648 | return formatStatementList(ast.body); 649 | }; 650 | 651 | /*--------------------------------------------------------------------------*/ 652 | 653 | var luamin = { 654 | 'version': '1.0.4', 655 | 'minify': minify 656 | }; 657 | 658 | // Some AMD build optimizers, like r.js, check for specific condition patterns 659 | // like the following: 660 | if ( 661 | typeof define == 'function' && 662 | typeof define.amd == 'object' && 663 | define.amd 664 | ) { 665 | define(function() { 666 | return luamin; 667 | }); 668 | } else if (freeExports && !freeExports.nodeType) { 669 | if (freeModule) { // in Node.js or RingoJS v0.8.0+ 670 | freeModule.exports = luamin; 671 | } else { // in Narwhal or RingoJS v0.7.0- 672 | extend(freeExports, luamin); 673 | } 674 | } else { // in Rhino or a web browser 675 | root.luamin = luamin; 676 | } 677 | 678 | }(this)); 679 | -------------------------------------------------------------------------------- /minifier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minifier", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "luaparse": "^0.3.1" 13 | }, 14 | "devDependencies": { 15 | "pkg": "^5.8.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /minifier/util.js: -------------------------------------------------------------------------------- 1 | function scan(parentChunk, type, callback) { 2 | for (var i in parentChunk) { 3 | let currentChunk = parentChunk[i]; 4 | 5 | if (typeof currentChunk != "object" || currentChunk == null) { 6 | continue; 7 | } 8 | 9 | if (currentChunk.type != null && currentChunk.type == type) { 10 | callback(currentChunk); 11 | } 12 | 13 | scan(currentChunk, type, callback); 14 | } 15 | } 16 | 17 | const letters = "abcdefghijklmnopqrstuvwxyz"; 18 | 19 | function generateVariable(num) { 20 | let ret = ""; 21 | 22 | ret += letters.charAt(num % letters.length); 23 | num = Math.floor(num / letters.length); 24 | 25 | while (num != 0) { 26 | ret += letters.charAt(num % letters.length); 27 | num = Math.floor(num / letters.length); 28 | } 29 | 30 | return ret; 31 | } 32 | 33 | module.exports = { 34 | scan, 35 | generateVariable, 36 | }; 37 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, File}, 3 | io::{BufReader, Read}, 4 | path::Path, 5 | process::Command, 6 | }; 7 | 8 | use clap::Parser; 9 | use lua_deserializer::deserializer::Deserializer; 10 | use obfuscator::vm_generator::VMGenerator; 11 | 12 | use crate::{ 13 | obfuscation_settings::ObfuscationSettings, obfuscator::encryption::constant_encryption, 14 | }; 15 | 16 | pub mod obfuscation_settings; 17 | pub mod obfuscator; 18 | 19 | static FINAL_FILE: &str = "temp4.lua"; 20 | 21 | /// Simple program to greet a person 22 | #[derive(Parser, Debug)] 23 | #[command(version, about, long_about = None)] 24 | struct Args { 25 | /// Name of the file to obfuscate 26 | #[arg(short, long)] 27 | file: String, 28 | 29 | /// Run the program after obfuscated 30 | #[arg(short, long, default_value_t = false)] 31 | run: bool, 32 | } 33 | 34 | fn main() { 35 | let settings = ObfuscationSettings::new(); 36 | 37 | let args = Args::parse(); 38 | 39 | if Path::new("temp").is_dir() { 40 | fs::remove_dir_all("temp").unwrap(); 41 | } 42 | fs::create_dir("temp").unwrap(); 43 | 44 | println!("{:?}", args); 45 | 46 | if !Path::new(&args.file).exists() { 47 | println!("File {} does not exist", args.file); 48 | return; 49 | } 50 | 51 | fs::copy(&args.file, "temp/temp1.lua").unwrap(); 52 | 53 | let luac_command = "luac"; 54 | 55 | println!("[Obfuscator] Encrypting Constants..."); 56 | 57 | let mut initial_code = 58 | fs::read_to_string("temp/temp1.lua").expect("Failed to read file temp1.lua"); 59 | constant_encryption::encrypt(&mut initial_code); 60 | 61 | fs::write("temp/temp2.lua", initial_code).expect("Failed to write to file temp2.lua"); 62 | 63 | println!("[Obfuscator] Compiling..."); 64 | 65 | Command::new(luac_command) 66 | .arg("temp2.lua") 67 | .current_dir("temp") 68 | .output() 69 | .expect("Failed to compile lua binary"); 70 | 71 | println!("[Obfuscator] Reading file..."); 72 | 73 | let mut reader = BufReader::new(File::open("temp/luac.out").unwrap()); 74 | let mut buffer = Vec::new(); 75 | 76 | reader.read_to_end(&mut buffer).unwrap(); 77 | 78 | println!("[Obfuscator] Deserializing..."); 79 | 80 | let mut deserializer = Deserializer::new(buffer); 81 | let main_chunk = deserializer.deserialize(); 82 | 83 | println!("[Obfuscator] Generating VM..."); 84 | 85 | let vm_generator = VMGenerator::new(); 86 | let vm = vm_generator.generate(main_chunk, settings); 87 | 88 | fs::write("temp/temp3.lua", vm).expect("Failed to write vm to file"); 89 | 90 | println!("[Obfuscator] Minifying..."); 91 | 92 | Command::new("node") 93 | .arg(".") 94 | .current_dir("minifier") 95 | .output() 96 | .expect("Failed to minify"); 97 | 98 | if args.run { 99 | println!("[Obfuscator] Running..."); 100 | 101 | let output = Command::new("lua") 102 | .arg(FINAL_FILE) 103 | .current_dir("temp") 104 | .output() 105 | .expect(&format!("Failed to run {}", FINAL_FILE)); 106 | 107 | let output_string: String = output.stdout.into_iter().map(|v| v as char).collect(); 108 | let output_error: String = output.stderr.into_iter().map(|v| v as char).collect(); 109 | 110 | println!("Program output:\n{}", output_string); 111 | if output_error != "" { 112 | println!("Program Error:\n{}", output_error); 113 | } 114 | } 115 | 116 | fs::copy("temp/".to_owned() + FINAL_FILE, "Out.lua").expect("Failed to copy final file"); 117 | 118 | if cfg!(not(debug_assertions)) { 119 | fs::remove_dir_all("temp").expect("Failed to delete temp directory"); 120 | } 121 | println!("[Obfuscator] Your obfuscated program has been written to Out.lua"); 122 | } 123 | -------------------------------------------------------------------------------- /src/obfuscation_settings.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone)] 2 | pub struct ObfuscationSettings { 3 | pub include_debug_line_info: bool, 4 | pub compress_bytecode: bool, 5 | pub encrypt_strings: bool, 6 | } 7 | 8 | impl ObfuscationSettings { 9 | pub fn new() -> Self { 10 | Self { 11 | include_debug_line_info: false, 12 | compress_bytecode: true, 13 | encrypt_strings: true, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/obfuscator/encryption/constant_encryption.rs: -------------------------------------------------------------------------------- 1 | fn encrypt_strings(input: &mut String) {} 2 | 3 | pub fn encrypt(input: &mut String) { 4 | encrypt_strings(input); 5 | } 6 | -------------------------------------------------------------------------------- /src/obfuscator/encryption/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constant_encryption; 2 | -------------------------------------------------------------------------------- /src/obfuscator/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod encryption; 2 | pub mod obfuscation_context; 3 | pub mod serializer; 4 | pub mod vm; 5 | pub mod vm_generator; 6 | -------------------------------------------------------------------------------- /src/obfuscator/obfuscation_context.rs: -------------------------------------------------------------------------------- 1 | use lua_deserializer::enums::{ 2 | chunk_components::ChunkComponents, lua_type::LuaType, opcode_type::OpcodeType, 3 | }; 4 | 5 | use super::vm_generator::ConstantType; 6 | 7 | #[derive(Clone)] 8 | pub struct ObfuscationContext { 9 | pub constant_type_map: [ConstantType; 4], 10 | pub opcode_map: Vec, 11 | pub chunk_component_map: [ChunkComponents; 3], 12 | } 13 | -------------------------------------------------------------------------------- /src/obfuscator/serializer.rs: -------------------------------------------------------------------------------- 1 | use lua_deserializer::{ 2 | enums::{ 3 | chunk_components::ChunkComponents, instruction_type::InstructionType, lua_type::LuaType, 4 | }, 5 | structs::{chunk::Chunk, constant::Constant, instruction::Instruction}, 6 | util::write_stream::WriteStream, 7 | }; 8 | 9 | use crate::obfuscation_settings::ObfuscationSettings; 10 | 11 | use super::obfuscation_context::ObfuscationContext; 12 | 13 | pub struct Serializer { 14 | write_stream: WriteStream, 15 | obfuscation_context: ObfuscationContext, 16 | settings: ObfuscationSettings, 17 | } 18 | 19 | impl Serializer { 20 | pub fn new(obfuscation_context: ObfuscationContext, settings: ObfuscationSettings) -> Self { 21 | Self { 22 | write_stream: WriteStream::new(), 23 | obfuscation_context, 24 | settings, 25 | } 26 | } 27 | 28 | fn serialize_constant(&mut self, constant: &Constant) { 29 | self.write_stream.write_int8(match constant.lua_type { 30 | LuaType::NIL => 0, 31 | LuaType::BOOLEAN(_) => 1, 32 | LuaType::INVALID => 2, 33 | LuaType::NUMBER(_) => 3, 34 | LuaType::STRING(_) => 4, 35 | }); 36 | 37 | match &constant.lua_type { 38 | LuaType::NIL => {} 39 | LuaType::BOOLEAN(data) => self.write_stream.write_int8(if *data { 1 } else { 0 }), 40 | LuaType::INVALID => unreachable!(), 41 | LuaType::NUMBER(data) => self.write_stream.write_double(*data), 42 | LuaType::STRING(data) => self.write_stream.write_string(&data), 43 | } 44 | } 45 | 46 | fn serialize_instruction(&mut self, instruction: &Instruction) { 47 | let opcode_num = self 48 | .obfuscation_context 49 | .opcode_map 50 | .iter() 51 | .position(|&v| v == instruction.opcode) 52 | .unwrap(); 53 | 54 | let mut instruction_data: u16 = 0; 55 | instruction_data |= ((opcode_num as u16) & 0x3f) << 4; 56 | instruction_data |= (match instruction.instruction_type { 57 | InstructionType::ABC => 0b01, 58 | InstructionType::ABx => 0b10, 59 | InstructionType::AsBx => 0b11, 60 | }) << 2; 61 | instruction_data |= if instruction.is_constant_b { 1 << 1 } else { 0 }; 62 | instruction_data |= if instruction.is_constant_c { 1 } else { 0 }; 63 | 64 | self.write_stream.write_int16(instruction_data); 65 | self.write_stream.write_int8(instruction.data_a); 66 | 67 | match instruction.instruction_type { 68 | InstructionType::ABC => { 69 | self.write_stream.write_int16(instruction.data_b as u16); 70 | self.write_stream.write_int16(instruction.data_c as u16); 71 | } 72 | InstructionType::ABx => self.write_stream.write_int32(instruction.data_b as u32), 73 | InstructionType::AsBx => self 74 | .write_stream 75 | .write_int32((instruction.data_b + 131071) as u32), 76 | } 77 | } 78 | 79 | fn serialize_chunk(&mut self, chunk: &Chunk) { 80 | self.write_stream.write_string(&chunk.source_name); 81 | self.write_stream.write_int8(chunk.upvalue_count); 82 | self.write_stream.write_int8(chunk.parameter_count); 83 | 84 | for component in self.obfuscation_context.chunk_component_map.clone() { 85 | match component { 86 | ChunkComponents::CONSTANTS => { 87 | self.write_stream.write_int64(chunk.constants.len() as u64); 88 | for constant in &chunk.constants { 89 | self.serialize_constant(constant); 90 | } 91 | } 92 | ChunkComponents::INSTRUCTIONS => { 93 | self.write_stream 94 | .write_int64(chunk.instructions.len() as u64); 95 | for instruction in &chunk.instructions { 96 | self.serialize_instruction(instruction); 97 | } 98 | } 99 | ChunkComponents::PROTOS => { 100 | self.write_stream.write_int64(chunk.protos.len() as u64); 101 | for proto in &chunk.protos { 102 | self.serialize_chunk(proto); 103 | } 104 | } 105 | } 106 | } 107 | 108 | if self.settings.include_debug_line_info { 109 | self.write_stream 110 | .write_int64(chunk.source_lines.len() as u64); 111 | 112 | for line in &chunk.source_lines { 113 | self.write_stream.write_int64(*line); 114 | } 115 | } 116 | } 117 | 118 | pub fn serialze(&mut self, main_chunk: Chunk) -> Vec { 119 | // self.main_chunk 120 | // .serialize(&mut self.write_stream, obfuscation_context, settings); 121 | self.serialize_chunk(&main_chunk); 122 | 123 | self.write_stream.bytes.clone() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/obfuscator/vm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod opcode_strings; 2 | pub mod vm_strings; 3 | -------------------------------------------------------------------------------- /src/obfuscator/vm/opcode_strings.rs: -------------------------------------------------------------------------------- 1 | use lua_deserializer::enums::opcode_type::OpcodeType; 2 | 3 | pub fn get_opcode_string(opcode: &OpcodeType, opcode_list: &Vec) -> String { 4 | match opcode { 5 | OpcodeType::OpMove => "memory[inst[$A_REGISTER$]] = memory[inst[$B_REGISTER$]]".to_string(), 6 | OpcodeType::OpLoadConst => "memory[inst[$A_REGISTER$]] = inst[$CONSTANT$]".to_string(), 7 | OpcodeType::OpLoadBool => { 8 | "memory[inst[$A_REGISTER$]] = inst[$B_REGISTER$] ~= 0 9 | 10 | if inst[$C_REGISTER$] ~= 0 then pc = pc + 1 end".to_string() 11 | } 12 | OpcodeType::OpLoadNil => "for i = inst[$A_REGISTER$], inst[$B_REGISTER$] do memory[i] = nil end".to_string(), 13 | OpcodeType::OpGetUpval => { 14 | "local uv = upvals[inst[$B_REGISTER$]] 15 | 16 | memory[inst[$A_REGISTER$]] = uv[2][uv[1]]".to_string() 17 | } 18 | OpcodeType::OpGetGlobal => "memory[inst[$A_REGISTER$]] = env[inst[$CONSTANT$]]".to_string(), 19 | OpcodeType::OpGetTable => "memory[inst[$A_REGISTER$]] = memory[inst[$B_REGISTER$]][constantC(inst)]".to_string(), 20 | OpcodeType::OpSetGlobal => "env[inst[$CONSTANT$]] = memory[inst[$A_REGISTER$]]".to_string(), 21 | OpcodeType::OpSetUpval => { 22 | "local uv = upvals[inst[$B_REGISTER$]] 23 | 24 | uv[2][uv[1]] = memory[inst[$A_REGISTER$]]".to_string() 25 | } 26 | OpcodeType::OpSetTable => "memory[inst[$A_REGISTER$]][constantB(inst)] = constantC(inst)".to_string(), 27 | OpcodeType::OpNewTable => "memory[inst[$A_REGISTER$]] = TableCreate(inst[$B_REGISTER$])".to_string(), 28 | OpcodeType::OpSelf => { 29 | "memory[inst[$A_REGISTER$] + 1] = memory[inst[$B_REGISTER$]] 30 | memory[inst[$A_REGISTER$]] = memory[inst[$B_REGISTER$]][constantC(inst)]".to_string() 31 | } 32 | OpcodeType::OpAdd => "memory[inst[$A_REGISTER$]] = constantB(inst) + constantC(inst)".to_string(), 33 | OpcodeType::OpSub => "memory[inst[$A_REGISTER$]] = constantB(inst) - constantC(inst)".to_string(), 34 | OpcodeType::OpMul => "memory[inst[$A_REGISTER$]] = constantB(inst) * constantC(inst)".to_string(), 35 | OpcodeType::OpDiv => "memory[inst[$A_REGISTER$]] = constantB(inst) / constantC(inst)".to_string(), 36 | OpcodeType::OpMod => "memory[inst[$A_REGISTER$]] = constantB(inst) % constantC(inst)".to_string(), 37 | OpcodeType::OpPow => "memory[inst[$A_REGISTER$]] = constantB(inst) ^ constantC(inst)".to_string(), 38 | OpcodeType::OpUnm => "memory[inst[$A_REGISTER$]] = -memory[inst[$B_REGISTER$]]".to_string(), 39 | OpcodeType::OpNot => "memory[inst[$A_REGISTER$]] = not memory[inst[$B_REGISTER$]]".to_string(), 40 | OpcodeType::OpLen => "memory[inst[$A_REGISTER$]] = #memory[inst[$B_REGISTER$]]".to_string(), 41 | OpcodeType::OpConcat => { 42 | "local B = inst[$B_REGISTER$] 43 | local str = memory[B] 44 | 45 | for i = B + 1, inst[$C_REGISTER$] do str = str .. memory[i] end 46 | 47 | memory[inst[$A_REGISTER$]] = str".to_string() 48 | } 49 | OpcodeType::OpJmp => "pc = pc + inst[$B_REGISTER$]".to_string(), 50 | OpcodeType::OpEq => "if (constantB(inst) == constantC(inst)) == (inst[$A_REGISTER$] ~= 0) then pc = pc + code[pc][$B_REGISTER$] end 51 | 52 | pc = pc + 1".to_string(), 53 | OpcodeType::OpLt => "if (constantB(inst) < constantC(inst)) == (inst[$A_REGISTER$] ~= 0) then pc = pc + code[pc][$B_REGISTER$] end 54 | 55 | pc = pc + 1".to_string(), 56 | OpcodeType::OpLe => "if (constantB(inst) <= constantC(inst)) == (inst[$A_REGISTER$] ~= 0) then pc = pc + code[pc][$B_REGISTER$] end 57 | 58 | pc = pc + 1".to_string(), 59 | OpcodeType::OpTest => "if (not memory[inst[$A_REGISTER$]]) ~= (inst[$C_REGISTER$] ~= 0) then pc = pc + code[pc][$B_REGISTER$] end 60 | pc = pc + 1".to_string(), 61 | OpcodeType::OpTestSet => "local A = inst[$A_REGISTER$] 62 | local B = inst[$B_REGISTER$] 63 | 64 | if (not memory[B]) ~= (inst[$C_REGISTER$] ~= 0) then 65 | memory[A] = memory[B] 66 | pc = pc + code[pc][$B_REGISTER$] 67 | end 68 | pc = pc + 1".to_string(), 69 | OpcodeType::OpCall => "local A = inst[$A_REGISTER$] 70 | local B = inst[$B_REGISTER$] 71 | local C = inst[$C_REGISTER$] 72 | local params 73 | 74 | if B == 0 then 75 | params = top_index - A 76 | else 77 | params = B - 1 78 | end 79 | 80 | local ret_list = TablePack(memory[A](TableUnpack(memory, A + 1, A + params))) 81 | local ret_num = ret_list.n 82 | 83 | if C == 0 then 84 | top_index = A + ret_num - 1 85 | else 86 | ret_num = C - 1 87 | end 88 | 89 | TableMove(ret_list, 1, ret_num, A, memory)".to_string(), 90 | OpcodeType::OpTailCall => "local A = inst[$A_REGISTER$] 91 | local B = inst[$B_REGISTER$] 92 | local params 93 | 94 | if B == 0 then 95 | params = top_index - A 96 | else 97 | params = B - 1 98 | end 99 | 100 | close_lua_upvalues(open_list, 0) 101 | 102 | return memory[A](TableUnpack(memory, A + 1, A + params))".to_string(), 103 | OpcodeType::OpReturn => "local A = inst[$A_REGISTER$] 104 | local B = inst[$B_REGISTER$] 105 | local len 106 | 107 | if B == 0 then 108 | len = top_index - A + 1 109 | else 110 | len = B - 1 111 | end 112 | 113 | close_lua_upvalues(open_list, 0) 114 | 115 | return TableUnpack(memory, A, A + len - 1)".to_string(), 116 | OpcodeType::OpForLoop => "local A = inst[$A_REGISTER$] 117 | local step = memory[A + 2] 118 | local index = memory[A] + step 119 | local limit = memory[A + 1] 120 | local loops 121 | 122 | if step == MathAbs(step) then 123 | loops = index <= limit 124 | else 125 | loops = index >= limit 126 | end 127 | 128 | if loops then 129 | memory[A] = index 130 | memory[A + 3] = index 131 | pc = pc + inst[$B_REGISTER$] 132 | end".to_string(), 133 | OpcodeType::OpForPrep => "local A = inst[$A_REGISTER$] 134 | -- local init, limit, step 135 | 136 | -- *: Possible additional error checking 137 | -- init = assert(tonumber(memory[A]), '`for` initial value must be a number') 138 | -- limit = assert(tonumber(memory[A + 1]), '`for` limit must be a number') 139 | -- step = assert(tonumber(memory[A + 2]), '`for` step must be a number') 140 | 141 | local init = Tonumber(memory[A]) 142 | local limit = Tonumber(memory[A + 1]) 143 | local step = Tonumber(memory[A + 2]) 144 | 145 | memory[A] = init - step 146 | memory[A + 1] = limit 147 | memory[A + 2] = step 148 | 149 | pc = pc + inst[$B_REGISTER$]".to_string(), 150 | OpcodeType::OpTForLoop => "local A = inst[$A_REGISTER$] 151 | local base = A + 3 152 | 153 | local vals = {memory[A](memory[A + 1], memory[A + 2])} 154 | 155 | TableMove(vals, 1, inst[$C_REGISTER$], base, memory) 156 | 157 | if memory[base] ~= nil then 158 | memory[A + 2] = memory[base] 159 | pc = pc + code[pc][$B_REGISTER$] 160 | end 161 | 162 | pc = pc + 1".to_string(), 163 | OpcodeType::OpSetList => "local A = inst[$A_REGISTER$] 164 | local C = inst[$C_REGISTER$] 165 | local len = inst[$B_REGISTER$] 166 | local tab = memory[A] 167 | local offset 168 | 169 | if len == 0 then len = top_index - A end 170 | 171 | if C == 0 then 172 | C = inst[pc][2] -- used to be .value (I think that this is a upvalue but idk so the index might be wrong) 173 | pc = pc + 1 174 | end 175 | 176 | offset = (C - 1) * 50 --FIELDS_PER_FLUSH 177 | 178 | TableMove(memory, A + 1, A + len, offset + 1, tab)".to_string(), 179 | OpcodeType::OpClose => "close_lua_upvalues(open_list, inst[$A_REGISTER$])".to_string(), 180 | OpcodeType::OpClosure => { 181 | let mut opcode_string = "local sub = subs[inst[$B_REGISTER$] + 1] -- offset for 1 based index 182 | local nups = sub[$UPVALUE_COUNT$] 183 | local uvlist 184 | 185 | if nups ~= 0 then 186 | uvlist = {}".to_string(); 187 | 188 | if opcode_list.contains(&OpcodeType::OpMove) || opcode_list.contains(&OpcodeType::OpGetUpval) { 189 | opcode_string += "for i = 1, nups do 190 | local pseudo = code[pc + i - 1]"; 191 | 192 | if opcode_list.contains(&OpcodeType::OpMove) { 193 | opcode_string += "if pseudo[$OPCODE$] == $MOVE_OPCODE$ then -- @MOVE 194 | uvlist[i - 1] = open_lua_upvalue(open_list, pseudo[$B_REGISTER$], memory) 195 | end"; 196 | } 197 | 198 | if opcode_list.contains(&OpcodeType::OpGetUpval) { 199 | opcode_string += " if pseudo[$OPCODE$] == $GETUPVAL_OPCODE$ then -- @GETUPVAL 200 | uvlist[i - 1] = upvals[pseudo[$B_REGISTER$]] 201 | end"; 202 | } 203 | 204 | opcode_string += " end 205 | 206 | pc = pc + nups"; 207 | } 208 | 209 | 210 | 211 | opcode_string += " end; memory[inst[$A_REGISTER$]] = lua_wrap_state(sub, env, uvlist)"; 212 | 213 | opcode_string}, 214 | OpcodeType::OpVarArg => "local A = inst[$A_REGISTER$] 215 | local len = inst[$B_REGISTER$] 216 | 217 | if len == 0 then 218 | len = vararg[1] 219 | top_index = A + len - 1 220 | end 221 | 222 | TableMove(vararg[2], 1, len, A, memory)".to_string(), 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /src/obfuscator/vm/vm_strings.rs: -------------------------------------------------------------------------------- 1 | pub static VARIABLE_DECLARATION: &str = " 2 | local String = string 3 | local StringChar = String.char 4 | local StringByte = String.byte 5 | local StringSub = String.sub 6 | local StringReverse = String.reverse 7 | local StringFindReal = String.find 8 | -- I had to do this BS because lua returns start and end index and I didn't want to deal with that 9 | local StringFind = function(str, val) local a, _ = StringFindReal(str, val) return a - 1 end 10 | local StringConcat = function(...) 11 | local str = '' 12 | local strs = {...} 13 | for i = 1, #strs do 14 | str = str .. strs[i] 15 | end 16 | 17 | return str 18 | end 19 | local Select = select 20 | local Table = table 21 | local Math = math 22 | local Error = error 23 | local Pairs = pairs 24 | local IPairs = ipairs 25 | local TableConcat = Table.concat 26 | local TableInsert = Table.insert 27 | local function TableUnpack(tbl, i, j) 28 | i = i or 1 29 | j = j or #tbl 30 | if j-i+1 >= 10 then 31 | return tbl[i], tbl[i + 1], tbl[i + 2], tbl[i + 3], tbl[i + 4], 32 | tbl[i + 5], tbl[i + 6], tbl[i + 7], tbl[i + 8], tbl[i + 9], 33 | TableUnpack(tbl, i + 10, j) 34 | end 35 | if i <= j then return tbl[i], TableUnpack(tbl, i + 1, j) end 36 | end 37 | local TableCreate = function(len) 38 | return {TableUnpack({}, 1, len or 1)} 39 | end 40 | local TablePack = function(...) 41 | return { n = Select(StringChar(35), ...), ... } 42 | end 43 | local TableMove = function(src, first, last, offset, dst) 44 | for i = 0, last - first do 45 | dst[offset + i] = src[first + i] 46 | end 47 | end 48 | local TableMerge = function(...) 49 | local newTable = {} 50 | local tbls = {...} 51 | for i = 1, #tbls do 52 | for j = 1, #(tbls[i]) do 53 | TableInsert(newTable, tbls[i][j]) 54 | end 55 | end 56 | 57 | return newTable 58 | end 59 | local Getfenv = getfenv 60 | local MathFloor = Math.floor 61 | local MathMax = Math.max 62 | local Pcall = pcall 63 | local MathAbs = Math.abs 64 | local Tonumber = tonumber 65 | 66 | local RangeGen = function(inputStart, finish, step) 67 | step = step or 1 68 | local start = finish and inputStart or 1 69 | finish = finish or inputStart 70 | 71 | local a = {} 72 | 73 | for i = start, finish, step do 74 | TableInsert(a, i) 75 | end 76 | 77 | return a 78 | end 79 | 80 | local getBitwise = (function() 81 | local function tobittable_r(x, ...) 82 | if (x or 0) == 0 then 83 | return ... 84 | end 85 | return tobittable_r(MathFloor(x / 2), x % 2, ...) 86 | end 87 | 88 | local function tobittable(x) 89 | if x == 0 then 90 | return { 0 } 91 | end 92 | return { tobittable_r(x) } 93 | end 94 | 95 | local function makeop(cond) 96 | local function oper(x, y, ...) 97 | if not y then 98 | return x 99 | end 100 | x, y = tobittable(x), tobittable(y) 101 | local xl, yl = #x, #y 102 | local t, tl = {}, MathMax(xl, yl) 103 | for i = 0, tl - 1 do 104 | local b1, b2 = x[xl - i], y[yl - i] 105 | if not (b1 or b2) then 106 | break 107 | end 108 | t[tl - i] = (cond((b1 or 0) ~= 0, (b2 or 0) ~= 0) and 1 or 0) 109 | end 110 | return oper(Tonumber(TableConcat(t), 2), ...) 111 | end 112 | return oper 113 | end 114 | 115 | --- 116 | -- Perform bitwise AND of several numbers. 117 | -- Truth table: 118 | -- band(0,0) -> 0, 119 | -- band(0,1) -> 0, 120 | -- band(1,0) -> 0, 121 | -- band(1,1) -> 1. 122 | -- @class function 123 | -- @name band 124 | -- @param ... Numbers. 125 | -- @return A number. 126 | local band = makeop(function(a, b) 127 | return a and b 128 | end) 129 | 130 | --- 131 | -- Shift a number's bits to the left. 132 | -- Roughly equivalent to (x * (2^bits)). 133 | -- @param x The number to shift (number). 134 | -- @param bits Number of positions to shift by (number). 135 | -- @return A number. 136 | local function blshift(x, bits) 137 | return MathFloor(x) * (2 ^ bits) 138 | end 139 | 140 | --- 141 | -- Shift a number's bits to the right. 142 | -- Roughly equivalent to (x / (2^bits)). 143 | -- @param x The number to shift (number). 144 | -- @param bits Number of positions to shift by (number). 145 | -- @return A number. 146 | local function brshift(x, bits) 147 | return MathFloor(MathFloor(x) / (2 ^ bits)) 148 | end 149 | 150 | return band, brshift, blshift 151 | end) 152 | local BitAnd, BitRShift, BitLShift = getBitwise() 153 | "; 154 | 155 | pub static DESERIALIZER: &str = " 156 | local lua_bc_to_state 157 | local lua_wrap_state 158 | local stm_lua_func 159 | 160 | -- int rd_int_basic(string src, int s, int e, int d) 161 | -- @src - Source binary string 162 | -- @s - Start index of a little endian integer 163 | -- @e - End index of the integer 164 | -- @d - Direction of the loop 165 | local function rd_int_basic(src, s, e, d) 166 | local num = 0 167 | 168 | -- if bb[l] > 127 then -- signed negative 169 | -- num = num - 256 ^ l 170 | -- bb[l] = bb[l] - 128 171 | -- end 172 | 173 | for i = s, e, d do 174 | local mul = 256 ^ MathAbs(i - s) 175 | 176 | num = num + mul * StringByte(src, i, i) 177 | end 178 | 179 | return num 180 | end 181 | 182 | -- double rd_dbl_basic(byte f1..8) 183 | -- @f1..8 - The 8 bytes composing a little endian double 184 | local function rd_dbl_basic(f1, f2, f3, f4, f5, f6, f7, f8) 185 | local sign = (-1) ^ BitRShift(f8, 7) 186 | local exp = BitLShift(BitAnd(f8, 0x7F), 4) + BitRShift(f7, 4) 187 | local frac = BitAnd(f7, 0x0F) * 2 ^ 48 188 | local normal = 1 189 | 190 | frac = frac + (f6 * 2 ^ 40) + (f5 * 2 ^ 32) + (f4 * 2 ^ 24) + (f3 * 2 ^ 16) + (f2 * 2 ^ 8) + f1 -- help 191 | 192 | if exp == 0 then 193 | if frac == 0 then 194 | return sign * 0 195 | else 196 | normal = 0 197 | exp = 1 198 | end 199 | elseif exp == 0x7FF then 200 | if frac == 0 then 201 | return sign * (1 / 0) 202 | else 203 | return sign * (0 / 0) 204 | end 205 | end 206 | 207 | return sign * 2 ^ (exp - 1023) * (normal + frac / 2 ^ 52) 208 | end 209 | 210 | -- int rd_int_le(string src, int s, int e) 211 | -- @src - Source binary string 212 | -- @s - Start index of a little endian integer 213 | -- @e - End index of the integer 214 | local function rd_int_le(src, s, e) return rd_int_basic(src, s, e - 1, 1) end 215 | 216 | -- double rd_dbl_le(string src, int s) 217 | -- @src - Source binary string 218 | -- @s - Start index of little endian double 219 | local function rd_dbl_le(src, s) return rd_dbl_basic(StringByte(src, s, s + 7)) end 220 | 221 | -- byte stm_byte(Stream S) 222 | -- @S - Stream object to read from 223 | local function stm_byte(S) 224 | local idx = S[1] 225 | local bt = StringByte(S[2], idx, idx) 226 | 227 | S[1] = idx + 1 228 | return bt 229 | end 230 | 231 | -- string stm_string(Stream S, int len) 232 | -- @S - Stream object to read from 233 | -- @len - Length of string being read 234 | local function stm_string(S, len) 235 | local pos = S[1] + len 236 | local str = StringSub(S[2], S[1], pos - 1) 237 | 238 | S[1] = pos 239 | return str 240 | end 241 | 242 | local function stm_int16(S) 243 | local pos = S[1] + 2 244 | local int = rd_int_le(S[2], S[1], pos) 245 | S[1] = pos 246 | 247 | return int 248 | end 249 | 250 | local function stm_int32(S) 251 | local pos = S[1] + 4 252 | local int = rd_int_le(S[2], S[1], pos) 253 | S[1] = pos 254 | 255 | return int 256 | end 257 | 258 | local function stm_int64(S) 259 | local pos = S[1] + 8 260 | local int = rd_int_le(S[2], S[1], pos) 261 | S[1] = pos 262 | 263 | return int 264 | end 265 | 266 | local function stm_num(S) 267 | local flt = rd_dbl_le(S[2], S[1]) 268 | S[1] = S[1] + 8 269 | 270 | return flt 271 | end 272 | 273 | -- string stm_lstring(Stream S) 274 | -- @S - Stream object to read from 275 | local function stm_lstring(S) 276 | local len = stm_int64(S) 277 | local str 278 | 279 | if len ~= 0 then str = StringSub(stm_string(S, len), 1, -2) end 280 | 281 | return str 282 | end 283 | 284 | local function stm_inst_list(S) 285 | local len = stm_int64(S) 286 | local list = TableCreate(len) 287 | 288 | for i = 1, len do 289 | local ins = stm_int16(S) 290 | local op = BitAnd(BitRShift(ins, 4), 0x3f) 291 | local args = BitAnd(BitRShift(ins, 2), 3) 292 | local isConstantB = BitAnd(BitRShift(ins, 1), 1) == 1 293 | local isConstantC = BitAnd(ins, 1) == 1 294 | local data = {} 295 | data[$OPCODE$] = op 296 | data[$A_REGISTER$] = stm_byte(S) 297 | 298 | if args == 1 then -- ABC 299 | data[$B_REGISTER$] = stm_int16(S) 300 | data[$C_REGISTER$] = stm_int16(S) 301 | data[$IS_KB$] = isConstantB and data[$B_REGISTER$] > 0xFF -- post process optimization 302 | data[$IS_KC$] = isConstantC and data[$C_REGISTER$] > 0xFF 303 | elseif args == 2 then -- ABx 304 | data[$B_REGISTER$] = stm_int32(S) 305 | data[$IS_CONST$] = isConstantB 306 | elseif args == 3 then -- AsBx 307 | data[$B_REGISTER$] = stm_int32(S) - 131071 308 | end 309 | 310 | list[i] = data 311 | end 312 | 313 | 314 | return list 315 | end 316 | 317 | local function stm_sub_list(S, src) 318 | local len = stm_int64(S) 319 | local list = TableCreate(len) 320 | 321 | for i = 1, len do 322 | list[i] = stm_lua_func(S, src) -- offset +1 in CLOSURE 323 | end 324 | 325 | return list 326 | end 327 | "; 328 | 329 | pub static DESERIALIZER_2: &str = " 330 | function stm_lua_func(stream, psrc) 331 | local src = stm_lstring(stream) or psrc -- source is propagated 332 | 333 | local proto = {} 334 | proto[$SOURCE_NAME$] = src 335 | 336 | -- stream:s_int() -- line defined 337 | -- stream:s_int() -- last line defined 338 | 339 | proto[$UPVALUE_COUNT$] = stm_byte(stream) -- num upvalues 340 | proto[$PARAMETER_COUNT$] = stm_byte(stream) -- num params 341 | 342 | 343 | -- stm_byte(stream) -- vararg flag 344 | -- proto.max_stack = stm_byte(stream) -- max stack size 345 | "; 346 | 347 | pub static DESERIALIZER_3: &str = " 348 | -- post process optimization 349 | for _, v in IPairs(proto[$OPCODE_LIST$]) do 350 | if v[$IS_CONST$] then 351 | v[$CONSTANT$] = proto[$CONSTANT_LIST$][v[$B_REGISTER$] + 1] -- offset for 1 based index 352 | else 353 | if v[$IS_KB$] then v[$CONST_B$] = proto[$CONSTANT_LIST$][v[$B_REGISTER$] - 0xFF] end 354 | 355 | if v[$IS_KC$] then v[$CONST_C$] = proto[$CONSTANT_LIST$][v[$C_REGISTER$] - 0xFF] end 356 | end 357 | end 358 | 359 | return proto 360 | end 361 | 362 | function lua_bc_to_state(src) 363 | -- stream object 364 | local stream = { 365 | -- data 366 | 1, 367 | src 368 | } 369 | 370 | return stm_lua_func(stream, '') 371 | end 372 | "; 373 | 374 | pub static RUN_HELPERS: &str = " 375 | local function close_lua_upvalues(list, index) 376 | for i, uv in Pairs(list) do 377 | if uv[1] >= index then 378 | -- Replace with indexes if uncommenting 379 | --uv.value = uv.store[uv.index] -- store value 380 | --uv.store = uv 381 | --uv.index = 'value' -- self reference 382 | list[i] = nil 383 | end 384 | end 385 | end 386 | 387 | local function open_lua_upvalue(list, index, memory) 388 | local prev = list[index] 389 | 390 | if not prev then 391 | prev = {index, memory} 392 | list[index] = prev 393 | end 394 | 395 | return prev 396 | end 397 | 398 | local function on_lua_error(failed, err) 399 | local src = failed[2] 400 | -- local line = failed.lines[failed.pc - 1] 401 | local line = 0 402 | 403 | Error(StringConcat(src, ':', line, ':', err), 0) 404 | end 405 | "; 406 | 407 | pub static RUN_HELPERS_LI: &str = " 408 | local function close_lua_upvalues(list, index) 409 | for i, uv in Pairs(list) do 410 | if uv[1] >= index then 411 | --uv.value = uv.store[uv.index] -- store value 412 | --uv.store = uv 413 | --uv.index = 'value' -- self reference 414 | list[i] = nil 415 | end 416 | end 417 | end 418 | 419 | local function open_lua_upvalue(list, index, memory) 420 | local prev = list[index] 421 | 422 | if not prev then 423 | prev = {index, memory} 424 | list[index] = prev 425 | end 426 | 427 | return prev 428 | end 429 | 430 | local function on_lua_error(failed, err) 431 | local src = failed[2] 432 | local line = failed[3][failed[1] - 1] 433 | 434 | Error(StringConcat(src, ':', line, ':', err), 0) 435 | end 436 | "; 437 | 438 | pub static RUN: &str = " 439 | local function run_lua_func(state, env, upvals) 440 | local code = state[3] 441 | local subs = state[4] 442 | local vararg = state[1] 443 | 444 | local top_index = -1 445 | local open_list = {} 446 | local memory = state[2] 447 | local pc = state[5] 448 | 449 | local function constantB(inst) 450 | return inst[$IS_KB$] and inst[$CONST_B$] or memory[inst[$B_REGISTER$]] 451 | end 452 | 453 | local function constantC(inst) 454 | return inst[$IS_KC$] and inst[$CONST_C$] or memory[inst[$C_REGISTER$]] 455 | end 456 | 457 | while true do 458 | local inst = code[pc] 459 | local op = inst[$OPCODE$] 460 | pc = pc + 1 461 | 462 | "; 463 | 464 | pub static RUN_2: &str = " 465 | state[5] = pc 466 | end 467 | end 468 | 469 | function lua_wrap_state(proto, env, upval) 470 | env = env or Getfenv(0) 471 | 472 | local function wrapped(...) 473 | local passed = TablePack(...) 474 | local memory = TableCreate() 475 | local vararg = {0, {}} 476 | 477 | TableMove(passed, 1, proto[$PARAMETER_COUNT$], 0, memory) 478 | 479 | if proto[$PARAMETER_COUNT$] < passed.n then 480 | local start = proto[$PARAMETER_COUNT$] + 1 481 | local len = passed.n - proto[$PARAMETER_COUNT$] 482 | 483 | vararg[1] = len 484 | TableMove(passed, start, start + len - 1, 1, vararg[2]) 485 | end 486 | 487 | local state = {vararg, memory, proto[$OPCODE_LIST$], proto[$PROTO_LIST$], 1} 488 | 489 | local result = TablePack(Pcall(run_lua_func, state, env, upval)) 490 | 491 | if result[1] then 492 | return TableUnpack(result, 2, result.n) 493 | else 494 | local failed = {state[5], proto[$SOURCE_NAME$] --[[,lines = proto.lines]]} 495 | 496 | on_lua_error(failed, result[2]) 497 | 498 | return 499 | end 500 | end 501 | 502 | return wrapped 503 | end 504 | "; 505 | 506 | pub static RUN_2_LI: &str = " 507 | state[5] = pc 508 | end 509 | end 510 | 511 | function lua_wrap_state(proto, env, upval) 512 | env = env or Getfenv(0) 513 | 514 | local function wrapped(...) 515 | local passed = TablePack(...) 516 | local memory = TableCreate() 517 | local vararg = {0, {}} 518 | 519 | TableMove(passed, 1, proto[$PARAMETER_COUNT$], 0, memory) 520 | 521 | if proto[$PARAMETER_COUNT$] < passed.n then 522 | local start = proto[$PARAMETER_COUNT$] + 1 523 | local len = passed.n - proto[$PARAMETER_COUNT$] 524 | 525 | vararg[1] = len 526 | TableMove(passed, start, start + len - 1, 1, vararg[2]) 527 | end 528 | 529 | local state = {vararg, memory, proto[$OPCODE_LIST$], proto[$PROTO_LIST$], 1} 530 | 531 | local result = TablePack(Pcall(run_lua_func, state, env, upval)) 532 | 533 | if result[1] then 534 | return TableUnpack(result, 2, result.n) 535 | else 536 | local failed = {state[5], proto[$SOURCE_NAME$], proto[$LINE_LIST$]} 537 | 538 | on_lua_error(failed, result[2]) 539 | 540 | return 541 | end 542 | end 543 | 544 | return wrapped 545 | end 546 | "; 547 | -------------------------------------------------------------------------------- /src/obfuscator/vm_generator.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use lua_deserializer::{ 4 | enums::{chunk_components::ChunkComponents, lua_type::LuaType, opcode_type::OpcodeType}, 5 | structs::chunk::Chunk, 6 | }; 7 | use rand::seq::SliceRandom; 8 | 9 | use crate::{ 10 | obfuscation_settings::ObfuscationSettings, obfuscator::obfuscation_context::ObfuscationContext, 11 | }; 12 | 13 | use super::{ 14 | serializer::Serializer, 15 | vm::{opcode_strings, vm_strings}, 16 | }; 17 | 18 | #[derive(Clone, PartialEq, Eq)] 19 | pub enum ConstantType { 20 | NIL, 21 | BOOLEAN, 22 | NUMBER, 23 | STRING, 24 | } 25 | 26 | fn get_used_opcodes(chunk: &Chunk) -> Vec { 27 | let mut opcodes = vec![]; 28 | 29 | for instruction in &chunk.instructions { 30 | if !opcodes.contains(&instruction.opcode) { 31 | opcodes.push(instruction.opcode); 32 | } 33 | } 34 | 35 | for proto in &chunk.protos { 36 | for opcode in get_used_opcodes(proto) { 37 | if !opcodes.contains(&opcode) { 38 | opcodes.push(opcode); 39 | } 40 | } 41 | } 42 | 43 | opcodes 44 | } 45 | 46 | fn create_context( 47 | const_list: [ConstantType; 4], 48 | opcode_list: Vec, 49 | chunk_component_list: [ChunkComponents; 3], 50 | ) -> ObfuscationContext { 51 | ObfuscationContext { 52 | constant_type_map: const_list, 53 | opcode_map: opcode_list, 54 | chunk_component_map: chunk_component_list, 55 | } 56 | } 57 | 58 | fn index_of(list: &[T], value: T) -> usize 59 | where 60 | T: PartialEq, 61 | { 62 | list.iter().position(|v| *v == value).unwrap() 63 | } 64 | 65 | // From: https://rosettacode.org/wiki/LZW_compression#Rust 66 | fn compress(data: Vec) -> Vec { 67 | // Build initial dictionary. 68 | let mut dictionary: HashMap, u32> = (0u32..=255).map(|i| (vec![i as u8], i)).collect(); 69 | 70 | let mut w = Vec::new(); 71 | let mut compressed = Vec::new(); 72 | 73 | for b in data { 74 | let mut wc = w.clone(); 75 | wc.push(b); 76 | 77 | if dictionary.contains_key(&wc) { 78 | w = wc; 79 | } else { 80 | // Write w to output. 81 | compressed.push(dictionary[&w]); 82 | 83 | // wc is a new sequence; add it to the dictionary. 84 | dictionary.insert(wc, dictionary.len() as u32); 85 | w.clear(); 86 | w.push(b); 87 | } 88 | } 89 | 90 | // Write remaining output if necessary. 91 | if !w.is_empty() { 92 | compressed.push(dictionary[&w]); 93 | } 94 | 95 | compressed 96 | } 97 | 98 | static BASE64_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 99 | 100 | fn to_base64(value: u64) -> String { 101 | let mut ret = String::new(); 102 | let mut value: usize = value.try_into().unwrap(); 103 | 104 | loop { 105 | ret.push( 106 | BASE64_CHARS 107 | .chars() 108 | .nth(value % BASE64_CHARS.len()) 109 | .unwrap(), 110 | ); 111 | value /= BASE64_CHARS.len(); 112 | 113 | if value == 0 { 114 | break; 115 | } 116 | } 117 | 118 | ret.chars().rev().collect() 119 | } 120 | 121 | pub struct VMGenerator; 122 | 123 | impl VMGenerator { 124 | pub fn new() -> Self { 125 | Self {} 126 | } 127 | 128 | pub fn generate(&self, main_chunk: Chunk, settings: ObfuscationSettings) -> String { 129 | let mut rand = rand::thread_rng(); 130 | 131 | let mut opcode_list = get_used_opcodes(&main_chunk); 132 | opcode_list.shuffle(&mut rand); 133 | 134 | let mut constant_list = [ 135 | ConstantType::NIL, 136 | ConstantType::BOOLEAN, 137 | ConstantType::NUMBER, 138 | ConstantType::STRING, 139 | ]; 140 | constant_list.shuffle(&mut rand); 141 | 142 | let mut chunk_component_list = [ 143 | ChunkComponents::CONSTANTS, 144 | ChunkComponents::INSTRUCTIONS, 145 | ChunkComponents::PROTOS, 146 | ]; 147 | chunk_component_list.shuffle(&mut rand); 148 | 149 | let obfuscation_context = 150 | create_context(constant_list, opcode_list.clone(), chunk_component_list); 151 | 152 | let mut serializer = Serializer::new(obfuscation_context.clone(), settings.clone()); 153 | let bytes = serializer.serialze(main_chunk); 154 | 155 | let bytecode_string: String = if settings.compress_bytecode { 156 | compress(bytes) 157 | .into_iter() 158 | .map(|v| { 159 | let byte_str = to_base64(v as u64); 160 | to_base64(byte_str.len() as u64) + &byte_str 161 | }) 162 | .collect() 163 | } else { 164 | bytes 165 | .into_iter() 166 | .map(|v| String::from("\\") + &v.to_string()) 167 | .collect() 168 | }; 169 | 170 | let mut vm_string = String::new(); 171 | 172 | vm_string += vm_strings::VARIABLE_DECLARATION; 173 | vm_string += vm_strings::DESERIALIZER; 174 | vm_string += &format!( 175 | " 176 | local function stm_const_list(S) 177 | local len = stm_int64(S) 178 | local list = TableCreate(len) 179 | 180 | for i = 1, len do 181 | local tt = stm_byte(S) 182 | local k 183 | 184 | if tt == {} then -- Bool 185 | k = stm_byte(S) ~= 0 186 | elseif tt == {} then -- Number 187 | k = stm_num(S) 188 | elseif tt == {} then -- String 189 | k = stm_lstring(S) 190 | end 191 | 192 | list[i] = k -- offset +1 during instruction decode 193 | end 194 | 195 | return list 196 | end 197 | ", 198 | index_of( 199 | &obfuscation_context.constant_type_map, 200 | ConstantType::BOOLEAN 201 | ), 202 | index_of(&obfuscation_context.constant_type_map, ConstantType::NUMBER), 203 | index_of(&obfuscation_context.constant_type_map, ConstantType::STRING), 204 | ); 205 | vm_string += vm_strings::DESERIALIZER_2; 206 | 207 | for component in &obfuscation_context.chunk_component_map { 208 | vm_string += match component { 209 | ChunkComponents::CONSTANTS => "proto[$CONSTANT_LIST$] = stm_const_list(stream)", 210 | ChunkComponents::INSTRUCTIONS => "proto[$OPCODE_LIST$] = stm_inst_list(stream)", 211 | ChunkComponents::PROTOS => "proto[$PROTO_LIST$] = stm_sub_list(stream, src)", 212 | }; 213 | vm_string += "\n"; 214 | } 215 | 216 | if settings.include_debug_line_info { 217 | vm_string += "proto[$LINE_LIST$] = stm_line_list(stream)"; 218 | } 219 | 220 | vm_string += vm_strings::DESERIALIZER_3; 221 | 222 | vm_string += if settings.include_debug_line_info { 223 | vm_strings::RUN_HELPERS_LI 224 | } else { 225 | vm_strings::RUN_HELPERS 226 | }; 227 | vm_string += vm_strings::RUN; 228 | 229 | for (i, opcode) in obfuscation_context.opcode_map.iter().enumerate() { 230 | vm_string += if i == 0 { "if " } else { " elseif " }; 231 | vm_string += &format!("op == {} then --[[{:#?}]] ", i, opcode); 232 | vm_string += &opcode_strings::get_opcode_string(opcode, &opcode_list); 233 | } 234 | 235 | vm_string += " end"; 236 | 237 | vm_string += if settings.include_debug_line_info { 238 | vm_strings::RUN_2_LI 239 | } else { 240 | vm_strings::RUN_2 241 | }; 242 | 243 | if settings.compress_bytecode { 244 | vm_string += " 245 | local base36Chars = StringChar(TableUnpack(TableMerge(RangeGen(48, 57), RangeGen(65, 90)))) 246 | 247 | local function base36Decode(inputStr) 248 | local num, str = 0, StringReverse(inputStr) 249 | 250 | for i = 1, #str do 251 | num = num + StringFind(base36Chars, StringSub(str, i, i)) * 36 ^ (i - 1) 252 | end 253 | 254 | return num 255 | end 256 | 257 | -- From https://rosettacode.org/wiki/LZW_compression#Lua 258 | local function decompress(compressed) -- table 259 | local dictionary, dictSize, entry, w, k = {}, 256, '', StringChar(compressed[1]) 260 | local result = {w} 261 | for i = 0, 255 do 262 | dictionary[i] = StringChar(i) 263 | end 264 | for i = 2, #compressed do 265 | k = compressed[i] 266 | if dictionary[k] then 267 | entry = dictionary[k] 268 | elseif k == dictSize then 269 | entry = w .. StringSub(w, 1, 1) 270 | else 271 | return nil, i 272 | end 273 | TableInsert(result, entry) 274 | dictionary[dictSize] = w .. StringSub(entry, 1, 1) 275 | dictSize = dictSize + 1 276 | w = entry 277 | end 278 | return TableConcat(result) 279 | end 280 | 281 | local function decode(bytecode) 282 | local ret = {} 283 | local i = 1 284 | while i <= #bytecode do 285 | local len = base36Decode(StringSub(bytecode, i, i)) 286 | i = i + 1 287 | TableInsert(ret, base36Decode(StringSub(bytecode, i, i + len - 1))) 288 | i = i + len 289 | end 290 | 291 | return decompress(ret) 292 | end 293 | "; 294 | } 295 | 296 | if settings.compress_bytecode { 297 | vm_string += &format!( 298 | "lua_wrap_state(lua_bc_to_state(decode('{}')))()", 299 | bytecode_string 300 | ); 301 | } else { 302 | vm_string += &format!("lua_wrap_state(lua_bc_to_state('{}'))()", bytecode_string); 303 | } 304 | 305 | let mut rename_map = [ 306 | "OPCODE", 307 | "A_REGISTER", 308 | "B_REGISTER", 309 | "C_REGISTER", 310 | "IS_CONST", 311 | "IS_KB", 312 | "IS_KC", 313 | "CONSTANT", 314 | "CONST_B", 315 | "CONST_C", 316 | "SOURCE_NAME", 317 | "UPVALUE_COUNT", 318 | "PARAMETER_COUNT", 319 | "CONSTANT_LIST", 320 | "OPCODE_LIST", 321 | "PROTO_LIST", 322 | "LINE_LIST", 323 | ]; 324 | rename_map.shuffle(&mut rand); 325 | 326 | for (i, rename) in rename_map.iter().enumerate() { 327 | vm_string = vm_string.replace(&format!("${}$", *rename), &(i + 1).to_string()); 328 | } 329 | 330 | let move_opcode = opcode_list.iter().position(|&r| r == OpcodeType::OpMove); 331 | let getupval_opcode = opcode_list 332 | .iter() 333 | .position(|&r| r == OpcodeType::OpGetUpval); 334 | 335 | if move_opcode != None { 336 | vm_string = vm_string.replace("$MOVE_OPCODE$", &move_opcode.unwrap().to_string()); 337 | } 338 | 339 | if getupval_opcode != None { 340 | vm_string = 341 | vm_string.replace("$GETUPVAL_OPCODE$", &getupval_opcode.unwrap().to_string()); 342 | } 343 | 344 | vm_string 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /vm/custom_vm.lua: -------------------------------------------------------------------------------- 1 | -- Based on FiOne https://github.com/Rerumu/FiOne/blob/master/source.lua 2 | 3 | local String = string 4 | local StringChar = String.char 5 | local StringByte = String.byte 6 | local Select = select 7 | local Table = table 8 | local Math = math 9 | local TableCreate = function(...) 10 | return {} 11 | end 12 | local TableUnpack = Table.unpack or unpack 13 | local TablePack = function(...) 14 | return { n = Select(StringChar(35), ...), ... } 15 | end 16 | local TableMove = function(src, first, last, offset, dst) 17 | for i = 0, last - first do 18 | dst[offset + i] = src[first + i] 19 | end 20 | end 21 | local TableConcat = Table.concat 22 | local Getfenv = getfenv 23 | local MathFloor = Math.floor 24 | local MathMax = Math.max 25 | local Pcall = pcall 26 | local MathAbs = Math.abs 27 | local Tonumber = tonumber 28 | local BitAnd, BitRShift, BitLShift = (function() 29 | local function tobittable_r(x, ...) 30 | if (x or 0) == 0 then 31 | return ... 32 | end 33 | return tobittable_r(MathFloor(x / 2), x % 2, ...) 34 | end 35 | 36 | local function tobittable(x) 37 | if x == 0 then 38 | return { 0 } 39 | end 40 | return { tobittable_r(x) } 41 | end 42 | 43 | local function makeop(cond) 44 | local function oper(x, y, ...) 45 | if not y then 46 | return x 47 | end 48 | x, y = tobittable(x), tobittable(y) 49 | local xl, yl = #x, #y 50 | local t, tl = {}, MathMax(xl, yl) 51 | for i = 0, tl - 1 do 52 | local b1, b2 = x[xl - i], y[yl - i] 53 | if not (b1 or b2) then 54 | break 55 | end 56 | t[tl - i] = (cond((b1 or 0) ~= 0, (b2 or 0) ~= 0) and 1 or 0) 57 | end 58 | return oper(Tonumber(TableConcat(t), 2), ...) 59 | end 60 | return oper 61 | end 62 | 63 | --- 64 | -- Perform bitwise AND of several numbers. 65 | -- Truth table: 66 | -- band(0,0) -> 0, 67 | -- band(0,1) -> 0, 68 | -- band(1,0) -> 0, 69 | -- band(1,1) -> 1. 70 | -- @class function 71 | -- @name band 72 | -- @param ... Numbers. 73 | -- @return A number. 74 | local band = makeop(function(a, b) 75 | return a and b 76 | end) 77 | 78 | --- 79 | -- Shift a number's bits to the left. 80 | -- Roughly equivalent to (x * (2^bits)). 81 | -- @param x The number to shift (number). 82 | -- @param bits Number of positions to shift by (number). 83 | -- @return A number. 84 | local function blshift(x, bits) 85 | return MathFloor(x) * (2 ^ bits) 86 | end 87 | 88 | --- 89 | -- Shift a number's bits to the right. 90 | -- Roughly equivalent to (x / (2^bits)). 91 | -- @param x The number to shift (number). 92 | -- @param bits Number of positions to shift by (number). 93 | -- @return A number. 94 | local function brshift(x, bits) 95 | return MathFloor(MathFloor(x) / (2 ^ bits)) 96 | end 97 | 98 | return band, brshift, blshift 99 | end)() 100 | 101 | local lua_bc_to_state 102 | local lua_wrap_state 103 | local stm_lua_func 104 | 105 | -- int rd_int_basic(string src, int s, int e, int d) 106 | -- @src - Source binary string 107 | -- @s - Start index of a little endian integer 108 | -- @e - End index of the integer 109 | -- @d - Direction of the loop 110 | local function rd_int_basic(src, s, e, d) 111 | local num = 0 112 | 113 | -- if bb[l] > 127 then -- signed negative 114 | -- num = num - 256 ^ l 115 | -- bb[l] = bb[l] - 128 116 | -- end 117 | 118 | for i = s, e, d do 119 | local mul = 256 ^ MathAbs(i - s) 120 | 121 | num = num + mul * StringByte(src, i, i) 122 | end 123 | 124 | return num 125 | end 126 | 127 | -- double rd_dbl_basic(byte f1..8) 128 | -- @f1..8 - The 8 bytes composing a little endian double 129 | local function rd_dbl_basic(f1, f2, f3, f4, f5, f6, f7, f8) 130 | local sign = (-1) ^ BitRShift(f8, 7) 131 | local exp = BitLShift(BitAnd(f8, 0x7F), 4) + BitRShift(f7, 4) 132 | local frac = BitAnd(f7, 0x0F) * 2 ^ 48 133 | local normal = 1 134 | 135 | frac = frac + (f6 * 2 ^ 40) + (f5 * 2 ^ 32) + (f4 * 2 ^ 24) + (f3 * 2 ^ 16) + (f2 * 2 ^ 8) + f1 -- help 136 | 137 | if exp == 0 then 138 | if frac == 0 then 139 | return sign * 0 140 | else 141 | normal = 0 142 | exp = 1 143 | end 144 | elseif exp == 0x7FF then 145 | if frac == 0 then 146 | return sign * (1 / 0) 147 | else 148 | return sign * (0 / 0) 149 | end 150 | end 151 | 152 | return sign * 2 ^ (exp - 1023) * (normal + frac / 2 ^ 52) 153 | end 154 | 155 | -- int rd_int_le(string src, int s, int e) 156 | -- @src - Source binary string 157 | -- @s - Start index of a little endian integer 158 | -- @e - End index of the integer 159 | local function rd_int_le(src, s, e) return rd_int_basic(src, s, e - 1, 1) end 160 | 161 | -- double rd_dbl_le(string src, int s) 162 | -- @src - Source binary string 163 | -- @s - Start index of little endian double 164 | local function rd_dbl_le(src, s) return rd_dbl_basic(string.byte(src, s, s + 7)) end 165 | 166 | -- byte stm_byte(Stream S) 167 | -- @S - Stream object to read from 168 | local function stm_byte(S) 169 | local idx = S.index 170 | local bt = string.byte(S.source, idx, idx) 171 | 172 | S.index = idx + 1 173 | return bt 174 | end 175 | 176 | -- string stm_string(Stream S, int len) 177 | -- @S - Stream object to read from 178 | -- @len - Length of string being read 179 | local function stm_string(S, len) 180 | local pos = S.index + len 181 | local str = string.sub(S.source, S.index, pos - 1) 182 | 183 | S.index = pos 184 | return str 185 | end 186 | 187 | -- string stm_lstring(Stream S) 188 | -- @S - Stream object to read from 189 | local function stm_lstring(S) 190 | local len = S:int64() 191 | local str 192 | 193 | if len ~= 0 then str = string.sub(stm_string(S, len), 1, -2) end 194 | 195 | return str 196 | end 197 | 198 | -- fn cst_int_rdr(string src, int len, fn func) 199 | -- @len - Length of type for reader 200 | -- @func - Reader callback 201 | local function cst_int_rdr(len, func) 202 | return function(S) 203 | local pos = S.index + len 204 | local int = func(S.source, S.index, pos) 205 | S.index = pos 206 | 207 | return int 208 | end 209 | end 210 | 211 | -- fn cst_flt_rdr(string src, int len, fn func) 212 | -- @len - Length of type for reader 213 | -- @func - Reader callback 214 | local function cst_flt_rdr(len, func) 215 | return function(S) 216 | local flt = func(S.source, S.index) 217 | S.index = S.index + len 218 | 219 | return flt 220 | end 221 | end 222 | 223 | local function stm_inst_list(S) 224 | local len = S:int64() 225 | local list = TableCreate(len) 226 | 227 | for i = 1, len do 228 | -- local ins = S:int16() 229 | local op = stm_byte(S) 230 | local args = stm_byte(S) 231 | local isConstantB = stm_byte(S) == 1 232 | local isConstantC = stm_byte(S) == 1 233 | local data = {op = op, A = stm_byte(S)} 234 | 235 | if args == 1 then -- ABC 236 | data.B = S:int16() 237 | data.C = S:int16() 238 | data.is_KB = isConstantB and data.B > 0xFF -- post process optimization 239 | data.is_KC = isConstantC and data.C > 0xFF 240 | elseif args == 2 then -- ABx 241 | data.Bx = S:int32() 242 | data.is_K = isConstantB 243 | elseif args == 3 then -- AsBx 244 | data.sBx = S:int32() - 131071 245 | end 246 | 247 | list[i] = data 248 | end 249 | 250 | 251 | return list 252 | end 253 | 254 | local function stm_const_list(S) 255 | local len = S:int64() 256 | local list = TableCreate(len) 257 | 258 | for i = 1, len do 259 | local tt = stm_byte(S) 260 | local k 261 | 262 | if tt == 1 then 263 | k = stm_byte(S) ~= 0 264 | elseif tt == 2 then 265 | k = S:s_num() 266 | elseif tt == 3 then 267 | k = stm_lstring(S) 268 | end 269 | 270 | list[i] = k -- offset +1 during instruction decode 271 | end 272 | 273 | return list 274 | end 275 | 276 | local function stm_sub_list(S, src) 277 | local len = S:int64() 278 | local list = TableCreate(len) 279 | 280 | for i = 1, len do 281 | list[i] = stm_lua_func(S, src) -- offset +1 in CLOSURE 282 | end 283 | 284 | return list 285 | end 286 | 287 | local function stm_line_list(S) 288 | local len = S:s_int() 289 | local list = TableCreate(len) 290 | 291 | for i = 1, len do list[i] = S:s_int() end 292 | 293 | return list 294 | end 295 | 296 | function stm_lua_func(stream, psrc) 297 | local proto = {} 298 | local src = stm_lstring(stream) or psrc -- source is propagated 299 | 300 | proto.source = src -- source name 301 | 302 | -- stream:s_int() -- line defined 303 | -- stream:s_int() -- last line defined 304 | 305 | proto.num_upval = stm_byte(stream) -- num upvalues 306 | proto.num_param = stm_byte(stream) -- num params 307 | 308 | 309 | -- stm_byte(stream) -- vararg flag 310 | -- proto.max_stack = stm_byte(stream) -- max stack size 311 | 312 | proto.const = stm_const_list(stream) 313 | proto.code = stm_inst_list(stream) 314 | proto.subs = stm_sub_list(stream, src) 315 | -- proto.lines = stm_line_list(stream) 316 | 317 | -- post process optimization 318 | for _, v in ipairs(proto.code) do 319 | if v.is_K then 320 | v.const = proto.const[v.Bx + 1] -- offset for 1 based index 321 | else 322 | if v.is_KB then v.const_B = proto.const[v.B - 0xFF] end 323 | 324 | if v.is_KC then v.const_C = proto.const[v.C - 0xFF] end 325 | end 326 | end 327 | 328 | return proto 329 | end 330 | 331 | function lua_bc_to_state(src) 332 | -- stream object 333 | local stream = { 334 | -- data 335 | index = 1, 336 | source = src, 337 | int16 = cst_int_rdr(2, rd_int_le), 338 | int32 = cst_int_rdr(4, rd_int_le), 339 | int64 = cst_int_rdr(8, rd_int_le), 340 | s_num = cst_flt_rdr(8, rd_dbl_le) 341 | } 342 | 343 | return stm_lua_func(stream, '@virtual') 344 | end 345 | 346 | local function close_lua_upvalues(list, index) 347 | for i, uv in pairs(list) do 348 | if uv.index >= index then 349 | uv.value = uv.store[uv.index] -- store value 350 | uv.store = uv 351 | uv.index = 'value' -- self reference 352 | list[i] = nil 353 | end 354 | end 355 | end 356 | 357 | local function open_lua_upvalue(list, index, memory) 358 | local prev = list[index] 359 | 360 | if not prev then 361 | prev = {index = index, store = memory} 362 | list[index] = prev 363 | end 364 | 365 | return prev 366 | end 367 | 368 | local function on_lua_error(failed, err) 369 | local src = failed.source 370 | -- local line = failed.lines[failed.pc - 1] 371 | local line = 0 372 | 373 | error(string.format('%s:%i: %s', src, line, err), 0) 374 | end 375 | 376 | local function run_lua_func(state, env, upvals) 377 | local code = state.code 378 | local subs = state.subs 379 | local vararg = state.vararg 380 | 381 | local top_index = -1 382 | local open_list = {} 383 | local memory = state.memory 384 | local pc = state.pc 385 | 386 | while true do 387 | local inst = code[pc] 388 | local op = inst.op 389 | pc = pc + 1 390 | 391 | if op == 0 then 392 | --[[MOVE]] 393 | memory[inst.A] = memory[inst.B] 394 | elseif op == 1 then 395 | --[[LOADK]] 396 | memory[inst.A] = inst.const 397 | elseif op == 2 then 398 | --[[LOADBOOL]] 399 | memory[inst.A] = inst.B ~= 0 400 | 401 | if inst.C ~= 0 then pc = pc + 1 end 402 | elseif op == 3 then 403 | --[[LOADNIL]] 404 | for i = inst.A, inst.B do memory[i] = nil end 405 | elseif op == 4 then 406 | --[[GETUPVAL]] 407 | local uv = upvals[inst.B] 408 | 409 | memory[inst.A] = uv.store[uv.index] 410 | elseif op == 5 then 411 | --[[GETGLOBAL]] 412 | memory[inst.A] = env[inst.const] 413 | elseif op == 6 then 414 | --[[GETTABLE]] 415 | local index 416 | 417 | if inst.is_KC then 418 | index = inst.const_C 419 | else 420 | index = memory[inst.C] 421 | end 422 | 423 | memory[inst.A] = memory[inst.B][index] 424 | elseif op == 7 then 425 | --[[SETGLOBAL]] 426 | env[inst.const] = memory[inst.A] 427 | elseif op == 8 then 428 | --[[SETUPVAL]] 429 | local uv = upvals[inst.B] 430 | 431 | uv.store[uv.index] = memory[inst.A] 432 | elseif op == 9 then 433 | --[[SETTABLE]] 434 | local index, value 435 | 436 | if inst.is_KB then 437 | index = inst.const_B 438 | else 439 | index = memory[inst.B] 440 | end 441 | 442 | if inst.is_KC then 443 | value = inst.const_C 444 | else 445 | value = memory[inst.C] 446 | end 447 | 448 | memory[inst.A][index] = value 449 | elseif op == 10 then 450 | --[[NEWTABLE]] 451 | memory[inst.A] = {} 452 | elseif op == 11 then 453 | --[[SELF]] 454 | local A = inst.A 455 | local B = inst.B 456 | local index 457 | 458 | if inst.is_KC then 459 | index = inst.const_C 460 | else 461 | index = memory[inst.C] 462 | end 463 | 464 | memory[A + 1] = memory[B] 465 | memory[A] = memory[B][index] 466 | elseif op == 12 then 467 | --[[ADD]] 468 | local lhs, rhs 469 | 470 | if inst.is_KB then 471 | lhs = inst.const_B 472 | else 473 | lhs = memory[inst.B] 474 | end 475 | 476 | if inst.is_KC then 477 | rhs = inst.const_C 478 | else 479 | rhs = memory[inst.C] 480 | end 481 | 482 | memory[inst.A] = lhs + rhs 483 | elseif op == 13 then 484 | --[[SUB]] 485 | local lhs, rhs 486 | 487 | if inst.is_KB then 488 | lhs = inst.const_B 489 | else 490 | lhs = memory[inst.B] 491 | end 492 | 493 | if inst.is_KC then 494 | rhs = inst.const_C 495 | else 496 | rhs = memory[inst.C] 497 | end 498 | 499 | memory[inst.A] = lhs - rhs 500 | elseif op == 14 then 501 | --[[MUL]] 502 | local lhs, rhs 503 | 504 | if inst.is_KB then 505 | lhs = inst.const_B 506 | else 507 | lhs = memory[inst.B] 508 | end 509 | 510 | if inst.is_KC then 511 | rhs = inst.const_C 512 | else 513 | rhs = memory[inst.C] 514 | end 515 | 516 | memory[inst.A] = lhs * rhs 517 | elseif op == 15 then 518 | --[[DIV]] 519 | local lhs, rhs 520 | 521 | if inst.is_KB then 522 | lhs = inst.const_B 523 | else 524 | lhs = memory[inst.B] 525 | end 526 | 527 | if inst.is_KC then 528 | rhs = inst.const_C 529 | else 530 | rhs = memory[inst.C] 531 | end 532 | 533 | memory[inst.A] = lhs / rhs 534 | elseif op == 16 then 535 | --[[MOD]] 536 | local lhs, rhs 537 | 538 | if inst.is_KB then 539 | lhs = inst.const_B 540 | else 541 | lhs = memory[inst.B] 542 | end 543 | 544 | if inst.is_KC then 545 | rhs = inst.const_C 546 | else 547 | rhs = memory[inst.C] 548 | end 549 | 550 | memory[inst.A] = lhs % rhs 551 | elseif op == 17 then 552 | --[[POW]] 553 | local lhs, rhs 554 | 555 | if inst.is_KB then 556 | lhs = inst.const_B 557 | else 558 | lhs = memory[inst.B] 559 | end 560 | 561 | if inst.is_KC then 562 | rhs = inst.const_C 563 | else 564 | rhs = memory[inst.C] 565 | end 566 | 567 | memory[inst.A] = lhs ^ rhs 568 | elseif op == 18 then 569 | --[[UNM]] 570 | memory[inst.A] = -memory[inst.B] 571 | elseif op == 19 then 572 | --[[NOT]] 573 | memory[inst.A] = not memory[inst.B] 574 | elseif op == 20 then 575 | --[[LEN]] 576 | memory[inst.A] = #memory[inst.B] 577 | elseif op == 21 then 578 | --[[CONCAT]] 579 | local B = inst.B 580 | local str = memory[B] 581 | 582 | for i = B + 1, inst.C do str = str .. memory[i] end 583 | 584 | memory[inst.A] = str 585 | elseif op == 22 then 586 | --[[JMP]] 587 | pc = pc + inst.sBx 588 | elseif op == 23 then 589 | --[[EQ]] 590 | local lhs, rhs 591 | 592 | if inst.is_KB then 593 | lhs = inst.const_B 594 | else 595 | lhs = memory[inst.B] 596 | end 597 | 598 | if inst.is_KC then 599 | rhs = inst.const_C 600 | else 601 | rhs = memory[inst.C] 602 | end 603 | 604 | if (lhs == rhs) == (inst.A ~= 0) then pc = pc + code[pc].sBx end 605 | 606 | pc = pc + 1 607 | elseif op == 24 then 608 | --[[LT]] 609 | local lhs, rhs 610 | 611 | if inst.is_KB then 612 | lhs = inst.const_B 613 | else 614 | lhs = memory[inst.B] 615 | end 616 | 617 | if inst.is_KC then 618 | rhs = inst.const_C 619 | else 620 | rhs = memory[inst.C] 621 | end 622 | 623 | if (lhs < rhs) == (inst.A ~= 0) then pc = pc + code[pc].sBx end 624 | 625 | pc = pc + 1 626 | elseif op == 25 then 627 | --[[LE]] 628 | local lhs, rhs 629 | 630 | if inst.is_KB then 631 | lhs = inst.const_B 632 | else 633 | lhs = memory[inst.B] 634 | end 635 | 636 | if inst.is_KC then 637 | rhs = inst.const_C 638 | else 639 | rhs = memory[inst.C] 640 | end 641 | 642 | if (lhs <= rhs) == (inst.A ~= 0) then pc = pc + code[pc].sBx end 643 | 644 | pc = pc + 1 645 | elseif op == 26 then 646 | --[[TEST]] 647 | if (not memory[inst.A]) ~= (inst.C ~= 0) then pc = pc + code[pc].sBx end 648 | pc = pc + 1 649 | elseif op == 27 then 650 | --[[TESTSET]] 651 | local A = inst.A 652 | local B = inst.B 653 | 654 | if (not memory[B]) ~= (inst.C ~= 0) then 655 | memory[A] = memory[B] 656 | pc = pc + code[pc].sBx 657 | end 658 | pc = pc + 1 659 | elseif op == 28 then 660 | --[[CALL]] 661 | local A = inst.A 662 | local B = inst.B 663 | local C = inst.C 664 | local params 665 | 666 | if B == 0 then 667 | params = top_index - A 668 | else 669 | params = B - 1 670 | end 671 | 672 | local ret_list = TablePack(memory[A](TableUnpack(memory, A + 1, A + params))) 673 | local ret_num = ret_list.n 674 | 675 | if C == 0 then 676 | top_index = A + ret_num - 1 677 | else 678 | ret_num = C - 1 679 | end 680 | 681 | TableMove(ret_list, 1, ret_num, A, memory) 682 | elseif op == 29 then 683 | --[[TAILCALL]] 684 | local A = inst.A 685 | local B = inst.B 686 | local params 687 | 688 | if B == 0 then 689 | params = top_index - A 690 | else 691 | params = B - 1 692 | end 693 | 694 | close_lua_upvalues(open_list, 0) 695 | 696 | return memory[A](TableUnpack(memory, A + 1, A + params)) 697 | elseif op == 30 then 698 | --[[RETURN]] 699 | local A = inst.A 700 | local B = inst.B 701 | local len 702 | 703 | if B == 0 then 704 | len = top_index - A + 1 705 | else 706 | len = B - 1 707 | end 708 | 709 | close_lua_upvalues(open_list, 0) 710 | 711 | return TableUnpack(memory, A, A + len - 1) 712 | elseif op == 31 then 713 | --[[FORLOOP]] 714 | local A = inst.A 715 | local step = memory[A + 2] 716 | local index = memory[A] + step 717 | local limit = memory[A + 1] 718 | local loops 719 | 720 | if step == MathAbs(step) then 721 | loops = index <= limit 722 | else 723 | loops = index >= limit 724 | end 725 | 726 | if loops then 727 | memory[A] = index 728 | memory[A + 3] = index 729 | pc = pc + inst.sBx 730 | end 731 | elseif op == 32 then 732 | --[[FORPREP]] 733 | local A = inst.A 734 | -- local init, limit, step 735 | 736 | -- *: Possible additional error checking 737 | -- init = assert(tonumber(memory[A]), '`for` initial value must be a number') 738 | -- limit = assert(tonumber(memory[A + 1]), '`for` limit must be a number') 739 | -- step = assert(tonumber(memory[A + 2]), '`for` step must be a number') 740 | 741 | local init = Tonumber(memory[A]) 742 | local limit = Tonumber(memory[A + 1]) 743 | local step = Tonumber(memory[A + 2]) 744 | 745 | memory[A] = init - step 746 | memory[A + 1] = limit 747 | memory[A + 2] = step 748 | 749 | pc = pc + inst.sBx 750 | elseif op == 33 then 751 | --[[TFORLOOP]] 752 | local A = inst.A 753 | local base = A + 3 754 | 755 | local vals = {memory[A](memory[A + 1], memory[A + 2])} 756 | 757 | TableMove(vals, 1, inst.C, base, memory) 758 | 759 | if memory[base] ~= nil then 760 | memory[A + 2] = memory[base] 761 | pc = pc + code[pc].sBx 762 | end 763 | 764 | pc = pc + 1 765 | elseif op == 34 then 766 | --[[SETLIST]] 767 | local A = inst.A 768 | local C = inst.C 769 | local len = inst.B 770 | local tab = memory[A] 771 | local offset 772 | 773 | if len == 0 then len = top_index - A end 774 | 775 | if C == 0 then 776 | C = inst[pc].value 777 | pc = pc + 1 778 | end 779 | 780 | offset = (C - 1) * 50 --FIELDS_PER_FLUSH 781 | 782 | TableMove(memory, A + 1, A + len, offset + 1, tab) 783 | elseif op == 35 then 784 | --[[CLOSE]] 785 | close_lua_upvalues(open_list, inst.A) 786 | elseif op == 36 then 787 | --[[CLOSURE]] 788 | local sub = subs[inst.Bx + 1] -- offset for 1 based index 789 | local nups = sub.num_upval 790 | local uvlist 791 | 792 | if nups ~= 0 then 793 | uvlist = {} 794 | 795 | for i = 1, nups do 796 | local pseudo = code[pc + i - 1] 797 | 798 | if pseudo.op == 0 then -- @MOVE 799 | uvlist[i - 1] = open_lua_upvalue(open_list, pseudo.B, memory) 800 | elseif pseudo.op == 4 then -- @GETUPVAL 801 | uvlist[i - 1] = upvals[pseudo.B] 802 | end 803 | end 804 | 805 | pc = pc + nups 806 | end 807 | 808 | memory[inst.A] = lua_wrap_state(sub, env, uvlist) 809 | elseif op == 37 then 810 | --[[VARARG]] 811 | local A = inst.A 812 | local len = inst.B 813 | 814 | if len == 0 then 815 | len = vararg.len 816 | top_index = A + len - 1 817 | end 818 | 819 | TableMove(vararg.list, 1, len, A, memory) 820 | end 821 | 822 | state.pc = pc 823 | end 824 | end 825 | 826 | function lua_wrap_state(proto, env, upval) 827 | env = env or Getfenv(0) 828 | 829 | local function wrapped(...) 830 | local passed = TablePack(...) 831 | local memory = TableCreate() 832 | local vararg = {len = 0, list = {}} 833 | 834 | TableMove(passed, 1, proto.num_param, 0, memory) 835 | 836 | if proto.num_param < passed.n then 837 | local start = proto.num_param + 1 838 | local len = passed.n - proto.num_param 839 | 840 | vararg.len = len 841 | TableMove(passed, start, start + len - 1, 1, vararg.list) 842 | end 843 | 844 | local state = {vararg = vararg, memory = memory, code = proto.code, subs = proto.subs, pc = 1} 845 | 846 | local result = TablePack(Pcall(run_lua_func, state, env, upval)) 847 | 848 | if result[1] then 849 | return TableUnpack(result, 2, result.n) 850 | else 851 | local failed = {pc = state.pc, source = proto.source --[[,lines = proto.lines]]} 852 | 853 | on_lua_error(failed, result[2]) 854 | 855 | return 856 | end 857 | end 858 | 859 | return wrapped 860 | end 861 | 862 | local bytecode = "\11\0\0\0\0\0\0\0\64\116\101\109\112\49\46\108\117\97\0\0\0\3\0\0\0\0\0\0\0\3\6\0\0\0\0\0\0\0\72\101\108\108\111\0\3\6\0\0\0\0\0\0\0\119\111\114\108\100\0\2\0\0\0\0\0\0\240\63\15\0\0\0\0\0\0\0\10\1\0\0\0\2\0\0\0\1\2\1\0\1\0\0\0\0\1\2\1\0\2\1\0\0\0\34\1\0\0\0\2\0\1\0\36\2\0\0\1\0\0\0\0\1\2\1\0\2\2\0\0\0\20\1\0\0\3\0\0\0\0\25\1\1\1\0\2\0\3\0\22\3\0\0\0\4\0\2\0\0\1\0\0\3\1\0\0\0\6\1\0\1\4\0\0\2\0\28\1\0\0\3\2\0\1\0\12\1\1\1\2\2\0\2\1\22\3\0\0\0\247\255\1\0\30\1\0\0\0\1\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\3\6\0\0\0\0\0\0\0\112\114\105\110\116\0\4\0\0\0\0\0\0\0\5\2\1\0\1\0\0\0\0\37\1\0\0\2\0\0\0\0\28\1\0\0\1\0\0\1\0\30\1\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0" 863 | 864 | lua_wrap_state(lua_bc_to_state(bytecode))() 865 | 866 | -------------------------------------------------------------------------------- /vm/out.lst: -------------------------------------------------------------------------------- 1 | Pos Hex Data Description or Code 2 | ------------------------------------------------------------------------ 3 | 0000 ** source chunk: luac.out 4 | ** global header start ** 5 | 0000 1B4C7561 header signature: "\27Lua" 6 | 0004 51 version (major:minor hex digits) 7 | 0005 00 format (0=official) 8 | 0006 01 endianness (1=little endian) 9 | 0007 04 size of int (bytes) 10 | 0008 08 size of size_t (bytes) 11 | 0009 04 size of Instruction (bytes) 12 | 000A 08 size of number (bytes) 13 | 000B 00 integral (1=integral) 14 | * number type: double 15 | * chunk platform unrecognized 16 | ** global header end ** 17 | 18 | 000C ** function [0] definition (level 1) 19 | ** start of function ** 20 | 000C 0B00000000000000 string size (11) 21 | 0014 40496E7075742E6C+ "@Input.l" 22 | 001C 756100 "ua\0" 23 | source name: @Input.lua 24 | 001F 00000000 line defined (0) 25 | 0023 00000000 last line defined (0) 26 | 0027 00 nups (0) 27 | 0028 00 numparams (0) 28 | 0029 02 is_vararg (2) 29 | 002A 04 maxstacksize (4) 30 | * code: 31 | 002B 0E000000 sizecode (14) 32 | 002F 0A000001 (10)[01] newtable 0 2 0 ; array=2, hash=0 33 | 0033 41000000 (2)[02] loadk 1 0 ; "Hello" 34 | 0037 81400000 (2)[03] loadk 2 1 ; "world" 35 | 003B 22400001 (34)[04] setlist 0 2 1 ; index 1 to 2 36 | 003F 41800000 (2)[05] loadk 1 2 ; 1 37 | 0043 94000000 (20)[06] len 2 0 38 | 0047 19808000 (25)[07] le 0 1 2 ; to [9] if true 39 | 004B 16000180 (22)[08] jmp 5 ; to [14] 40 | 004F 85C00000 (5)[09] getglobal 2 3 ; print 41 | 0053 C6400000 (6)[10] gettable 3 0 1 42 | 0057 9C400001 (28)[11] call 2 2 1 43 | 005B 4C80C000 (12)[12] add 1 1 258 ; 1 44 | 005F 16C0FD7F (22)[13] jmp -8 ; to [6] 45 | 0063 1E008000 (30)[14] return 0 1 46 | * constants: 47 | 0067 04000000 sizek (4) 48 | 006B 04 const type 4 49 | 006C 0600000000000000 string size (6) 50 | 0074 48656C6C6F00 "Hello\0" 51 | const [0]: "Hello" 52 | 007A 04 const type 4 53 | 007B 0600000000000000 string size (6) 54 | 0083 776F726C6400 "world\0" 55 | const [1]: "world" 56 | 0089 03 const type 3 57 | 008A 000000000000F03F const [2]: (1) 58 | 0092 04 const type 4 59 | 0093 0600000000000000 string size (6) 60 | 009B 7072696E7400 "print\0" 61 | const [3]: "print" 62 | * functions: 63 | 00A1 00000000 sizep (0) 64 | * lines: 65 | 00A5 0E000000 sizelineinfo (14) 66 | [pc] (line) 67 | 00A9 01000000 [01] (1) 68 | 00AD 01000000 [02] (1) 69 | 00B1 01000000 [03] (1) 70 | 00B5 01000000 [04] (1) 71 | 00B9 03000000 [05] (3) 72 | 00BD 04000000 [06] (4) 73 | 00C1 04000000 [07] (4) 74 | 00C5 04000000 [08] (4) 75 | 00C9 05000000 [09] (5) 76 | 00CD 05000000 [10] (5) 77 | 00D1 05000000 [11] (5) 78 | 00D5 06000000 [12] (6) 79 | 00D9 06000000 [13] (6) 80 | 00DD 07000000 [14] (7) 81 | * locals: 82 | 00E1 02000000 sizelocvars (2) 83 | 00E5 0200000000000000 string size (2) 84 | 00ED 6100 "a\0" 85 | local [0]: a 86 | 00EF 04000000 startpc (4) 87 | 00F3 0D000000 endpc (13) 88 | 00F7 0200000000000000 string size (2) 89 | 00FF 6900 "i\0" 90 | local [1]: i 91 | 0101 05000000 startpc (5) 92 | 0105 0D000000 endpc (13) 93 | * upvalues: 94 | 0109 00000000 sizeupvalues (0) 95 | ** end of function ** 96 | 97 | 010D ** end of chunk ** 98 | -------------------------------------------------------------------------------- /vm/vm.lua: -------------------------------------------------------------------------------- 1 | -- FiOne https://github.com/Rerumu/FiOne/blob/master/source.lua 2 | 3 | local String = string 4 | local StringChar = String.char 5 | local StringByte = String.byte 6 | local Select = select 7 | local Table = table 8 | local Math = math 9 | local TableCreate = function(...) 10 | return {} 11 | end 12 | local TableUnpack = Table.unpack or unpack 13 | local TablePack = function(...) 14 | return { n = Select(StringChar(35), ...), ... } 15 | end 16 | local TableMove = function(src, first, last, offset, dst) 17 | for i = 0, last - first do 18 | dst[offset + i] = src[first + i] 19 | end 20 | end 21 | local TableConcat = Table.concat 22 | local Getfenv = getfenv 23 | local MathFloor = Math.floor 24 | local MathMax = Math.max 25 | local Pcall = pcall 26 | local MathAbs = Math.abs 27 | local Tonumber = tonumber 28 | local BitAnd, BitRShift, BitLShift = (function() 29 | local function tobittable_r(x, ...) 30 | if (x or 0) == 0 then 31 | return ... 32 | end 33 | return tobittable_r(MathFloor(x / 2), x % 2, ...) 34 | end 35 | 36 | local function tobittable(x) 37 | if x == 0 then 38 | return { 0 } 39 | end 40 | return { tobittable_r(x) } 41 | end 42 | 43 | local function makeop(cond) 44 | local function oper(x, y, ...) 45 | if not y then 46 | return x 47 | end 48 | x, y = tobittable(x), tobittable(y) 49 | local xl, yl = #x, #y 50 | local t, tl = {}, MathMax(xl, yl) 51 | for i = 0, tl - 1 do 52 | local b1, b2 = x[xl - i], y[yl - i] 53 | if not (b1 or b2) then 54 | break 55 | end 56 | t[tl - i] = (cond((b1 or 0) ~= 0, (b2 or 0) ~= 0) and 1 or 0) 57 | end 58 | return oper(Tonumber(TableConcat(t), 2), ...) 59 | end 60 | return oper 61 | end 62 | 63 | --- 64 | -- Perform bitwise AND of several numbers. 65 | -- Truth table: 66 | -- band(0,0) -> 0, 67 | -- band(0,1) -> 0, 68 | -- band(1,0) -> 0, 69 | -- band(1,1) -> 1. 70 | -- @class function 71 | -- @name band 72 | -- @param ... Numbers. 73 | -- @return A number. 74 | local band = makeop(function(a, b) 75 | return a and b 76 | end) 77 | 78 | --- 79 | -- Shift a number's bits to the left. 80 | -- Roughly equivalent to (x * (2^bits)). 81 | -- @param x The number to shift (number). 82 | -- @param bits Number of positions to shift by (number). 83 | -- @return A number. 84 | local function blshift(x, bits) 85 | return MathFloor(x) * (2 ^ bits) 86 | end 87 | 88 | --- 89 | -- Shift a number's bits to the right. 90 | -- Roughly equivalent to (x / (2^bits)). 91 | -- @param x The number to shift (number). 92 | -- @param bits Number of positions to shift by (number). 93 | -- @return A number. 94 | local function brshift(x, bits) 95 | return MathFloor(MathFloor(x) / (2 ^ bits)) 96 | end 97 | 98 | return band, brshift, blshift 99 | end)() 100 | 101 | local lua_bc_to_state 102 | local lua_wrap_state 103 | local stm_lua_func 104 | 105 | -- SETLIST config 106 | local FIELDS_PER_FLUSH = 50 107 | 108 | -- opcode types for getting values 109 | local OPCODE_T = { 110 | [0] = 'ABC', 111 | 'ABx', 112 | 'ABC', 113 | 'ABC', 114 | 'ABC', 115 | 'ABx', 116 | 'ABC', 117 | 'ABx', 118 | 'ABC', 119 | 'ABC', 120 | 'ABC', 121 | 'ABC', 122 | 'ABC', 123 | 'ABC', 124 | 'ABC', 125 | 'ABC', 126 | 'ABC', 127 | 'ABC', 128 | 'ABC', 129 | 'ABC', 130 | 'ABC', 131 | 'ABC', 132 | 'AsBx', 133 | 'ABC', 134 | 'ABC', 135 | 'ABC', 136 | 'ABC', 137 | 'ABC', 138 | 'ABC', 139 | 'ABC', 140 | 'ABC', 141 | 'AsBx', 142 | 'AsBx', 143 | 'ABC', 144 | 'ABC', 145 | 'ABC', 146 | 'ABx', 147 | 'ABC', 148 | } 149 | 150 | local OPCODE_M = { 151 | [0] = {b = 'OpArgR', c = 'OpArgN'}, 152 | {b = 'OpArgK', c = 'OpArgN'}, 153 | {b = 'OpArgU', c = 'OpArgU'}, 154 | {b = 'OpArgR', c = 'OpArgN'}, 155 | {b = 'OpArgU', c = 'OpArgN'}, 156 | {b = 'OpArgK', c = 'OpArgN'}, 157 | {b = 'OpArgR', c = 'OpArgK'}, 158 | {b = 'OpArgK', c = 'OpArgN'}, 159 | {b = 'OpArgU', c = 'OpArgN'}, 160 | {b = 'OpArgK', c = 'OpArgK'}, 161 | {b = 'OpArgU', c = 'OpArgU'}, 162 | {b = 'OpArgR', c = 'OpArgK'}, 163 | {b = 'OpArgK', c = 'OpArgK'}, 164 | {b = 'OpArgK', c = 'OpArgK'}, 165 | {b = 'OpArgK', c = 'OpArgK'}, 166 | {b = 'OpArgK', c = 'OpArgK'}, 167 | {b = 'OpArgK', c = 'OpArgK'}, 168 | {b = 'OpArgK', c = 'OpArgK'}, 169 | {b = 'OpArgR', c = 'OpArgN'}, 170 | {b = 'OpArgR', c = 'OpArgN'}, 171 | {b = 'OpArgR', c = 'OpArgN'}, 172 | {b = 'OpArgR', c = 'OpArgR'}, 173 | {b = 'OpArgR', c = 'OpArgN'}, 174 | {b = 'OpArgK', c = 'OpArgK'}, 175 | {b = 'OpArgK', c = 'OpArgK'}, 176 | {b = 'OpArgK', c = 'OpArgK'}, 177 | {b = 'OpArgR', c = 'OpArgU'}, 178 | {b = 'OpArgR', c = 'OpArgU'}, 179 | {b = 'OpArgU', c = 'OpArgU'}, 180 | {b = 'OpArgU', c = 'OpArgU'}, 181 | {b = 'OpArgU', c = 'OpArgN'}, 182 | {b = 'OpArgR', c = 'OpArgN'}, 183 | {b = 'OpArgR', c = 'OpArgN'}, 184 | {b = 'OpArgN', c = 'OpArgU'}, 185 | {b = 'OpArgU', c = 'OpArgU'}, 186 | {b = 'OpArgN', c = 'OpArgN'}, 187 | {b = 'OpArgU', c = 'OpArgN'}, 188 | {b = 'OpArgU', c = 'OpArgN'}, 189 | } 190 | 191 | -- int rd_int_basic(string src, int s, int e, int d) 192 | -- @src - Source binary string 193 | -- @s - Start index of a little endian integer 194 | -- @e - End index of the integer 195 | -- @d - Direction of the loop 196 | local function rd_int_basic(src, s, e, d) 197 | local num = 0 198 | 199 | -- if bb[l] > 127 then -- signed negative 200 | -- num = num - 256 ^ l 201 | -- bb[l] = bb[l] - 128 202 | -- end 203 | 204 | for i = s, e, d do 205 | local mul = 256 ^ MathAbs(i - s) 206 | 207 | num = num + mul * StringByte(src, i, i) 208 | end 209 | 210 | return num 211 | end 212 | 213 | -- float rd_flt_basic(byte f1..8) 214 | -- @f1..4 - The 4 bytes composing a little endian float 215 | local function rd_flt_basic(f1, f2, f3, f4) 216 | local sign = (-1) ^ BitRShift(f4, 7) 217 | local exp = BitRShift(f3, 7) + BitLShift(BitAnd(f4, 0x7F), 1) 218 | local frac = f1 + BitLShift(f2, 8) + BitLShift(BitAnd(f3, 0x7F), 16) 219 | local normal = 1 220 | 221 | if exp == 0 then 222 | if frac == 0 then 223 | return sign * 0 224 | else 225 | normal = 0 226 | exp = 1 227 | end 228 | elseif exp == 0x7F then 229 | if frac == 0 then 230 | return sign * (1 / 0) 231 | else 232 | return sign * (0 / 0) 233 | end 234 | end 235 | 236 | return sign * 2 ^ (exp - 127) * (1 + normal / 2 ^ 23) 237 | end 238 | 239 | -- double rd_dbl_basic(byte f1..8) 240 | -- @f1..8 - The 8 bytes composing a little endian double 241 | local function rd_dbl_basic(f1, f2, f3, f4, f5, f6, f7, f8) 242 | local sign = (-1) ^ BitRShift(f8, 7) 243 | local exp = BitLShift(BitAnd(f8, 0x7F), 4) + BitRShift(f7, 4) 244 | local frac = BitAnd(f7, 0x0F) * 2 ^ 48 245 | local normal = 1 246 | 247 | frac = frac + (f6 * 2 ^ 40) + (f5 * 2 ^ 32) + (f4 * 2 ^ 24) + (f3 * 2 ^ 16) + (f2 * 2 ^ 8) + f1 -- help 248 | 249 | if exp == 0 then 250 | if frac == 0 then 251 | return sign * 0 252 | else 253 | normal = 0 254 | exp = 1 255 | end 256 | elseif exp == 0x7FF then 257 | if frac == 0 then 258 | return sign * (1 / 0) 259 | else 260 | return sign * (0 / 0) 261 | end 262 | end 263 | 264 | return sign * 2 ^ (exp - 1023) * (normal + frac / 2 ^ 52) 265 | end 266 | 267 | -- int rd_int_le(string src, int s, int e) 268 | -- @src - Source binary string 269 | -- @s - Start index of a little endian integer 270 | -- @e - End index of the integer 271 | local function rd_int_le(src, s, e) return rd_int_basic(src, s, e - 1, 1) end 272 | 273 | -- int rd_int_be(string src, int s, int e) 274 | -- @src - Source binary string 275 | -- @s - Start index of a big endian integer 276 | -- @e - End index of the integer 277 | local function rd_int_be(src, s, e) return rd_int_basic(src, e - 1, s, -1) end 278 | 279 | -- float rd_flt_le(string src, int s) 280 | -- @src - Source binary string 281 | -- @s - Start index of little endian float 282 | local function rd_flt_le(src, s) return rd_flt_basic(string.byte(src, s, s + 3)) end 283 | 284 | -- float rd_flt_be(string src, int s) 285 | -- @src - Source binary string 286 | -- @s - Start index of big endian float 287 | local function rd_flt_be(src, s) 288 | local f1, f2, f3, f4 = string.byte(src, s, s + 3) 289 | return rd_flt_basic(f4, f3, f2, f1) 290 | end 291 | 292 | -- double rd_dbl_le(string src, int s) 293 | -- @src - Source binary string 294 | -- @s - Start index of little endian double 295 | local function rd_dbl_le(src, s) return rd_dbl_basic(string.byte(src, s, s + 7)) end 296 | 297 | -- double rd_dbl_be(string src, int s) 298 | -- @src - Source binary string 299 | -- @s - Start index of big endian double 300 | local function rd_dbl_be(src, s) 301 | local f1, f2, f3, f4, f5, f6, f7, f8 = string.byte(src, s, s + 7) -- same 302 | return rd_dbl_basic(f8, f7, f6, f5, f4, f3, f2, f1) 303 | end 304 | 305 | -- to avoid nested ifs in deserializing 306 | local float_types = { 307 | [4] = {little = rd_flt_le, big = rd_flt_be}, 308 | [8] = {little = rd_dbl_le, big = rd_dbl_be}, 309 | } 310 | 311 | -- byte stm_byte(Stream S) 312 | -- @S - Stream object to read from 313 | local function stm_byte(S) 314 | local idx = S.index 315 | local bt = string.byte(S.source, idx, idx) 316 | 317 | S.index = idx + 1 318 | return bt 319 | end 320 | 321 | -- string stm_string(Stream S, int len) 322 | -- @S - Stream object to read from 323 | -- @len - Length of string being read 324 | local function stm_string(S, len) 325 | local pos = S.index + len 326 | local str = string.sub(S.source, S.index, pos - 1) 327 | 328 | S.index = pos 329 | return str 330 | end 331 | 332 | -- string stm_lstring(Stream S) 333 | -- @S - Stream object to read from 334 | local function stm_lstring(S) 335 | local len = S:s_szt() 336 | local str 337 | 338 | if len ~= 0 then str = string.sub(stm_string(S, len), 1, -2) end 339 | 340 | return str 341 | end 342 | 343 | -- fn cst_int_rdr(string src, int len, fn func) 344 | -- @len - Length of type for reader 345 | -- @func - Reader callback 346 | local function cst_int_rdr(len, func) 347 | return function(S) 348 | local pos = S.index + len 349 | local int = func(S.source, S.index, pos) 350 | S.index = pos 351 | 352 | return int 353 | end 354 | end 355 | 356 | -- fn cst_flt_rdr(string src, int len, fn func) 357 | -- @len - Length of type for reader 358 | -- @func - Reader callback 359 | local function cst_flt_rdr(len, func) 360 | return function(S) 361 | local flt = func(S.source, S.index) 362 | S.index = S.index + len 363 | 364 | return flt 365 | end 366 | end 367 | 368 | local function stm_inst_list(S) 369 | local len = S:s_int() 370 | local list = TableCreate(len) 371 | 372 | for i = 1, len do 373 | local ins = S:s_ins() 374 | local op = BitAnd(ins, 0x3F) 375 | local args = OPCODE_T[op] 376 | local mode = OPCODE_M[op] 377 | local data = {value = ins, op = op, A = BitAnd(BitRShift(ins, 6), 0xFF)} 378 | 379 | if args == 'ABC' then 380 | data.B = BitAnd(BitRShift(ins, 23), 0x1FF) 381 | data.C = BitAnd(BitRShift(ins, 14), 0x1FF) 382 | data.is_KB = mode.b == 'OpArgK' and data.B > 0xFF -- post process optimization 383 | data.is_KC = mode.c == 'OpArgK' and data.C > 0xFF 384 | elseif args == 'ABx' then 385 | data.Bx = BitAnd(BitRShift(ins, 14), 0x3FFFF) 386 | data.is_K = mode.b == 'OpArgK' 387 | elseif args == 'AsBx' then 388 | data.sBx = BitAnd(BitRShift(ins, 14), 0x3FFFF) - 131071 389 | end 390 | 391 | list[i] = data 392 | end 393 | 394 | return list 395 | end 396 | 397 | local function stm_const_list(S) 398 | local len = S:s_int() 399 | local list = TableCreate(len) 400 | 401 | for i = 1, len do 402 | local tt = stm_byte(S) 403 | local k 404 | 405 | if tt == 1 then 406 | k = stm_byte(S) ~= 0 407 | elseif tt == 3 then 408 | k = S:s_num() 409 | elseif tt == 4 then 410 | k = stm_lstring(S) 411 | end 412 | 413 | list[i] = k -- offset +1 during instruction decode 414 | end 415 | 416 | return list 417 | end 418 | 419 | local function stm_sub_list(S, src) 420 | local len = S:s_int() 421 | local list = TableCreate(len) 422 | 423 | for i = 1, len do 424 | list[i] = stm_lua_func(S, src) -- offset +1 in CLOSURE 425 | end 426 | 427 | return list 428 | end 429 | 430 | local function stm_line_list(S) 431 | local len = S:s_int() 432 | local list = TableCreate(len) 433 | 434 | for i = 1, len do list[i] = S:s_int() end 435 | 436 | return list 437 | end 438 | 439 | local function stm_loc_list(S) 440 | local len = S:s_int() 441 | local list = TableCreate(len) 442 | 443 | for i = 1, len do list[i] = {varname = stm_lstring(S), startpc = S:s_int(), endpc = S:s_int()} end 444 | 445 | return list 446 | end 447 | 448 | local function stm_upval_list(S) 449 | local len = S:s_int() 450 | local list = TableCreate(len) 451 | 452 | for i = 1, len do list[i] = stm_lstring(S) end 453 | 454 | return list 455 | end 456 | 457 | function stm_lua_func(S, psrc) 458 | local proto = {} 459 | local src = stm_lstring(S) or psrc -- source is propagated 460 | 461 | proto.source = src -- source name 462 | 463 | S:s_int() -- line defined 464 | S:s_int() -- last line defined 465 | 466 | proto.num_upval = stm_byte(S) -- num upvalues 467 | proto.num_param = stm_byte(S) -- num params 468 | 469 | stm_byte(S) -- vararg flag 470 | proto.max_stack = stm_byte(S) -- max stack size 471 | 472 | proto.code = stm_inst_list(S) 473 | proto.const = stm_const_list(S) 474 | proto.subs = stm_sub_list(S, src) 475 | proto.lines = stm_line_list(S) 476 | 477 | stm_loc_list(S) 478 | stm_upval_list(S) 479 | 480 | -- post process optimization 481 | for _, v in ipairs(proto.code) do 482 | if v.is_K then 483 | v.const = proto.const[v.Bx + 1] -- offset for 1 based index 484 | else 485 | if v.is_KB then v.const_B = proto.const[v.B - 0xFF] end 486 | 487 | if v.is_KC then v.const_C = proto.const[v.C - 0xFF] end 488 | end 489 | end 490 | 491 | return proto 492 | end 493 | 494 | function lua_bc_to_state(src) 495 | -- func reader 496 | local rdr_func 497 | 498 | -- header flags 499 | local little 500 | local size_int 501 | local size_szt 502 | local size_ins 503 | local size_num 504 | local flag_int 505 | 506 | -- stream object 507 | local stream = { 508 | -- data 509 | index = 1, 510 | source = src, 511 | } 512 | 513 | assert(stm_string(stream, 4) == '\27Lua', 'invalid Lua signature') 514 | assert(stm_byte(stream) == 0x51, 'invalid Lua version') 515 | assert(stm_byte(stream) == 0, 'invalid Lua format') 516 | 517 | little = stm_byte(stream) ~= 0 518 | size_int = stm_byte(stream) 519 | size_szt = stm_byte(stream) 520 | size_ins = stm_byte(stream) 521 | size_num = stm_byte(stream) 522 | flag_int = stm_byte(stream) ~= 0 523 | 524 | rdr_func = little and rd_int_le or rd_int_be 525 | stream.s_int = cst_int_rdr(size_int, rdr_func) 526 | stream.s_szt = cst_int_rdr(size_szt, rdr_func) 527 | stream.s_ins = cst_int_rdr(size_ins, rdr_func) 528 | 529 | if flag_int then 530 | stream.s_num = cst_int_rdr(size_num, rdr_func) 531 | elseif float_types[size_num] then 532 | stream.s_num = cst_flt_rdr(size_num, float_types[size_num][little and 'little' or 'big']) 533 | else 534 | error('unsupported float size') 535 | end 536 | 537 | return stm_lua_func(stream, '@virtual') 538 | end 539 | 540 | local function close_lua_upvalues(list, index) 541 | for i, uv in pairs(list) do 542 | if uv.index >= index then 543 | uv.value = uv.store[uv.index] -- store value 544 | uv.store = uv 545 | uv.index = 'value' -- self reference 546 | list[i] = nil 547 | end 548 | end 549 | end 550 | 551 | local function open_lua_upvalue(list, index, memory) 552 | local prev = list[index] 553 | 554 | if not prev then 555 | prev = {index = index, store = memory} 556 | list[index] = prev 557 | end 558 | 559 | return prev 560 | end 561 | 562 | local function on_lua_error(failed, err) 563 | local src = failed.source 564 | local line = failed.lines[failed.pc - 1] 565 | 566 | error(string.format('%s:%i: %s', src, line, err), 0) 567 | end 568 | 569 | local function run_lua_func(state, env, upvals) 570 | local code = state.code 571 | local subs = state.subs 572 | local vararg = state.vararg 573 | 574 | local top_index = -1 575 | local open_list = {} 576 | local memory = state.memory 577 | local pc = state.pc 578 | 579 | while true do 580 | local inst = code[pc] 581 | local op = inst.op 582 | pc = pc + 1 583 | 584 | if op == 0 then 585 | --[[MOVE]] 586 | memory[inst.A] = memory[inst.B] 587 | elseif op == 1 then 588 | --[[LOADK]] 589 | memory[inst.A] = inst.const 590 | elseif op == 2 then 591 | --[[LOADBOOL]] 592 | memory[inst.A] = inst.B ~= 0 593 | 594 | if inst.C ~= 0 then pc = pc + 1 end 595 | elseif op == 3 then 596 | --[[LOADNIL]] 597 | for i = inst.A, inst.B do memory[i] = nil end 598 | elseif op == 4 then 599 | --[[GETUPVAL]] 600 | local uv = upvals[inst.B] 601 | 602 | memory[inst.A] = uv.store[uv.index] 603 | elseif op == 5 then 604 | --[[GETGLOBAL]] 605 | memory[inst.A] = env[inst.const] 606 | elseif op == 6 then 607 | --[[GETTABLE]] 608 | local index 609 | 610 | if inst.is_KC then 611 | index = inst.const_C 612 | else 613 | index = memory[inst.C] 614 | end 615 | 616 | memory[inst.A] = memory[inst.B][index] 617 | elseif op == 7 then 618 | --[[SETGLOBAL]] 619 | env[inst.const] = memory[inst.A] 620 | elseif op == 8 then 621 | --[[SETUPVAL]] 622 | local uv = upvals[inst.B] 623 | 624 | uv.store[uv.index] = memory[inst.A] 625 | elseif op == 9 then 626 | --[[SETTABLE]] 627 | local index, value 628 | 629 | if inst.is_KB then 630 | index = inst.const_B 631 | else 632 | index = memory[inst.B] 633 | end 634 | 635 | if inst.is_KC then 636 | value = inst.const_C 637 | else 638 | value = memory[inst.C] 639 | end 640 | 641 | memory[inst.A][index] = value 642 | elseif op == 10 then 643 | --[[NEWTABLE]] 644 | memory[inst.A] = {} 645 | elseif op == 11 then 646 | --[[SELF]] 647 | local A = inst.A 648 | local B = inst.B 649 | local index 650 | 651 | if inst.is_KC then 652 | index = inst.const_C 653 | else 654 | index = memory[inst.C] 655 | end 656 | 657 | memory[A + 1] = memory[B] 658 | memory[A] = memory[B][index] 659 | elseif op == 12 then 660 | --[[ADD]] 661 | local lhs, rhs 662 | 663 | if inst.is_KB then 664 | lhs = inst.const_B 665 | else 666 | lhs = memory[inst.B] 667 | end 668 | 669 | if inst.is_KC then 670 | rhs = inst.const_C 671 | else 672 | rhs = memory[inst.C] 673 | end 674 | 675 | memory[inst.A] = lhs + rhs 676 | elseif op == 13 then 677 | --[[SUB]] 678 | local lhs, rhs 679 | 680 | if inst.is_KB then 681 | lhs = inst.const_B 682 | else 683 | lhs = memory[inst.B] 684 | end 685 | 686 | if inst.is_KC then 687 | rhs = inst.const_C 688 | else 689 | rhs = memory[inst.C] 690 | end 691 | 692 | memory[inst.A] = lhs - rhs 693 | elseif op == 14 then 694 | --[[MUL]] 695 | local lhs, rhs 696 | 697 | if inst.is_KB then 698 | lhs = inst.const_B 699 | else 700 | lhs = memory[inst.B] 701 | end 702 | 703 | if inst.is_KC then 704 | rhs = inst.const_C 705 | else 706 | rhs = memory[inst.C] 707 | end 708 | 709 | memory[inst.A] = lhs * rhs 710 | elseif op == 15 then 711 | --[[DIV]] 712 | local lhs, rhs 713 | 714 | if inst.is_KB then 715 | lhs = inst.const_B 716 | else 717 | lhs = memory[inst.B] 718 | end 719 | 720 | if inst.is_KC then 721 | rhs = inst.const_C 722 | else 723 | rhs = memory[inst.C] 724 | end 725 | 726 | memory[inst.A] = lhs / rhs 727 | elseif op == 16 then 728 | --[[MOD]] 729 | local lhs, rhs 730 | 731 | if inst.is_KB then 732 | lhs = inst.const_B 733 | else 734 | lhs = memory[inst.B] 735 | end 736 | 737 | if inst.is_KC then 738 | rhs = inst.const_C 739 | else 740 | rhs = memory[inst.C] 741 | end 742 | 743 | memory[inst.A] = lhs % rhs 744 | elseif op == 17 then 745 | --[[POW]] 746 | local lhs, rhs 747 | 748 | if inst.is_KB then 749 | lhs = inst.const_B 750 | else 751 | lhs = memory[inst.B] 752 | end 753 | 754 | if inst.is_KC then 755 | rhs = inst.const_C 756 | else 757 | rhs = memory[inst.C] 758 | end 759 | 760 | memory[inst.A] = lhs ^ rhs 761 | elseif op == 18 then 762 | --[[UNM]] 763 | memory[inst.A] = -memory[inst.B] 764 | elseif op == 19 then 765 | --[[NOT]] 766 | memory[inst.A] = not memory[inst.B] 767 | elseif op == 20 then 768 | --[[LEN]] 769 | memory[inst.A] = #memory[inst.B] 770 | elseif op == 21 then 771 | --[[CONCAT]] 772 | local B = inst.B 773 | local str = memory[B] 774 | 775 | for i = B + 1, inst.C do str = str .. memory[i] end 776 | 777 | memory[inst.A] = str 778 | elseif op == 22 then 779 | --[[JMP]] 780 | pc = pc + inst.sBx 781 | elseif op == 23 then 782 | --[[EQ]] 783 | local lhs, rhs 784 | 785 | if inst.is_KB then 786 | lhs = inst.const_B 787 | else 788 | lhs = memory[inst.B] 789 | end 790 | 791 | if inst.is_KC then 792 | rhs = inst.const_C 793 | else 794 | rhs = memory[inst.C] 795 | end 796 | 797 | if (lhs == rhs) == (inst.A ~= 0) then pc = pc + code[pc].sBx end 798 | 799 | pc = pc + 1 800 | elseif op == 24 then 801 | --[[LT]] 802 | local lhs, rhs 803 | 804 | if inst.is_KB then 805 | lhs = inst.const_B 806 | else 807 | lhs = memory[inst.B] 808 | end 809 | 810 | if inst.is_KC then 811 | rhs = inst.const_C 812 | else 813 | rhs = memory[inst.C] 814 | end 815 | 816 | if (lhs < rhs) == (inst.A ~= 0) then pc = pc + code[pc].sBx end 817 | 818 | pc = pc + 1 819 | elseif op == 25 then 820 | --[[LE]] 821 | local lhs, rhs 822 | 823 | if inst.is_KB then 824 | lhs = inst.const_B 825 | else 826 | lhs = memory[inst.B] 827 | end 828 | 829 | if inst.is_KC then 830 | rhs = inst.const_C 831 | else 832 | rhs = memory[inst.C] 833 | end 834 | 835 | if (lhs <= rhs) == (inst.A ~= 0) then pc = pc + code[pc].sBx end 836 | 837 | pc = pc + 1 838 | elseif op == 26 then 839 | --[[TEST]] 840 | if (not memory[inst.A]) ~= (inst.C ~= 0) then pc = pc + code[pc].sBx end 841 | pc = pc + 1 842 | elseif op == 27 then 843 | --[[TESTSET]] 844 | local A = inst.A 845 | local B = inst.B 846 | 847 | if (not memory[B]) ~= (inst.C ~= 0) then 848 | memory[A] = memory[B] 849 | pc = pc + code[pc].sBx 850 | end 851 | pc = pc + 1 852 | elseif op == 28 then 853 | --[[CALL]] 854 | local A = inst.A 855 | local B = inst.B 856 | local C = inst.C 857 | local params 858 | 859 | if B == 0 then 860 | params = top_index - A 861 | else 862 | params = B - 1 863 | end 864 | 865 | local ret_list = TablePack(memory[A](TableUnpack(memory, A + 1, A + params))) 866 | local ret_num = ret_list.n 867 | 868 | if C == 0 then 869 | top_index = A + ret_num - 1 870 | else 871 | ret_num = C - 1 872 | end 873 | 874 | TableMove(ret_list, 1, ret_num, A, memory) 875 | elseif op == 29 then 876 | --[[TAILCALL]] 877 | local A = inst.A 878 | local B = inst.B 879 | local params 880 | 881 | if B == 0 then 882 | params = top_index - A 883 | else 884 | params = B - 1 885 | end 886 | 887 | close_lua_upvalues(open_list, 0) 888 | 889 | return memory[A](TableUnpack(memory, A + 1, A + params)) 890 | elseif op == 30 then 891 | --[[RETURN]] 892 | local A = inst.A 893 | local B = inst.B 894 | local len 895 | 896 | if B == 0 then 897 | len = top_index - A + 1 898 | else 899 | len = B - 1 900 | end 901 | 902 | close_lua_upvalues(open_list, 0) 903 | 904 | return TableUnpack(memory, A, A + len - 1) 905 | elseif op == 31 then 906 | --[[FORLOOP]] 907 | local A = inst.A 908 | local step = memory[A + 2] 909 | local index = memory[A] + step 910 | local limit = memory[A + 1] 911 | local loops 912 | 913 | if step == MathAbs(step) then 914 | loops = index <= limit 915 | else 916 | loops = index >= limit 917 | end 918 | 919 | if loops then 920 | memory[A] = index 921 | memory[A + 3] = index 922 | pc = pc + inst.sBx 923 | end 924 | elseif op == 32 then 925 | --[[FORPREP]] 926 | local A = inst.A 927 | -- local init, limit, step 928 | 929 | -- *: Possible additional error checking 930 | -- init = assert(tonumber(memory[A]), '`for` initial value must be a number') 931 | -- limit = assert(tonumber(memory[A + 1]), '`for` limit must be a number') 932 | -- step = assert(tonumber(memory[A + 2]), '`for` step must be a number') 933 | 934 | local init = Tonumber(memory[A]) 935 | local limit = Tonumber(memory[A + 1]) 936 | local step = Tonumber(memory[A + 2]) 937 | 938 | memory[A] = init - step 939 | memory[A + 1] = limit 940 | memory[A + 2] = step 941 | 942 | pc = pc + inst.sBx 943 | elseif op == 33 then 944 | --[[TFORLOOP]] 945 | local A = inst.A 946 | local base = A + 3 947 | 948 | local vals = {memory[A](memory[A + 1], memory[A + 2])} 949 | 950 | TableMove(vals, 1, inst.C, base, memory) 951 | 952 | if memory[base] ~= nil then 953 | memory[A + 2] = memory[base] 954 | pc = pc + code[pc].sBx 955 | end 956 | 957 | pc = pc + 1 958 | elseif op == 34 then 959 | --[[SETLIST]] 960 | local A = inst.A 961 | local C = inst.C 962 | local len = inst.B 963 | local tab = memory[A] 964 | local offset 965 | 966 | if len == 0 then len = top_index - A end 967 | 968 | if C == 0 then 969 | C = inst[pc].value 970 | pc = pc + 1 971 | end 972 | 973 | offset = (C - 1) * FIELDS_PER_FLUSH 974 | 975 | TableMove(memory, A + 1, A + len, offset + 1, tab) 976 | elseif op == 35 then 977 | --[[CLOSE]] 978 | close_lua_upvalues(open_list, inst.A) 979 | elseif op == 36 then 980 | --[[CLOSURE]] 981 | local sub = subs[inst.Bx + 1] -- offset for 1 based index 982 | local nups = sub.num_upval 983 | local uvlist 984 | 985 | if nups ~= 0 then 986 | uvlist = {} 987 | 988 | for i = 1, nups do 989 | local pseudo = code[pc + i - 1] 990 | 991 | if pseudo.op == 0 then -- @MOVE 992 | uvlist[i - 1] = open_lua_upvalue(open_list, pseudo.B, memory) 993 | elseif pseudo.op == 4 then -- @GETUPVAL 994 | uvlist[i - 1] = upvals[pseudo.B] 995 | end 996 | end 997 | 998 | pc = pc + nups 999 | end 1000 | 1001 | memory[inst.A] = lua_wrap_state(sub, env, uvlist) 1002 | elseif op == 37 then 1003 | --[[VARARG]] 1004 | local A = inst.A 1005 | local len = inst.B 1006 | 1007 | if len == 0 then 1008 | len = vararg.len 1009 | top_index = A + len - 1 1010 | end 1011 | 1012 | TableMove(vararg.list, 1, len, A, memory) 1013 | end 1014 | 1015 | state.pc = pc 1016 | end 1017 | end 1018 | 1019 | function lua_wrap_state(proto, env, upval) 1020 | env = env or Getfenv(0) 1021 | 1022 | local function wrapped(...) 1023 | local passed = TablePack(...) 1024 | local memory = TableCreate(proto.max_stack) 1025 | local vararg = {len = 0, list = {}} 1026 | 1027 | TableMove(passed, 1, proto.num_param, 0, memory) 1028 | 1029 | if proto.num_param < passed.n then 1030 | local start = proto.num_param + 1 1031 | local len = passed.n - proto.num_param 1032 | 1033 | vararg.len = len 1034 | TableMove(passed, start, start + len - 1, 1, vararg.list) 1035 | end 1036 | 1037 | local state = {vararg = vararg, memory = memory, code = proto.code, subs = proto.subs, pc = 1} 1038 | 1039 | local result = TablePack(Pcall(run_lua_func, state, env, upval)) 1040 | 1041 | if result[1] then 1042 | return TableUnpack(result, 2, result.n) 1043 | else 1044 | local failed = {pc = state.pc, source = proto.source, lines = proto.lines} 1045 | 1046 | on_lua_error(failed, result[2]) 1047 | 1048 | return 1049 | end 1050 | end 1051 | 1052 | return wrapped 1053 | end 1054 | 1055 | local function read_file(path) 1056 | local file = io.open(path, "rb") -- r read mode and b binary mode 1057 | if not file then 1058 | return nil 1059 | end 1060 | local content = file:read("*a") -- *a or *all reads the whole file 1061 | file:close() 1062 | return content 1063 | end 1064 | 1065 | lua_wrap_state(lua_bc_to_state(read_file("luac.out")))() 1066 | 1067 | -- return {bc_to_state = lua_bc_to_state, wrap_state = lua_wrap_state} 1068 | --------------------------------------------------------------------------------