scriptcore: reconstructed from preserved seed files
Reconstructed Lua scripting infrastructure consumed by every per-game
flash script. Two source files preserved at D:\B\Srift\games\Flash\:
- functional.lua (109 lines): table.from/max/min/find/filter/map
- internal.lua (172 lines): Markers, Rule.All/Any sugar, Hex/Addr,
print override, gadget enumerators
Surface inventory at D:\B\Srift\games\Flash\SURFACE.md confirms
these two files cover scriptcore's lua-level responsibilities for
CODMW, CODMW2, IW9, DayZ, Template. RB6 may need a Rule.Refs stub.
Server contract (Builder.ts:286-380): pushing this repo to Gitea as
srift/core triggers the standard webhook pipeline and produces
S.Build doc { kind:'script', product:'scriptcore' }.
This commit is contained in:
commit
c1bfbaf36a
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Editor / OS
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Node noise (Builder.BuildVirtual ignores these anyway — kept for hygiene)
|
||||
node_modules/
|
||||
package-lock.json
|
||||
31
README.md
Normal file
31
README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# srift/core (scriptcore)
|
||||
|
||||
Universal Lua scripting infrastructure consumed by every per-game flash script.
|
||||
|
||||
**Server contract:** this repo, when pushed to Gitea under owner `srift` and name `core`, triggers the standard Gitea webhook pipeline:
|
||||
|
||||
```
|
||||
git push → Gitea → POST /ggit → overwatch app.ts:31
|
||||
→ RepoUpdate.Schedule('srift/core')
|
||||
→ Gitea.UpdateRepos('srift', 'core', branch, commit)
|
||||
→ Builder.Build('srift', 'core', localdir)
|
||||
→ Builder.Upload(result, details, branch, commit)
|
||||
→ S.Build doc { kind:'script', product:'scriptcore', pid:<branch>@<commit> }
|
||||
```
|
||||
|
||||
After upload, `GameSession.loadProfile` at `D:\B\Srift\site\www\api\native\client\GameSession.ts:272` finds the Build (`Store.PickBuild('script', user, 'scriptcore')`) and ships its files into the per-session VFS at prefix `core/` (per `Builder.BuildVirtual` line 110, prefix = repo name).
|
||||
|
||||
## Contents
|
||||
|
||||
| File | Role | Lines |
|
||||
|---|---|---|
|
||||
| `functional.lua` | Lua stdlib extensions: `table.from/max/min/find/filter/map` | 109 |
|
||||
| `internal.lua` | `_G.Markers`, `Rule.All`/`Rule.Any` sugar, `Hex`/`Addr`/`print` override, three `Register(...)` calls for `_fast_return_gadgets` / `_fix_arg_gadgets` / `_stack_spoof_gadgets` | 172 |
|
||||
|
||||
## Surface inventory
|
||||
|
||||
See `D:\B\Srift\games\Flash\SURFACE.md` for the full enumeration of what scriptcore must export vs. what chost provides natively.
|
||||
|
||||
## Reconstruction provenance
|
||||
|
||||
Clean-room reconstruction 2026-06-29 from preserved seed files at `D:\B\Srift\games\Flash\`. Original `srift/core` Gitea repo not accessible; surface was reverse-engineered from per-game flash script call sites (`games\Games\*\flash\*.lua`).
|
||||
110
functional.lua
Normal file
110
functional.lua
Normal file
@ -0,0 +1,110 @@
|
||||
table.from = function(f)
|
||||
local tbl = {}
|
||||
local i = 1
|
||||
for v in f do
|
||||
tbl[i] = v
|
||||
i = i + 1
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
table.max = function(t)
|
||||
local maxk, maxv
|
||||
if type(t) == "table" then
|
||||
for k,v in pairs(t) do
|
||||
if not maxk or maxv < v then
|
||||
maxk = k
|
||||
maxv = v
|
||||
end
|
||||
end
|
||||
else
|
||||
for k,v in t do
|
||||
if not maxk or maxv < v then
|
||||
maxk = k
|
||||
maxv = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return maxk, maxv
|
||||
end
|
||||
|
||||
table.min = function(t)
|
||||
local mink, minv
|
||||
if type(t) == "table" then
|
||||
for k,v in pairs(t) do
|
||||
if not mink or minv > v then
|
||||
mink = k
|
||||
minv = v
|
||||
end
|
||||
end
|
||||
else
|
||||
for k,v in t do
|
||||
if not mink or minv > v then
|
||||
mink = k
|
||||
minv = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return mink, minv
|
||||
end
|
||||
|
||||
table.find = function(t, f)
|
||||
if type(t) == "table" then
|
||||
for k,v in pairs(t) do
|
||||
if f(k,v) then
|
||||
return k,v
|
||||
end
|
||||
end
|
||||
else
|
||||
for k,v in t do
|
||||
if f(k,v) then
|
||||
return k,v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.filter = function(t, f)
|
||||
if type(t) == "table" then
|
||||
local it = nil
|
||||
return function()
|
||||
while true do
|
||||
it = next(t, it)
|
||||
if not it then
|
||||
break
|
||||
elseif f(it,t[it]) then
|
||||
return it,t[it]
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return function()
|
||||
while true do
|
||||
local k,v = t()
|
||||
if not k then
|
||||
break
|
||||
elseif f(k,v) then
|
||||
return k,v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.map = function(t, f)
|
||||
if type(t) == "table" then
|
||||
local it = nil
|
||||
return function()
|
||||
it = next(t, it)
|
||||
if it then
|
||||
return f(it, t[it])
|
||||
end
|
||||
end
|
||||
else
|
||||
return function()
|
||||
local k,v = t()
|
||||
if k then
|
||||
return f(k,v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
173
internal.lua
Normal file
173
internal.lua
Normal file
@ -0,0 +1,173 @@
|
||||
|
||||
-- Implement the marker table.
|
||||
--
|
||||
local markerObjects = {}
|
||||
_G.Markers = { Clear = function() markerObjects = {} end }
|
||||
setmetatable(_G.Markers, {
|
||||
__index = function(_, name)
|
||||
local marker = markerObjects[name]
|
||||
return marker and Rule.ReadMarker(marker)
|
||||
end
|
||||
})
|
||||
|
||||
-- Parses a table of rules and returns a table of valid Rule objects.
|
||||
--
|
||||
local function parseRuleList(arg)
|
||||
local result = {}
|
||||
local it = 1
|
||||
while true do
|
||||
local v = arg[it]
|
||||
if v == nil then break end
|
||||
local t = type(v)
|
||||
|
||||
if t == "string" then
|
||||
if v:sub(1,1) == "#" then
|
||||
v = v:sub(2)
|
||||
local m = markerObjects[v]
|
||||
if not m then
|
||||
m = Rule.Marker()
|
||||
markerObjects[v] = m
|
||||
end
|
||||
result[#result+1] = m
|
||||
elseif type(arg[it+1]) == "number" then
|
||||
result[#result+1] = Rule.Instruction(v, arg[it+1])
|
||||
it = it + 1
|
||||
else
|
||||
result[#result+1] = Rule.Pattern(v)
|
||||
end
|
||||
elseif t == "table" then
|
||||
result[#result+1] = Rule.Pad(v[1], v[2])
|
||||
elseif t == "function" then
|
||||
result[#result+1] = Rule.Filter(v)
|
||||
elseif t == "userdata" then
|
||||
result[#result+1] = v
|
||||
else
|
||||
error("Did not expect: " .. t)
|
||||
end
|
||||
it = it + 1
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Implement Rule.All and Rule.Any.
|
||||
--
|
||||
function Rule.All(arg)
|
||||
return Rule.AllImpl(parseRuleList(arg))
|
||||
end
|
||||
function Rule.Any(arg)
|
||||
return Rule.AnyImpl(parseRuleList(arg))
|
||||
end
|
||||
|
||||
-- Hex helper, override print.
|
||||
--
|
||||
function Hex(value) return value and string.format("%x", value) or "nil" end
|
||||
function Addr(value) return value and string.format("%x", value + Image.base) or "nil" end
|
||||
|
||||
function print(...)
|
||||
local args = table.pack(...)
|
||||
local msg = ""
|
||||
for i=1, args.n do
|
||||
if #msg ~= 0 then
|
||||
msg = msg .. " " .. tostring(args[i])
|
||||
else
|
||||
msg = tostring(args[i])
|
||||
end
|
||||
end
|
||||
Log(msg)
|
||||
end
|
||||
|
||||
local function findAny(patternList)
|
||||
local output = {0}
|
||||
local n = 0
|
||||
for k,v in ipairs(patternList) do
|
||||
for rva in Image:Find(Rule.Pattern(v[1])) do
|
||||
local res = rva + ((#v[1]+1)/3) + v[2]
|
||||
output[n+2] = res
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
output[1] = n
|
||||
return output
|
||||
end
|
||||
|
||||
-- Implement searchers for user call gadgets.
|
||||
--
|
||||
Register("_fast_return_gadgets", R_ARR, function()
|
||||
return findAny{
|
||||
{ "E8 ?? 0F 05 00", -3 }, -- call rel32
|
||||
{ "0F 8? 0F 05 00", -3 }, -- jcc rel32
|
||||
{ "FF 15 ?? 0F 05", -2 }, -- call [rip+rel32]
|
||||
{ "48 8D ?? ?? 0F 05", -2 }, -- lea [rip+rel32]
|
||||
{ "48 89 ?? ?? 0F 05", -2 }, -- mov [rip+rel32]
|
||||
{ "4C 8D ?? ?? 0F 05", -2 }, -- lea [rip+rel32]
|
||||
{ "4C 89 ?? ?? 0F 05", -2 }, -- mov [rip+rel32]
|
||||
}
|
||||
end)
|
||||
|
||||
Register("_fix_arg_gadgets", R_ARR, function()
|
||||
return findAny{
|
||||
{ "F3 41 0F 59 C3", -2 }, -- mulss, mulps, vmulss, vmulps
|
||||
{ "F3 43 0F 59 C3", -2 },
|
||||
{ "41 0F 59 C3", -2 },
|
||||
{ "F3 0F 59 C3", -2 },
|
||||
{ "C4 C1 ?? 59 C3", -2 }
|
||||
}
|
||||
end)
|
||||
|
||||
Register("_stack_spoof_gadgets", R_ARR, function()
|
||||
-- Matches a call m64 instruction
|
||||
-- reg = /2
|
||||
local callPattern = { "FF 10", "FF 38" }
|
||||
|
||||
-- Matches an epilogue
|
||||
--
|
||||
local epiPatterns = {
|
||||
{ "48 83 C4 08 C3", "FF FF FF 8F FF" }, -- [add rsp, imm8], no negative/misaligned immediates
|
||||
{ "48 81 C4 08 00 00 00 C3", "FF FF FF 0F FF FF FF FF" } -- [add rsp, imm32], no negative/misaligned/huge immediates
|
||||
}
|
||||
|
||||
-- For each epilogue pattern:
|
||||
--
|
||||
local results = {}
|
||||
for _,v in ipairs(epiPatterns) do
|
||||
-- For each call instruction suffix length:
|
||||
--
|
||||
for suf=5,0,-1 do
|
||||
-- Find the gadget:
|
||||
--
|
||||
local value = callPattern[1] .. string.rep(" 00", suf) .. " " .. v[1]
|
||||
local mask = callPattern[2] .. string.rep(" 00", suf) .. " " .. v[2]
|
||||
for match in Image:Find(Rule.Pattern(value, mask)) do
|
||||
-- If valid:
|
||||
--
|
||||
if Image:DecodeLen(match) == (2+suf) then
|
||||
-- Decode the sub instruction, if it is not at arg area, write the result:
|
||||
--
|
||||
local subinsn = Image:Decode(match+2+suf)
|
||||
if subinsn.imm0 >= (8*9) then
|
||||
results[#results+1] = {
|
||||
callRva = match,
|
||||
subRva = match + 2 + suf,
|
||||
delta = subinsn.imm0 + 8
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort the table based on call complexity.
|
||||
--
|
||||
table.sort(results, function(a, b) return (a.subRva-a.callRva)>(b.subRva-b.callRva) end)
|
||||
|
||||
-- Get table length, trimming to a maximum of 32 and write the immediates.
|
||||
--
|
||||
local len = math.min(32, #results)
|
||||
local output = {}
|
||||
output[1] = len
|
||||
for i=1,len do
|
||||
output[i*2 + 0] = results[i].subRva
|
||||
output[i*2 + 1] = results[i].delta
|
||||
end
|
||||
return output
|
||||
end)
|
||||
Loading…
Reference in New Issue
Block a user