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:
srift 2026-06-29 21:03:19 +02:00
commit c1bfbaf36a
4 changed files with 324 additions and 0 deletions

10
.gitignore vendored Normal file
View 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
View 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
View 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
View 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)