Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 194 additions & 15 deletions src/HeadlessWrapper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,81 @@
return 0
end

-- Search Handles
function NewFileSearch() end
-- [PoEW Patch] Basic file search for headless mode
-- Supports exact paths and simple glob patterns (e.g. "*.part*")
do
local function matchGlob(filename, pattern)
local luaPat = pattern:gsub("%.", "%%."):gsub("%*", ".*")
return filename:match("^" .. luaPat .. "$") ~= nil
end

local IS_WINDOWS = package.config:sub(1,1) == "\\"

local function listDir(dir)
local files = {}
local cmd
if IS_WINDOWS then
cmd = 'dir /b "' .. dir:gsub("/", "\\") .. '" 2>nul'
else
cmd = 'ls -1 "' .. dir .. '" 2>/dev/null'
end
local handle = io.popen(cmd)
if handle then
for line in handle:lines() do
line = line:gsub("%s+$", "") -- trim trailing \r on Windows
if line ~= "" then
files[#files + 1] = line
end
end
handle:close()
end
return files
end

local fileSearchClass = {}
fileSearchClass.__index = fileSearchClass

function fileSearchClass:GetFileName()
return self.files[self.index]
end

function fileSearchClass:GetFileModifiedTime()
-- Return a fixed timestamp; the .bin cache check will just re-extract
return 0
end

function fileSearchClass:NextFile()
self.index = self.index + 1
return self.index <= #self.files
end

function NewFileSearch(pattern)
if not pattern or pattern == "" then return nil end
local dir = pattern:gsub("[/\\][^/\\]*$", "")
local filePattern = pattern:gsub(".*[/\\]", "")

if not filePattern:find("[%*%?]") then
-- Exact path — just check existence
local f = io.open(pattern, "r")
if not f then return nil end
f:close()
local obj = setmetatable({ files = { filePattern }, index = 1, dir = dir }, fileSearchClass)
return obj
end

-- Glob pattern — list directory and filter
local entries = listDir(dir)
local matched = {}
for _, name in ipairs(entries) do
if matchGlob(name, filePattern) then
matched[#matched + 1] = name
end
end
table.sort(matched)
if #matched == 0 then return nil end
return setmetatable({ files = matched, index = 1, dir = dir }, fileSearchClass)
end
end

-- General Functions
function SetWindowTitle(title) end
Expand All @@ -88,32 +161,83 @@
function IsKeyDown(keyName) end
function Copy(text) end
function Paste() end
function Deflate(data)
-- TODO: Might need this
return ""
end
function Inflate(data)
-- TODO: And this
return ""
-- [PoEW Patch] Inflate/Deflate via LuaJIT FFI + system zlib
do
local ok, ffi = pcall(require, "ffi")
if ok and ffi then
ffi.cdef[[
unsigned long compressBound(unsigned long sourceLen);
int compress2(uint8_t *dest, unsigned long *destLen,
const uint8_t *source, unsigned long sourceLen, int level);
int uncompress(uint8_t *dest, unsigned long *destLen,
const uint8_t *source, unsigned long sourceLen);
]]
local zlib
-- Try loading zlib from common locations (Windows, Linux, macOS)
local libs = { "zlib1", "z", "libz.so.1", "libz.1.dylib" }
for _, name in ipairs(libs) do
local lok, lib = pcall(ffi.load, name)
if lok then zlib = lib; break end
end
if zlib then
function Deflate(data)
if not data or #data == 0 then return "" end
local srcLen = #data
local bound = zlib.compressBound(srcLen)
local buf = ffi.new("uint8_t[?]", bound)
local destLen = ffi.new("unsigned long[1]", bound)
local ret = zlib.compress2(buf, destLen, data, srcLen, 9)
if ret ~= 0 then return "" end
return ffi.string(buf, destLen[0])
end
function Inflate(data)
if not data or #data == 0 then return "" end
local srcLen = #data
-- Try progressively larger buffers (data can expand 10-50x)
for mult = 10, 100, 10 do
local destSize = srcLen * mult
local buf = ffi.new("uint8_t[?]", destSize)
local destLen = ffi.new("unsigned long[1]", destSize)
local ret = zlib.uncompress(buf, destLen, data, srcLen)
if ret == 0 then
return ffi.string(buf, destLen[0])
end
-- ret == -5 means buffer too small, try larger
if ret ~= -5 then return "" end
end
return ""
end
print("zlib loaded via FFI — Inflate/Deflate available")
else
print("WARNING: zlib not found — timeless jewel data unavailable")
function Deflate(data) return "" end
function Inflate(data) return "" end
end
else
function Deflate(data) return "" end
function Inflate(data) return "" end
end
end
function GetTime()
return 0
end
-- [PoEW Patch] Return actual paths for data file resolution
function GetScriptPath()
return ""
return _G.POB_SCRIPT_DIR or "."
end
function GetRuntimePath()
return ""
local base = _G.POB_SCRIPT_DIR or "."
return base .. "/../runtime"
end
function GetUserPath()
return ""
return _G.POB_SCRIPT_DIR or "."
end
function GetWorkDir()
return _G.POB_SCRIPT_DIR or ""
end
function MakeDir(path) end
function RemoveDir(path) end
function SetWorkDir(path) end
function GetWorkDir()
return ""
end
function LaunchSubScript(scriptText, funcList, subList, ...) end
function AbortSubScript(ssID) end
function IsSubScriptRunning(ssID) end
Expand Down Expand Up @@ -170,16 +294,56 @@
end



-- [PoEW Patch] Determine script directory for robust path resolution
-- Uses debug.getinfo instead of io.popen('pwd') for cross-platform support
local function _poew_get_script_dir()

Check warning on line 300 in src/HeadlessWrapper.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (poew)
local info = debug and debug.getinfo and debug.getinfo(1, 'S')
local src = info and info.source or ''
if type(src) == 'string' and src:sub(1,1) == '@' then
local path = src:sub(2)
local dir = (path:gsub('[^/\\]+$', '')):gsub('[/\\]$', '')
if dir ~= '' then return dir end
end
-- Fallback: probe for known files
local probes = { '.', 'src' }
for _, d in ipairs(probes) do
local fh = io.open(d .. '/HeadlessWrapper.lua', 'r')
if fh then fh:close(); return d end
end
return '.'
end
_G.POB_SCRIPT_DIR = _poew_get_script_dir()

Check warning on line 316 in src/HeadlessWrapper.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (poew)

local l_require = require
function require(name)
-- Hack to stop it looking for lcurl, which we don't really need
if name == "lcurl.safe" then
return
end
-- [PoEW Patch] UTF-8 fallback for headless mode (no native .so on macOS)
if name == "lua-utf8" then
local ok, mod = pcall(l_require, name)
if ok and type(mod) == 'table' then return mod end
local dir = _G.POB_SCRIPT_DIR or '.'
local fok, fmod = pcall(dofile, dir .. '/lua-utf8.lua')
if fok and type(fmod) == 'table' then return fmod end
return {}
end
return l_require(name)
end


-- [PoEW Patch] Add runtime Lua libraries to package.path
local base = _G.POB_SCRIPT_DIR or "."
local runtimeLua = base .. "/../runtime/lua"
local testPath = runtimeLua .. "/xml.lua"
local fh = io.open(testPath, "r")
if fh then
fh:close()
package.path = runtimeLua .. "/?.lua;" .. runtimeLua .. "/?/init.lua;" .. package.path
end

dofile("Launch.lua")

-- Prevents loading of ModCache
Expand Down Expand Up @@ -217,3 +381,18 @@
-- You now have a build without a correct main skill selected, or any configuration options set
-- Good luck!
end

-- [PoEW Patch] CLI flag detection
local function _poew_has_flag(flag)

Check warning on line 386 in src/HeadlessWrapper.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (poew)
if type(arg) ~= 'table' then return false end
for i = 1, #arg do if arg[i] == flag then return true end end
return false
end

-- [PoEW Patch] JSON-RPC API server activation (env-gated)
-- Set POB_API_STDIO=1 or pass --stdio to start the API server
if os.getenv('POB_API_STDIO') == '1' or _poew_has_flag('--stdio') then

Check warning on line 394 in src/HeadlessWrapper.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (poew)
local srvPath = (_G.POB_SCRIPT_DIR or '.') .. '/API/Server.lua'
dofile(srvPath)
return
end
Loading