Merge branch 'master'

This commit is contained in:
2025-10-06 10:01:14 -04:00
14 changed files with 786 additions and 2740 deletions

Binary file not shown.

View File

@@ -1,323 +0,0 @@
-----------------------------------------------------------------------------
-- LTN12 - Filters, sources, sinks and pumps.
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module
-----------------------------------------------------------------------------
local string = require("string")
local table = require("table")
local base = _G
module("ltn12")
filter = {}
source = {}
sink = {}
pump = {}
-- 2048 seems to be better in windows...
BLOCKSIZE = 2048
_VERSION = "LTN12 1.0.1"
-----------------------------------------------------------------------------
-- Filter stuff
-----------------------------------------------------------------------------
-- returns a high level filter that cycles a low-level filter
function filter.cycle(low, ctx, extra)
base.assert(low)
return function(chunk)
local ret
ret, ctx = low(ctx, chunk, extra)
return ret
end
end
-- chains a bunch of filters together
-- (thanks to Wim Couwenberg)
function filter.chain(...)
local n = #arg
local top, index = 1, 1
local retry = ""
return function(chunk)
retry = chunk and retry
while true do
if index == top then
chunk = arg[index](chunk)
if chunk == "" or top == n then
return chunk
elseif chunk then
index = index + 1
else
top = top + 1
index = top
end
else
chunk = arg[index](chunk or "")
if chunk == "" then
index = index - 1
chunk = retry
elseif chunk then
if index == n then
return chunk
else
index = index + 1
end
else
base.error("filter returned inappropriate nil")
end
end
end
end
end
-----------------------------------------------------------------------------
-- Source stuff
-----------------------------------------------------------------------------
-- create an empty source
local function empty()
return nil
end
function source.empty()
return empty
end
-- returns a source that just outputs an error
function source.error(err)
return function()
return nil, err
end
end
-- creates a file source
function source.file(handle, io_err)
if handle then
return function()
local chunk = handle:read(BLOCKSIZE)
if not chunk then handle:close() end
return chunk
end
else
return source.error(io_err or "unable to open file")
end
end
-- turns a fancy source into a simple source
function source.simplify(src)
base.assert(src)
return function()
local chunk, err_or_new = src()
src = err_or_new or src
if not chunk then
return nil, err_or_new
else
return chunk
end
end
end
-- creates string source
function source.string(s)
if s then
local i = 1
return function()
local chunk = string.sub(s, i, i + BLOCKSIZE - 1)
i = i + BLOCKSIZE
if chunk ~= "" then
return chunk
else
return nil
end
end
else
return source.empty()
end
end
-- creates rewindable source
function source.rewind(src)
base.assert(src)
local t = {}
return function(chunk)
if not chunk then
chunk = table.remove(t)
if not chunk then
return src()
else
return chunk
end
else
table.insert(t, chunk)
end
end
end
function source.chain(src, f)
base.assert(src and f)
local last_in, last_out = "", ""
local state = "feeding"
local err
return function()
if not last_out then
base.error('source is empty!', 2)
end
while true do
if state == "feeding" then
last_in, err = src()
if err then return nil, err end
last_out = f(last_in)
if not last_out then
if last_in then
base.error('filter returned inappropriate nil')
else
return nil
end
elseif last_out ~= "" then
state = "eating"
if last_in then last_in = "" end
return last_out
end
else
last_out = f(last_in)
if last_out == "" then
if last_in == "" then
state = "feeding"
else
base.error('filter returned ""')
end
elseif not last_out then
if last_in then
base.error('filter returned inappropriate nil')
else
return nil
end
else
return last_out
end
end
end
end
end
-- creates a source that produces contents of several sources, one after the
-- other, as if they were concatenated
-- (thanks to Wim Couwenberg)
function source.cat(...)
local src = table.remove(arg, 1)
return function()
while src do
local chunk, err = src()
if chunk then return chunk end
if err then return nil, err end
src = table.remove(arg, 1)
end
end
end
-----------------------------------------------------------------------------
-- Sink stuff
-----------------------------------------------------------------------------
-- creates a sink that stores into a table
function sink.table(t)
t = t or {}
local f = function(chunk, err)
if chunk then table.insert(t, chunk) end
return 1
end
return f, t
end
-- turns a fancy sink into a simple sink
function sink.simplify(snk)
base.assert(snk)
return function(chunk, err)
local ret, err_or_new = snk(chunk, err)
if not ret then return nil, err_or_new end
snk = err_or_new or snk
return 1
end
end
-- creates a file sink
function sink.file(handle, io_err)
if handle then
return function(chunk, err)
if not chunk then
handle:close()
return 1
else
return handle:write(chunk)
end
end
else
return sink.error(io_err or "unable to open file")
end
end
-- creates a sink that discards data
local function null()
return 1
end
function sink.null()
return null
end
-- creates a sink that just returns an error
function sink.error(err)
return function()
return nil, err
end
end
-- chains a sink with a filter
function sink.chain(f, snk)
base.assert(f and snk)
return function(chunk, err)
if chunk ~= "" then
local filtered = f(chunk)
local done = chunk and ""
while true do
local ret, snkerr = snk(filtered, err)
if not ret then return nil, snkerr end
if filtered == done then return 1 end
filtered = f(done)
end
else
return 1
end
end
end
-----------------------------------------------------------------------------
-- Pump stuff
-----------------------------------------------------------------------------
-- pumps one chunk from the source to the sink
function pump.step(src, snk)
local chunk, src_err = src()
local ret, snk_err = snk(chunk, src_err)
if chunk and ret then
return 1
else
return nil, src_err or snk_err
end
end
-- pumps all data from a source to a sink, using a step function
function pump.all(src, snk, step)
base.assert(src and snk)
step = step or pump.step
while true do
local ret, err = step(src, snk)
if not ret then
if err then
return nil, err
else
return 1
end
end
end
end

View File

@@ -1,94 +0,0 @@
-----------------------------------------------------------------------------
-- MIME support for the Lua language.
-- Author: Diego Nehab
-- Conforming to RFCs 2045-2049
-- RCS ID: $Id: mime.lua,v 1.29 2007/06/11 23:44:54 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local ltn12 = require("ltn12")
local mime = require("mime.core")
--local io = require("io")
local string = require("string")
module("mime")
-- encode, decode and wrap algorithm tables
encodet = {}
decodet = {}
wrapt = {}
-- creates a function that chooses a filter by name from a given table
local function choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then
base.error("unknown key (" .. base.tostring(name) .. ")", 3)
else
return f(opt1, opt2)
end
end
end
-- define the encoding filters
encodet['base64'] = function()
return ltn12.filter.cycle(b64, "")
end
encodet['quoted-printable'] = function(mode)
return ltn12.filter.cycle(qp, "",
(mode == "binary") and "=0D=0A" or "\r\n")
end
-- define the decoding filters
decodet['base64'] = function()
return ltn12.filter.cycle(unb64, "")
end
decodet['quoted-printable'] = function()
return ltn12.filter.cycle(unqp, "")
end
local function format(chunk)
if chunk then
if chunk == "" then
return "''"
else
return string.len(chunk)
end
else
return "nil"
end
end
-- define the line-wrap filters
wrapt['text'] = function(length)
length = length or 76
return ltn12.filter.cycle(wrp, length, length)
end
wrapt['base64'] = wrapt['text']
wrapt['default'] = wrapt['text']
wrapt['quoted-printable'] = function()
return ltn12.filter.cycle(qpwrp, 76, 76)
end
-- function that choose the encoding, decoding or wrap algorithm
encode = choose(encodet)
decode = choose(decodet)
wrap = choose(wrapt)
-- define the end-of-line normalization filter
function normalize(marker)
return ltn12.filter.cycle(eol, 0, marker)
end
-- high level stuffing filter
function stuff()
return ltn12.filter.cycle(dot, 2)
end

Binary file not shown.

View File

@@ -1,143 +0,0 @@
-----------------------------------------------------------------------------
-- LuaSocket helper module
-- Author: Diego Nehab
-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket.core")
module("socket")
-----------------------------------------------------------------------------
-- Exported auxiliar functions
-----------------------------------------------------------------------------
function connect(address, port, laddress, lport)
local sock, err = socket.tcp()
if not sock then return nil, err end
if laddress then
local res, err = sock:bind(laddress, lport, -1)
if not res then return nil, err end
end
local res, err = sock:connect(address, port)
if not res then return nil, err end
return sock
end
function bind(host, port, backlog)
local sock, err = socket.tcp()
if not sock then return nil, err end
sock:setoption("reuseaddr", true)
local res, err = sock:bind(host, port)
if not res then return nil, err end
res, err = sock:listen(backlog)
if not res then return nil, err end
return sock
end
try = newtry()
function choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then
base.error("unknown key (" .. base.tostring(name) .. ")", 3)
else
return f(opt1, opt2)
end
end
end
-----------------------------------------------------------------------------
-- Socket sources and sinks, conforming to LTN12
-----------------------------------------------------------------------------
-- create namespaces inside LuaSocket namespace
sourcet = {}
sinkt = {}
BLOCKSIZE = 2048
sinkt["close-when-done"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then
sock:close()
return 1
else
return sock:send(chunk)
end
end
})
end
sinkt["keep-open"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if chunk then
return sock:send(chunk)
else
return 1
end
end
})
end
sinkt["default"] = sinkt["keep-open"]
sink = choose(sinkt)
sourcet["by-length"] = function(sock, length)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if length <= 0 then return nil end
local size = math.min(socket.BLOCKSIZE, length)
local chunk, err = sock:receive(size)
if err then return nil, err end
length = length - string.len(chunk)
return chunk
end
})
end
sourcet["until-closed"] = function(sock)
local done
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if done then return nil end
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
if not err then
return chunk
elseif err == "closed" then
sock:close()
done = 1
return partial
else
return nil, err
end
end
})
end
sourcet["default"] = sourcet["until-closed"]
source = choose(sourcet)

Binary file not shown.

View File

@@ -1,288 +0,0 @@
-----------------------------------------------------------------------------
-- FTP support for the Lua language
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: ftp.lua,v 1.45 2007/07/11 19:25:47 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local table = require("table")
local string = require("string")
local math = require("math")
local socket = require("socket")
local url = require("socket.url")
local tp = require("socket.tp")
local ltn12 = require("ltn12")
module("socket.ftp")
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- timeout in seconds before the program gives up on a connection
TIMEOUT = 60
-- default port for ftp service
PORT = 21
-- this is the default anonymous password. used when no password is
-- provided in url. should be changed to your e-mail.
USER = "ftp"
PASSWORD = "anonymous@anonymous.org"
-----------------------------------------------------------------------------
-- Low level FTP API
-----------------------------------------------------------------------------
local metat = { __index = {} }
function open(server, port, create)
local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create))
local f = base.setmetatable({ tp = tp }, metat)
-- make sure everything gets closed in an exception
f.try = socket.newtry(function() f:close() end)
return f
end
function metat.__index:portconnect()
self.try(self.server:settimeout(TIMEOUT))
self.data = self.try(self.server:accept())
self.try(self.data:settimeout(TIMEOUT))
end
function metat.__index:pasvconnect()
self.data = self.try(socket.tcp())
self.try(self.data:settimeout(TIMEOUT))
self.try(self.data:connect(self.pasvt.ip, self.pasvt.port))
end
function metat.__index:login(user, password)
self.try(self.tp:command("user", user or USER))
local code, reply = self.try(self.tp:check { "2..", 331 })
if code == 331 then
self.try(self.tp:command("pass", password or PASSWORD))
self.try(self.tp:check("2.."))
end
return 1
end
function metat.__index:pasv()
self.try(self.tp:command("pasv"))
local code, reply = self.try(self.tp:check("2.."))
local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)"
local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern))
self.try(a and b and c and d and p1 and p2, reply)
self.pasvt = {
ip = string.format("%d.%d.%d.%d", a, b, c, d),
port = p1 * 256 + p2
}
if self.server then
self.server:close()
self.server = nil
end
return self.pasvt.ip, self.pasvt.port
end
function metat.__index:port(ip, port)
self.pasvt = nil
if not ip then
ip, port = self.try(self.tp:getcontrol():getsockname())
self.server = self.try(socket.bind(ip, 0))
ip, port = self.try(self.server:getsockname())
self.try(self.server:settimeout(TIMEOUT))
end
local pl = math.mod(port, 256)
local ph = (port - pl) / 256
local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
self.try(self.tp:command("port", arg))
self.try(self.tp:check("2.."))
return 1
end
function metat.__index:send(sendt)
self.try(self.pasvt or self.server, "need port or pasv first")
-- if there is a pasvt table, we already sent a PASV command
-- we just get the data connection into self.data
if self.pasvt then self:pasvconnect() end
-- get the transfer argument and command
local argument = sendt.argument or
url.unescape(string.gsub(sendt.path or "", "^[/\\]", ""))
if argument == "" then argument = nil end
local command = sendt.command or "stor"
-- send the transfer command and check the reply
self.try(self.tp:command(command, argument))
local code, reply = self.try(self.tp:check { "2..", "1.." })
-- if there is not a a pasvt table, then there is a server
-- and we already sent a PORT command
if not self.pasvt then self:portconnect() end
-- get the sink, source and step for the transfer
local step = sendt.step or ltn12.pump.step
local readt = { self.tp.c }
local checkstep = function(src, snk)
-- check status in control connection while downloading
local readyt = socket.select(readt, nil, 0)
if readyt[tp] then code = self.try(self.tp:check("2..")) end
return step(src, snk)
end
local sink = socket.sink("close-when-done", self.data)
-- transfer all data and check error
self.try(ltn12.pump.all(sendt.source, sink, checkstep))
if string.find(code, "1..") then self.try(self.tp:check("2..")) end
-- done with data connection
self.data:close()
-- find out how many bytes were sent
local sent = socket.skip(1, self.data:getstats())
self.data = nil
return sent
end
function metat.__index:receive(recvt)
self.try(self.pasvt or self.server, "need port or pasv first")
if self.pasvt then self:pasvconnect() end
local argument = recvt.argument or
url.unescape(string.gsub(recvt.path or "", "^[/\\]", ""))
if argument == "" then argument = nil end
local command = recvt.command or "retr"
self.try(self.tp:command(command, argument))
local code = self.try(self.tp:check { "1..", "2.." })
if not self.pasvt then self:portconnect() end
local source = socket.source("until-closed", self.data)
local step = recvt.step or ltn12.pump.step
self.try(ltn12.pump.all(source, recvt.sink, step))
if string.find(code, "1..") then self.try(self.tp:check("2..")) end
self.data:close()
self.data = nil
return 1
end
function metat.__index:cwd(dir)
self.try(self.tp:command("cwd", dir))
self.try(self.tp:check(250))
return 1
end
function metat.__index:type(type)
self.try(self.tp:command("type", type))
self.try(self.tp:check(200))
return 1
end
function metat.__index:greet()
local code = self.try(self.tp:check { "1..", "2.." })
if string.find(code, "1..") then self.try(self.tp:check("2..")) end
return 1
end
function metat.__index:quit()
self.try(self.tp:command("quit"))
self.try(self.tp:check("2.."))
return 1
end
function metat.__index:close()
if self.data then self.data:close() end
if self.server then self.server:close() end
return self.tp:close()
end
-----------------------------------------------------------------------------
-- High level FTP API
-----------------------------------------------------------------------------
local function override(t)
if t.url then
local u = url.parse(t.url)
for i, v in base.pairs(t) do
u[i] = v
end
return u
else
return t
end
end
local function tput(putt)
putt = override(putt)
socket.try(putt.host, "missing hostname")
local f = open(putt.host, putt.port, putt.create)
f:greet()
f:login(putt.user, putt.password)
if putt.type then f:type(putt.type) end
f:pasv()
local sent = f:send(putt)
f:quit()
f:close()
return sent
end
local default = {
path = "/",
scheme = "ftp"
}
local function parse(u)
local t = socket.try(url.parse(u, default))
socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'")
socket.try(t.host, "missing hostname")
local pat = "^type=(.)$"
if t.params then
t.type = socket.skip(2, string.find(t.params, pat))
socket.try(t.type == "a" or t.type == "i",
"invalid type '" .. t.type .. "'")
end
return t
end
local function sput(u, body)
local putt = parse(u)
putt.source = ltn12.source.string(body)
return tput(putt)
end
put = socket.protect(function(putt, body)
if base.type(putt) == "string" then
return sput(putt, body)
else
return tput(putt)
end
end)
local function tget(gett)
gett = override(gett)
socket.try(gett.host, "missing hostname")
local f = open(gett.host, gett.port, gett.create)
f:greet()
f:login(gett.user, gett.password)
if gett.type then f:type(gett.type) end
f:pasv()
f:receive(gett)
f:quit()
return f:close()
end
local function sget(u)
local gett = parse(u)
local t = {}
gett.sink = ltn12.sink.table(t)
tget(gett)
return table.concat(t)
end
command = socket.protect(function(cmdt)
cmdt = override(cmdt)
socket.try(cmdt.host, "missing hostname")
socket.try(cmdt.command, "missing command")
local f = open(cmdt.host, cmdt.port, cmdt.create)
f:greet()
f:login(cmdt.user, cmdt.password)
f.try(f.tp:command(cmdt.command, cmdt.argument))
if cmdt.check then f.try(f.tp:check(cmdt.check)) end
f:quit()
return f:close()
end)
get = socket.protect(function(gett)
if base.type(gett) == "string" then
return sget(gett)
else
return tget(gett)
end
end)

View File

@@ -1,359 +0,0 @@
-----------------------------------------------------------------------------
-- HTTP/1.1 client support for the Lua language.
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: http.lua,v 1.70 2007/03/12 04:08:40 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-------------------------------------------------------------------------------
local socket = require("socket")
local url = require("socket.url")
local ltn12 = require("ltn12")
local mime = require("mime")
local string = require("string")
local base = _G
local table = require("table")
module("socket.http")
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- connection timeout in seconds
TIMEOUT = 60
-- default port for document retrieval
PORT = 80
-- user agent field sent in request
USERAGENT = socket._VERSION
-----------------------------------------------------------------------------
-- Reads MIME headers from a connection, unfolding where needed
-----------------------------------------------------------------------------
local function receiveheaders(sock, headers)
local line, name, value, err
headers = headers or {}
-- get first line
line, err = sock:receive()
if err then return nil, err end
-- headers go until a blank line is found
while line ~= "" do
-- get field-name and value
name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
if not (name and value) then return nil, "malformed reponse headers" end
name = string.lower(name)
-- get next line (value might be folded)
line, err = sock:receive()
if err then return nil, err end
-- unfold any folded values
while string.find(line, "^%s") do
value = value .. line
line = sock:receive()
if err then return nil, err end
end
-- save pair in table
if headers[name] then
headers[name] = headers[name] .. ", " .. value
else
headers[name] = value
end
end
return headers
end
-----------------------------------------------------------------------------
-- Extra sources and sinks
-----------------------------------------------------------------------------
socket.sourcet["http-chunked"] = function(sock, headers)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
-- get chunk size, skip extention
local line, err = sock:receive()
if err then return nil, err end
local size = base.tonumber(string.gsub(line, ";.*", ""), 16)
if not size then return nil, "invalid chunk size" end
-- was it the last chunk?
if size > 0 then
-- if not, get chunk and skip terminating CRLF
local chunk, err, part = sock:receive(size)
if chunk then sock:receive() end
return chunk, err
else
-- if it was, read trailers into headers table
headers, err = receiveheaders(sock, headers)
if not headers then return nil, err end
end
end
})
end
socket.sinkt["http-chunked"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then return sock:send("0\r\n\r\n") end
local size = string.format("%X\r\n", string.len(chunk))
return sock:send(size .. chunk .. "\r\n")
end
})
end
-----------------------------------------------------------------------------
-- Low level HTTP API
-----------------------------------------------------------------------------
local metat = { __index = {} }
function open(host, port, create)
-- create socket with user connect function, or with default
local c = socket.try((create or socket.tcp)())
local h = base.setmetatable({ c = c }, metat)
-- create finalized try
h.try = socket.newtry(function() h:close() end)
-- set timeout before connecting
h.try(c:settimeout(TIMEOUT))
h.try(c:connect(host, port or PORT))
-- here everything worked
return h
end
function metat.__index:sendrequestline(method, uri)
local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
return self.try(self.c:send(reqline))
end
function metat.__index:sendheaders(headers)
local h = "\r\n"
for i, v in base.pairs(headers) do
h = i .. ": " .. v .. "\r\n" .. h
end
self.try(self.c:send(h))
return 1
end
function metat.__index:sendbody(headers, source, step)
source = source or ltn12.source.empty()
step = step or ltn12.pump.step
-- if we don't know the size in advance, send chunked and hope for the best
local mode = "http-chunked"
if headers["content-length"] then mode = "keep-open" end
return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step))
end
function metat.__index:receivestatusline()
local status = self.try(self.c:receive(5))
-- identify HTTP/0.9 responses, which do not contain a status line
-- this is just a heuristic, but is what the RFC recommends
if status ~= "HTTP/" then return nil, status end
-- otherwise proceed reading a status line
status = self.try(self.c:receive("*l", status))
local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
return self.try(base.tonumber(code), status)
end
function metat.__index:receiveheaders()
return self.try(receiveheaders(self.c))
end
function metat.__index:receivebody(headers, sink, step)
sink = sink or ltn12.sink.null()
step = step or ltn12.pump.step
local length = base.tonumber(headers["content-length"])
local t = headers["transfer-encoding"] -- shortcut
local mode = "default" -- connection close
if t and t ~= "identity" then
mode = "http-chunked"
elseif base.tonumber(headers["content-length"]) then
mode = "by-length"
end
return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
sink, step))
end
function metat.__index:receive09body(status, sink, step)
local source = ltn12.source.rewind(socket.source("until-closed", self.c))
source(status)
return self.try(ltn12.pump.all(source, sink, step))
end
function metat.__index:close()
return self.c:close()
end
-----------------------------------------------------------------------------
-- High level HTTP API
-----------------------------------------------------------------------------
local function adjusturi(reqt)
local u = reqt
-- if there is a proxy, we need the full url. otherwise, just a part.
if not reqt.proxy and not PROXY then
u = {
path = socket.try(reqt.path, "invalid path 'nil'"),
params = reqt.params,
query = reqt.query,
fragment = reqt.fragment
}
end
return url.build(u)
end
local function adjustproxy(reqt)
local proxy = reqt.proxy or PROXY
if proxy then
proxy = url.parse(proxy)
return proxy.host, proxy.port or 3128
else
return reqt.host, reqt.port
end
end
local function adjustheaders(reqt)
-- default headers
local lower = {
["user-agent"] = USERAGENT,
["host"] = reqt.host,
["connection"] = "close, TE",
["te"] = "trailers"
}
-- if we have authentication information, pass it along
if reqt.user and reqt.password then
lower["authorization"] =
"Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password))
end
-- override with user headers
for i, v in base.pairs(reqt.headers or lower) do
lower[string.lower(i)] = v
end
return lower
end
-- default url parts
local default = {
host = "",
port = PORT,
path = "/",
scheme = "http"
}
local function adjustrequest(reqt)
-- parse url if provided
local nreqt = reqt.url and url.parse(reqt.url, default) or {}
-- explicit components override url
for i, v in base.pairs(reqt) do nreqt[i] = v end
if nreqt.port == "" then nreqt.port = 80 end
socket.try(nreqt.host and nreqt.host ~= "",
"invalid host '" .. base.tostring(nreqt.host) .. "'")
-- compute uri if user hasn't overriden
nreqt.uri = reqt.uri or adjusturi(nreqt)
-- ajust host and port if there is a proxy
nreqt.host, nreqt.port = adjustproxy(nreqt)
-- adjust headers in request
nreqt.headers = adjustheaders(nreqt)
return nreqt
end
local function shouldredirect(reqt, code, headers)
return headers.location and
string.gsub(headers.location, "%s", "") ~= "" and
(reqt.redirect ~= false) and
(code == 301 or code == 302) and
(not reqt.method or reqt.method == "GET" or reqt.method == "HEAD")
and (not reqt.nredirects or reqt.nredirects < 5)
end
local function shouldreceivebody(reqt, code)
if reqt.method == "HEAD" then return nil end
if code == 204 or code == 304 then return nil end
if code >= 100 and code < 200 then return nil end
return 1
end
-- forward declarations
local trequest, tredirect
function tredirect(reqt, location)
local result, code, headers, status = trequest {
-- the RFC says the redirect URL has to be absolute, but some
-- servers do not respect that
url = url.absolute(reqt.url, location),
source = reqt.source,
sink = reqt.sink,
headers = reqt.headers,
proxy = reqt.proxy,
nredirects = (reqt.nredirects or 0) + 1,
create = reqt.create
}
-- pass location header back as a hint we redirected
headers = headers or {}
headers.location = headers.location or location
return result, code, headers, status
end
function trequest(reqt)
-- we loop until we get what we want, or
-- until we are sure there is no way to get it
local nreqt = adjustrequest(reqt)
local h = open(nreqt.host, nreqt.port, nreqt.create)
-- send request line and headers
h:sendrequestline(nreqt.method, nreqt.uri)
h:sendheaders(nreqt.headers)
-- if there is a body, send it
if nreqt.source then
h:sendbody(nreqt.headers, nreqt.source, nreqt.step)
end
local code, status = h:receivestatusline()
-- if it is an HTTP/0.9 server, simply get the body and we are done
if not code then
h:receive09body(status, nreqt.sink, nreqt.step)
return 1, 200
end
local headers
-- ignore any 100-continue messages
while code == 100 do
headers = h:receiveheaders()
code, status = h:receivestatusline()
end
headers = h:receiveheaders()
-- at this point we should have a honest reply from the server
-- we can't redirect if we already used the source, so we report the error
if shouldredirect(nreqt, code, headers) and not nreqt.source then
h:close()
return tredirect(reqt, headers.location)
end
-- here we are finally done
if shouldreceivebody(nreqt, code) then
h:receivebody(headers, nreqt.sink, nreqt.step)
end
h:close()
return 1, code, headers, status
end
local function srequest(u, b)
local t = {}
local reqt = {
url = u,
sink = ltn12.sink.table(t)
}
if b then
reqt.source = ltn12.source.string(b)
reqt.headers = {
["content-length"] = string.len(b),
["content-type"] = "application/x-www-form-urlencoded"
}
reqt.method = "POST"
end
local code, headers, status = socket.skip(1, trequest(reqt))
return table.concat(t), code, headers, status
end
request = socket.protect(function(reqt, body)
if base.type(reqt) == "string" then
return srequest(reqt, body)
else
return trequest(reqt)
end
end)

View File

@@ -1,262 +0,0 @@
-----------------------------------------------------------------------------
-- SMTP client support for the Lua language.
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: smtp.lua,v 1.46 2007/03/12 04:08:40 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local coroutine = require("coroutine")
local string = require("string")
local math = require("math")
local os = require("os")
local socket = require("socket")
local tp = require("socket.tp")
local ltn12 = require("ltn12")
local mime = require("mime")
module("socket.smtp")
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- timeout for connection
TIMEOUT = 60
-- default server used to send e-mails
SERVER = "localhost"
-- default port
PORT = 25
-- domain used in HELO command and default sendmail
-- If we are under a CGI, try to get from environment
DOMAIN = os.getenv("SERVER_NAME") or "localhost"
-- default time zone (means we don't know)
ZONE = "-0000"
---------------------------------------------------------------------------
-- Low level SMTP API
-----------------------------------------------------------------------------
local metat = { __index = {} }
function metat.__index:greet(domain)
self.try(self.tp:check("2.."))
self.try(self.tp:command("EHLO", domain or DOMAIN))
return socket.skip(1, self.try(self.tp:check("2..")))
end
function metat.__index:mail(from)
self.try(self.tp:command("MAIL", "FROM:" .. from))
return self.try(self.tp:check("2.."))
end
function metat.__index:rcpt(to)
self.try(self.tp:command("RCPT", "TO:" .. to))
return self.try(self.tp:check("2.."))
end
function metat.__index:data(src, step)
self.try(self.tp:command("DATA"))
self.try(self.tp:check("3.."))
self.try(self.tp:source(src, step))
self.try(self.tp:send("\r\n.\r\n"))
return self.try(self.tp:check("2.."))
end
function metat.__index:quit()
self.try(self.tp:command("QUIT"))
return self.try(self.tp:check("2.."))
end
function metat.__index:close()
return self.tp:close()
end
function metat.__index:login(user, password)
self.try(self.tp:command("AUTH", "LOGIN"))
self.try(self.tp:check("3.."))
self.try(self.tp:command(mime.b64(user)))
self.try(self.tp:check("3.."))
self.try(self.tp:command(mime.b64(password)))
return self.try(self.tp:check("2.."))
end
function metat.__index:plain(user, password)
local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password)
self.try(self.tp:command("AUTH", auth))
return self.try(self.tp:check("2.."))
end
function metat.__index:auth(user, password, ext)
if not user or not password then return 1 end
if string.find(ext, "AUTH[^\n]+LOGIN") then
return self:login(user, password)
elseif string.find(ext, "AUTH[^\n]+PLAIN") then
return self:plain(user, password)
else
self.try(nil, "authentication not supported")
end
end
-- send message or throw an exception
function metat.__index:send(mailt)
self:mail(mailt.from)
if base.type(mailt.rcpt) == "table" then
for i, v in base.ipairs(mailt.rcpt) do
self:rcpt(v)
end
else
self:rcpt(mailt.rcpt)
end
self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step)
end
function open(server, port, create)
local tp = socket.try(tp.connect(server or SERVER, port or PORT,
TIMEOUT, create))
local s = base.setmetatable({ tp = tp }, metat)
-- make sure tp is closed if we get an exception
s.try = socket.newtry(function()
s:close()
end)
return s
end
-- convert headers to lowercase
local function lower_headers(headers)
local lower = {}
for i, v in base.pairs(headers or lower) do
lower[string.lower(i)] = v
end
return lower
end
---------------------------------------------------------------------------
-- Multipart message source
-----------------------------------------------------------------------------
-- returns a hopefully unique mime boundary
local seqno = 0
local function newboundary()
seqno = seqno + 1
return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'),
math.random(0, 99999), seqno)
end
-- send_message forward declaration
local send_message
-- yield the headers all at once, it's faster
local function send_headers(headers)
local h = "\r\n"
for i, v in base.pairs(headers) do
h = i .. ': ' .. v .. "\r\n" .. h
end
coroutine.yield(h)
end
-- yield multipart message body from a multipart message table
local function send_multipart(mesgt)
-- make sure we have our boundary and send headers
local bd = newboundary()
local headers = lower_headers(mesgt.headers or {})
headers['content-type'] = headers['content-type'] or 'multipart/mixed'
headers['content-type'] = headers['content-type'] ..
'; boundary="' .. bd .. '"'
send_headers(headers)
-- send preamble
if mesgt.body.preamble then
coroutine.yield(mesgt.body.preamble)
coroutine.yield("\r\n")
end
-- send each part separated by a boundary
for i, m in base.ipairs(mesgt.body) do
coroutine.yield("\r\n--" .. bd .. "\r\n")
send_message(m)
end
-- send last boundary
coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n")
-- send epilogue
if mesgt.body.epilogue then
coroutine.yield(mesgt.body.epilogue)
coroutine.yield("\r\n")
end
end
-- yield message body from a source
local function send_source(mesgt)
-- make sure we have a content-type
local headers = lower_headers(mesgt.headers or {})
headers['content-type'] = headers['content-type'] or
'text/plain; charset="iso-8859-1"'
send_headers(headers)
-- send body from source
while true do
local chunk, err = mesgt.body()
if err then
coroutine.yield(nil, err)
elseif chunk then
coroutine.yield(chunk)
else
break
end
end
end
-- yield message body from a string
local function send_string(mesgt)
-- make sure we have a content-type
local headers = lower_headers(mesgt.headers or {})
headers['content-type'] = headers['content-type'] or
'text/plain; charset="iso-8859-1"'
send_headers(headers)
-- send body from string
coroutine.yield(mesgt.body)
end
-- message source
function send_message(mesgt)
if base.type(mesgt.body) == "table" then
send_multipart(mesgt)
elseif base.type(mesgt.body) == "function" then
send_source(mesgt)
else
send_string(mesgt)
end
end
-- set defaul headers
local function adjust_headers(mesgt)
local lower = lower_headers(mesgt.headers)
lower["date"] = lower["date"] or
os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE)
lower["x-mailer"] = lower["x-mailer"] or socket._VERSION
-- this can't be overriden
lower["mime-version"] = "1.0"
return lower
end
function message(mesgt)
mesgt.headers = adjust_headers(mesgt)
-- create and return message source
local co = coroutine.create(function() send_message(mesgt) end)
return function()
local ret, a, b = coroutine.resume(co)
if ret then
return a, b
else
return nil, a
end
end
end
---------------------------------------------------------------------------
-- High level SMTP API
-----------------------------------------------------------------------------
send = socket.protect(function(mailt)
local s = open(mailt.server, mailt.port, mailt.create)
local ext = s:greet(mailt.domain)
s:auth(mailt.user, mailt.password, ext)
s:send(mailt)
s:quit()
return s:close()
end)

View File

@@ -1,127 +0,0 @@
-----------------------------------------------------------------------------
-- Unified SMTP/FTP subsystem
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: tp.lua,v 1.22 2006/03/14 09:04:15 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local socket = require("socket")
local ltn12 = require("ltn12")
module("socket.tp")
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
TIMEOUT = 60
-----------------------------------------------------------------------------
-- Implementation
-----------------------------------------------------------------------------
-- gets server reply (works for SMTP and FTP)
local function get_reply(c)
local code, current, sep
local line, err = c:receive()
local reply = line
if err then return nil, err end
code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)"))
if not code then return nil, "invalid server reply" end
if sep == "-" then -- reply is multiline
repeat
line, err = c:receive()
if err then return nil, err end
current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)"))
reply = reply .. "\n" .. line
-- reply ends with same code
until code == current and sep == " "
end
return code, reply
end
-- metatable for sock object
local metat = { __index = {} }
function metat.__index:check(ok)
local code, reply = get_reply(self.c)
if not code then return nil, reply end
if base.type(ok) ~= "function" then
if base.type(ok) == "table" then
for i, v in base.ipairs(ok) do
if string.find(code, v) then
return base.tonumber(code), reply
end
end
return nil, reply
else
if string.find(code, ok) then
return base.tonumber(code), reply
else
return nil, reply
end
end
else
return ok(base.tonumber(code), reply)
end
end
function metat.__index:command(cmd, arg)
if arg then
return self.c:send(cmd .. " " .. arg .. "\r\n")
else
return self.c:send(cmd .. "\r\n")
end
end
function metat.__index:sink(snk, pat)
local chunk, err = c:receive(pat)
return snk(chunk, err)
end
function metat.__index:send(data)
return self.c:send(data)
end
function metat.__index:receive(pat)
return self.c:receive(pat)
end
function metat.__index:getfd()
return self.c:getfd()
end
function metat.__index:dirty()
return self.c:dirty()
end
function metat.__index:getcontrol()
return self.c
end
function metat.__index:source(source, step)
local sink = socket.sink("keep-open", self.c)
local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step)
return ret, err
end
-- closes the underlying c
function metat.__index:close()
self.c:close()
return 1
end
-- connect with server and return c object
function connect(host, port, timeout, create)
local c, e = (create or socket.tcp)()
if not c then return nil, e end
c:settimeout(timeout or TIMEOUT)
local r, e = c:connect(host, port)
if not r then
c:close()
return nil, e
end
return base.setmetatable({ c = c }, metat)
end

View File

@@ -1,311 +0,0 @@
-----------------------------------------------------------------------------
-- URI parsing, composition and relative URL resolution
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module
-----------------------------------------------------------------------------
local string = require("string")
local base = _G
local table = require("table")
module("socket.url")
-----------------------------------------------------------------------------
-- Module version
-----------------------------------------------------------------------------
_VERSION = "URL 1.0.1"
-----------------------------------------------------------------------------
-- Encodes a string into its escaped hexadecimal representation
-- Input
-- s: binary string to be encoded
-- Returns
-- escaped representation of string binary
-----------------------------------------------------------------------------
function escape(s)
return string.gsub(s, "([^A-Za-z0-9_])", function(c)
return string.format("%%%02x", string.byte(c))
end)
end
-----------------------------------------------------------------------------
-- Protects a path segment, to prevent it from interfering with the
-- url parsing.
-- Input
-- s: binary string to be encoded
-- Returns
-- escaped representation of string binary
-----------------------------------------------------------------------------
local function make_set(t)
local s = {}
for i, v in base.ipairs(t) do
s[t[i]] = 1
end
return s
end
-- these are allowed withing a path segment, along with alphanum
-- other characters must be escaped
local segment_set = make_set {
"-", "_", ".", "!", "~", "*", "'", "(",
")", ":", "@", "&", "=", "+", "$", ",",
}
local function protect_segment(s)
return string.gsub(s, "([^A-Za-z0-9_])", function(c)
if segment_set[c] then
return c
else
return string.format("%%%02x", string.byte(c))
end
end)
end
-----------------------------------------------------------------------------
-- Encodes a string into its escaped hexadecimal representation
-- Input
-- s: binary string to be encoded
-- Returns
-- escaped representation of string binary
-----------------------------------------------------------------------------
function unescape(s)
return string.gsub(s, "%%(%x%x)", function(hex)
return string.char(base.tonumber(hex, 16))
end)
end
-----------------------------------------------------------------------------
-- Builds a path from a base path and a relative path
-- Input
-- base_path
-- relative_path
-- Returns
-- corresponding absolute path
-----------------------------------------------------------------------------
local function absolute_path(base_path, relative_path)
if string.sub(relative_path, 1, 1) == "/" then return relative_path end
local path = string.gsub(base_path, "[^/]*$", "")
path = path .. relative_path
path = string.gsub(path, "([^/]*%./)", function(s)
if s ~= "./" then return s else return "" end
end)
path = string.gsub(path, "/%.$", "/")
local reduced
while reduced ~= path do
reduced = path
path = string.gsub(reduced, "([^/]*/%.%./)", function(s)
if s ~= "../../" then return "" else return s end
end)
end
path = string.gsub(reduced, "([^/]*/%.%.)$", function(s)
if s ~= "../.." then return "" else return s end
end)
return path
end
-----------------------------------------------------------------------------
-- Parses a url and returns a table with all its parts according to RFC 2396
-- The following grammar describes the names given to the URL parts
-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
-- <authority> ::= <userinfo>@<host>:<port>
-- <userinfo> ::= <user>[:<password>]
-- <path> :: = {<segment>/}<segment>
-- Input
-- url: uniform resource locator of request
-- default: table with default values for each field
-- Returns
-- table with the following fields, where RFC naming conventions have
-- been preserved:
-- scheme, authority, userinfo, user, password, host, port,
-- path, params, query, fragment
-- Obs:
-- the leading '/' in {/<path>} is considered part of <path>
-----------------------------------------------------------------------------
function parse(url, default)
-- initialize default parameters
local parsed = {}
for i, v in base.pairs(default or parsed) do parsed[i] = v end
-- empty url is parsed to nil
if not url or url == "" then return nil, "invalid url" end
-- remove whitespace
-- url = string.gsub(url, "%s", "")
-- get fragment
url = string.gsub(url, "#(.*)$", function(f)
parsed.fragment = f
return ""
end)
-- get scheme
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
function(s)
parsed.scheme = s; return ""
end)
-- get authority
url = string.gsub(url, "^//([^/]*)", function(n)
parsed.authority = n
return ""
end)
-- get query stringing
url = string.gsub(url, "%?(.*)", function(q)
parsed.query = q
return ""
end)
-- get params
url = string.gsub(url, "%;(.*)", function(p)
parsed.params = p
return ""
end)
-- path is whatever was left
if url ~= "" then parsed.path = url end
local authority = parsed.authority
if not authority then return parsed end
authority = string.gsub(authority, "^([^@]*)@",
function(u)
parsed.userinfo = u; return ""
end)
authority = string.gsub(authority, ":([^:]*)$",
function(p)
parsed.port = p; return ""
end)
if authority ~= "" then parsed.host = authority end
local userinfo = parsed.userinfo
if not userinfo then return parsed end
userinfo = string.gsub(userinfo, ":([^:]*)$",
function(p)
parsed.password = p; return ""
end)
parsed.user = userinfo
return parsed
end
-----------------------------------------------------------------------------
-- Rebuilds a parsed URL from its components.
-- Components are protected if any reserved or unallowed characters are found
-- Input
-- parsed: parsed URL, as returned by parse
-- Returns
-- a stringing with the corresponding URL
-----------------------------------------------------------------------------
function build(parsed)
local ppath = parse_path(parsed.path or "")
local url = build_path(ppath)
if parsed.params then url = url .. ";" .. parsed.params end
if parsed.query then url = url .. "?" .. parsed.query end
local authority = parsed.authority
if parsed.host then
authority = parsed.host
if parsed.port then authority = authority .. ":" .. parsed.port end
local userinfo = parsed.userinfo
if parsed.user then
userinfo = parsed.user
if parsed.password then
userinfo = userinfo .. ":" .. parsed.password
end
end
if userinfo then authority = userinfo .. "@" .. authority end
end
if authority then url = "//" .. authority .. url end
if parsed.scheme then url = parsed.scheme .. ":" .. url end
if parsed.fragment then url = url .. "#" .. parsed.fragment end
-- url = string.gsub(url, "%s", "")
return url
end
-----------------------------------------------------------------------------
-- Builds a absolute URL from a base and a relative URL according to RFC 2396
-- Input
-- base_url
-- relative_url
-- Returns
-- corresponding absolute url
-----------------------------------------------------------------------------
function absolute(base_url, relative_url)
if base.type(base_url) == "table" then
base_parsed = base_url
base_url = build(base_parsed)
else
base_parsed = parse(base_url)
end
local relative_parsed = parse(relative_url)
if not base_parsed then
return relative_url
elseif not relative_parsed then
return base_url
elseif relative_parsed.scheme then
return relative_url
else
relative_parsed.scheme = base_parsed.scheme
if not relative_parsed.authority then
relative_parsed.authority = base_parsed.authority
if not relative_parsed.path then
relative_parsed.path = base_parsed.path
if not relative_parsed.params then
relative_parsed.params = base_parsed.params
if not relative_parsed.query then
relative_parsed.query = base_parsed.query
end
end
else
relative_parsed.path = absolute_path(base_parsed.path or "",
relative_parsed.path)
end
end
return build(relative_parsed)
end
end
-----------------------------------------------------------------------------
-- Breaks a path into its segments, unescaping the segments
-- Input
-- path
-- Returns
-- segment: a table with one entry per segment
-----------------------------------------------------------------------------
function parse_path(path)
local parsed = {}
path = path or ""
--path = string.gsub(path, "%s", "")
string.gsub(path, "([^/]+)", function(s) table.insert(parsed, s) end)
for i = 1, #parsed do
parsed[i] = unescape(parsed[i])
end
if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end
if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end
return parsed
end
-----------------------------------------------------------------------------
-- Builds a path component from its segments, escaping protected characters.
-- Input
-- parsed: path segments
-- unsafe: if true, segments are not protected before path is built
-- Returns
-- path: corresponding path stringing
-----------------------------------------------------------------------------
function build_path(parsed, unsafe)
local path = ""
local n = #parsed
if unsafe then
for i = 1, n - 1 do
path = path .. parsed[i]
path = path .. "/"
end
if n > 0 then
path = path .. parsed[n]
if parsed.is_directory then path = path .. "/" end
end
else
for i = 1, n - 1 do
path = path .. protect_segment(parsed[i])
path = path .. "/"
end
if n > 0 then
path = path .. protect_segment(parsed[n])
if parsed.is_directory then path = path .. "/" end
end
end
if parsed.is_absolute then path = "/" .. path end
return path
end

View File

@@ -7,18 +7,17 @@ Lua scripting for Blockland
- Install RedBlocklandLoader - Install RedBlocklandLoader
- Copy `lua5.1.dll` into your Blockland install folder, next to `Blockland.exe` - Copy `lua5.1.dll` into your Blockland install folder, next to `Blockland.exe`
- Copy `BlockLua.dll` into the `modules` folder within the Blockland folder - Copy `BlockLua.dll` into the `modules` folder within the Blockland folder
- Optional: Copy the `lualib` folder into `modules`
## Quick Reference ## Quick Reference
### From TorqueScript ### From TorqueScript
`'print('hello world')` - Execute Lua code in the console by prepending a `'` (single quote) `'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
`luaeval("code");` - Eval Lua code `luaeval("code");` - Execute Lua code
`luacall("funcName", %args);` - Call a Lua global function `luacall("funcName", %args...);` - Call a Lua global function
`luaexec("fileName");` - Execute a Lua file. Path rules are the same as executing .cs files. `luaexec("fileName");` - Execute a Lua file. Path rules are the same as executing .cs files.
`luaget("varName");` - Read a Lua global variable `luaget("varName");` - Read a Lua global variable
`luaset("varName");` - Write a Lua global variable `luaset("varName", %value);` - Write a Lua global variable
### From Lua ### From Lua
`bl.eval('code')` - Eval TorqueScript code `bl.eval('code')` - Eval TorqueScript code
@@ -26,6 +25,7 @@ Lua scripting for Blockland
`bl.varName` - Read a TorqueScript global variable `bl.varName` - Read a TorqueScript global variable
`bl['varName']` - Read a TorqueScript global variable (i.e. with special characters in the name, or from an array) `bl['varName']` - Read a TorqueScript global variable (i.e. with special characters in the name, or from an array)
`bl.set('varName', value)` - Write a TorqueScript global variable `bl.set('varName', value)` - Write a TorqueScript global variable
`bl['namespaceName::funcName'](args)` - Call a namespaced TorqueScript function
### Accessing Torque Objects from Lua ### Accessing Torque Objects from Lua
`bl.objectName` - Access a Torque object by name `bl.objectName` - Access a Torque object by name
@@ -96,7 +96,7 @@ When reading from outside ZIPs, binary files are fully supported.
`bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance `bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance
### Classes and Types ### Classes and Types
`bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'boolean', 'object', and nil (default is nil, which applies automatic conversion). `bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'boolean', 'object', 'string' (prevents automatic conversion), and nil (default, applies automatic conversion).
`bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all default functions. `bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all default functions.
`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method. `bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.
`bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes. `bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes.
@@ -179,14 +179,12 @@ TorqueScript stores no type information; all values in TorqueScript are strings.
- Tables cannot be passed and will throw an error - Tables cannot be passed and will throw an error
## I/O and Safety ## I/O and Safety
All Lua code is sandboxed, and file access is confied to the default directories in the same way TorqueScript is. All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.
BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there. BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there.
### Unsafe Mode ### Unsafe Mode
BlockLua-Unsafe.dll can be built and used in place of BlockLua.dll (see compile.bat), to remove the sandboxing of Lua code. This allows Lua code to access any file and use any library, including ffi. BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi.
Please do not publish add-ons that require unsafe mode. Please do not publish add-ons that require unsafe mode.
## Other Reference
### List of Object Types ### List of Object Types
`'all'` - Any object `'all'` - Any object
`'player'` - Players or bots `'player'` - Players or bots
@@ -196,3 +194,10 @@ Please do not publish add-ons that require unsafe mode.
`'brick'` - Bricks with raycasting enabled `'brick'` - Bricks with raycasting enabled
`'brickalways'` - All bricks including those with raycasting disabled `'brickalways'` - All bricks including those with raycasting disabled
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'` Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
## Compiling
With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory:
`g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 src/bllua`
LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/

View File

@@ -1,7 +1,7 @@
// Private - Utilities used by libbl from the Lua side // Private - Utilities used by libbl from the Lua side
package _bllua_smartEval { package smartEval {
// Allow prepending ' to console commands to eval in lua instead of TS // Allow prepending ' to console commands to eval in lua instead of TS
// Also wraps TS lines with echo(...); if they don't end in ; or } // Also wraps TS lines with echo(...); if they don't end in ; or }
function ConsoleEntry::eval() { function ConsoleEntry::eval() {
@@ -28,7 +28,7 @@ package _bllua_smartEval {
} }
} }
}; };
activatePackage(_bllua_smartEval); activatePackage(smartEval);
package _bllua_objectDeletionHook { package _bllua_objectDeletionHook {
// Hook object deletion to clean up its lua data // Hook object deletion to clean up its lua data

View File

@@ -106,20 +106,19 @@ local function valToTs(val)
error('valToTs: could not convert '..type(val), 3) error('valToTs: could not convert '..type(val), 3)
end end
end end
local fromTsForceTypes = {
['boolean'] = tsBool,
['object'] = function(val) toTsObject(val) end, -- toTsObject not defined yet
['string'] = tostring,
}
local function convertValFromTs(val, typ) local function convertValFromTs(val, typ)
if typ == 'boolean' then return fromTsForceTypes[typ](val) or
return tsBool(val)
elseif typ == 'object' then
return toTsObject(val)
else
error('valFromTs: invalid force type '..typ, 4) error('valFromTs: invalid force type '..typ, 4)
end end
end
bl._forceType = bl._forceType or {} bl._forceType = bl._forceType or {}
local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase
if type(val)~='string' then if type(val)~='string' then
error('valFromTs: expected string, got ' .. type(val), 3) error('valFromTs: expected string, got '..type(val), 3) end
end
if name then if name then
name = name:lower() name = name:lower()
if bl._forceType[name] then if bl._forceType[name] then
@@ -144,11 +143,9 @@ local function valFromTs(val, name, name2) -- todo: ensure name and name2 are al
'^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '.. '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '..
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
-- box (2 vectors) -- box (2 vectors)
if x1S then if x1S then return {
return {
vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)}, vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)},
vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } } vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} } end
end
-- string -- string
return val return val
end end
@@ -165,18 +162,15 @@ end
local function classFromForceTypeStr(name) local function classFromForceTypeStr(name)
local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
if not class then if not class then
class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end
end
return class,rest return class,rest
end end
local setForceType local setForceType
setForceType = function(ftname, typ) setForceType = function(ftname, typ)
if typ ~= 'boolean' and typ ~= 'object' and typ ~= nil then if typ~=nil and not fromTsForceTypes[typ] then
error('bl.type: can only set type to \'boolean\', \'object\', or nil', 2) error('bl.type: invalid type \''..typ..'\'', 2) end
end
if not isValidFuncNameNsArgn(ftname) then if not isValidFuncNameNsArgn(ftname) then
error('bl.type: invalid function or variable name \'' .. ftname .. '\'', 2) error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end
end
ftname = ftname:lower() ftname = ftname:lower()
@@ -207,11 +201,8 @@ local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', nam
local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end
local function tsIsFunctionNsname(nsname) local function tsIsFunctionNsname(nsname)
local ns, name = nsname:match('^([^:]+)::([^:]+)$') local ns, name = nsname:match('^([^:]+)::([^:]+)$')
if ns then if ns then return tsIsFunctionNs(ns, name)
return tsIsFunctionNs(ns, name) else return tsIsFunction(nsname) end
else
return tsIsFunction(nsname)
end
end end
function bl.isObject(obj) function bl.isObject(obj)
@@ -224,15 +215,12 @@ function bl.isObject(obj)
end end
return tsIsObject(obj) return tsIsObject(obj)
end end
function bl.isFunction(a1, a2) function bl.isFunction(a1, a2)
if type(a1)~='string' then if type(a1)~='string' then
error('bl.isFunction: argument #1: expected string', 2) error('bl.isFunction: argument #1: expected string', 2) end
end
if a2 then if a2 then
if type(a2)~='string' then if type(a2)~='string' then
error('bl.isFunction: argument #2: expected string', 2) error('bl.isFunction: argument #2: expected string', 2) end
end
return tsIsFunctionNs(a1, a2) return tsIsFunctionNs(a1, a2)
else else
return tsIsFunction(a1) return tsIsFunction(a1)
@@ -249,11 +237,9 @@ local tsClassMeta = {
bl._objectUserMetas = bl._objectUserMetas or {} bl._objectUserMetas = bl._objectUserMetas or {}
function bl.class(cname, inhname) function bl.class(cname, inhname)
if not ( type(cname)=='string' and isValidFuncName(cname) ) then if not ( type(cname)=='string' and isValidFuncName(cname) ) then
error('bl.class: argument #1: invalid class name', 2) error('bl.class: argument #1: invalid class name', 2) end
end
if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then
error('bl.class: argument #2: inherit name must be a string or nil', 2) error('bl.class: argument #2: inherit name must be a string or nil', 2) end
end
cname = cname:lower() cname = cname:lower()
local met = bl._objectUserMetas[cname] or { local met = bl._objectUserMetas[cname] or {
@@ -268,18 +254,15 @@ function bl.class(cname, inhname)
inhname = inhname:lower() inhname = inhname:lower()
local inh = bl._objectUserMetas[inhname] local inh = bl._objectUserMetas[inhname]
if not inh then if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '..
error('bl.class: argument #2: \'' .. inhname .. '\' is not the ' .. 'name of an existing class', 2) end
'name of an existing class', 2)
end
inh._children[cname] = true inh._children[cname] = true
local inhI = met._inherit local inhI = met._inherit
if inhI and inhI~=inh then if inhI and inhI~=inh then
error('bl.class: argument #2: class already exists and '.. error('bl.class: argument #2: class already exists and '..
'inherits a different parent.', 2) 'inherits a different parent.', 2) end
end
met._inherit = inh met._inherit = inh
-- apply inherited method and field types -- apply inherited method and field types
@@ -291,7 +274,6 @@ function bl.class(cname, inhname)
end end
end end
end end
local function objectInheritedMetas(name) local function objectInheritedMetas(name)
local inh = bl._objectUserMetas[name:lower()] local inh = bl._objectUserMetas[name:lower()]
return function() return function()
@@ -306,25 +288,25 @@ local tsObjectMeta = {
-- Return torque member function or value -- Return torque member function or value
__index = function(t, name) __index = function(t, name)
if rawget(t,'_deleted') then if rawget(t,'_deleted') then
error('ts object index: object no longer exists', 2) error('ts object index: object no longer exists', 2) end
end
if type(name)~='string' and type(name)~='number' then if type(name)~='string' and type(name)~='number' then
error('ts object index: index must be a string or number', 2) error('ts object index: index must be a string or number', 2) end
end
if getmetatable(t)[name] then if getmetatable(t)[name] then
return getmetatable(t)[name] return getmetatable(t)[name]
elseif type(name)=='number' then elseif type(name)=='number' then
if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then
error('ts object __index: index is number, but object does not have '.. error('ts object __index: index is number, but object does not have '..
'getObject method', 2) 'getObject method', 2) end
end
return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject',
tostring(name))) tostring(name)))
else else
for inh in objectInheritedMetas(rawget(t,'_tsClassName')) do for inh in objectInheritedMetas(rawget(t,'_tsClassName')) do
if inh[name] then return inh[name] end if inh[name] then return inh[name] end
end end
if tsIsFunctionNs(rawget(t, '_tsNamespace'), name) then if
tsIsFunctionNs(rawget(t,'_tsNamespace'), name) or
tsIsFunctionNs(rawget(t,'_tsName'), name)
then
return function(t, ...) return function(t, ...)
local args = {...} local args = {...}
local argsS = arglistToTs(args) local argsS = arglistToTs(args)
@@ -346,11 +328,9 @@ local tsObjectMeta = {
-- Use :set() to set Torque data -- Use :set() to set Torque data
__newindex = function(t, name, val) __newindex = function(t, name, val)
if rawget(t,'_deleted') then if rawget(t,'_deleted') then
error('ts object newindex: object no longer exists', 2) error('ts object newindex: object no longer exists', 2) end
end
if type(name)~='string' then if type(name)~='string' then
error('ts object newindex: index must be a string', 2) error('ts object newindex: index must be a string', 2) end
end
rawset(t, name, val) rawset(t, name, val)
-- create strong reference since it's now storing lua data -- create strong reference since it's now storing lua data
bl._objectsStrong[rawget(t,'_tsObjectId')] = t bl._objectsStrong[rawget(t,'_tsObjectId')] = t
@@ -359,14 +339,11 @@ local tsObjectMeta = {
-- Use to set torque data -- Use to set torque data
set = function(t, name, val) set = function(t, name, val)
if t==nil or type(t)~='table' or not t._tsObjectId then if t==nil or type(t)~='table' or not t._tsObjectId then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
if type(name)~='string' then if type(name)~='string' then
error('ts object :set(): index must be a string', 2) error('ts object :set(): index must be a string', 2) end
end
_bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) _bllua_ts.setfield(t._tsObjectId, name, valToTs(val))
end, end,
-- __tostring: Called when printing -- __tostring: Called when printing
@@ -379,11 +356,9 @@ local tsObjectMeta = {
-- If the object has a getCount method, return its count -- If the object has a getCount method, return its count
__len = function(t) __len = function(t)
if t._deleted then if t._deleted then
error('ts object __len: object no longer exists', 2) error('ts object __len: object no longer exists', 2) end
end
if not tsIsFunctionNs(t._tsNamespace, 'getCount') then if not tsIsFunctionNs(t._tsNamespace, 'getCount') then
error('ts object __len: object has no getCount method', 2) error('ts object __len: object has no getCount method', 2) end
end
return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
end, end,
-- object:members() -- object:members()
@@ -391,17 +366,14 @@ local tsObjectMeta = {
-- for index, object in group:members() do ... end -- for index, object in group:members() do ... end
members = function(t) members = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
if not ( if not (
tsIsFunctionNs(t._tsNamespace, 'getCount' ) and tsIsFunctionNs(t._tsNamespace, 'getCount' ) and
tsIsFunctionNs(t._tsNamespace, 'getObject')) then tsIsFunctionNs(t._tsNamespace, 'getObject')) then
error('ts object :members() - '.. error('ts object :members() - '..
'Object does not have getCount and getObject methods', 2) 'Object does not have getCount and getObject methods', 2) end
end
local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
local idx = 0 local idx = 0
return function() return function()
@@ -418,29 +390,23 @@ local tsObjectMeta = {
-- Wrap some Torque functions for performance and error checking -- Wrap some Torque functions for performance and error checking
getName = function(t) getName = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
return t._tsName return t._tsName
end, end,
getId = function(t) getId = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
return tonumber(t._tsObjectId) return tonumber(t._tsObjectId)
end, end,
getType = function(t) getType = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')]
end, end,
---- Schedule method for objects ---- Schedule method for objects
@@ -462,11 +428,9 @@ local tsObjectMeta = {
--end, --end,
exists = function(t) exists = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
return false return false end
end
return tsIsObject(t._tsObjectId) return tsIsObject(t._tsObjectId)
end, end,
} }
@@ -496,15 +460,13 @@ end
-- Return a Torque object for the object ID string, or create one if none exists -- Return a Torque object for the object ID string, or create one if none exists
toTsObject = function(idiS) toTsObject = function(idiS)
if type(idiS)~='string' then if type(idiS)~='string' then
error('toTsObject: input must be a string', 2) error('toTsObject: input must be a string', 2) end
end
idiS = idiS:lower() idiS = idiS:lower()
if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end
if not tsBool(_bllua_ts.call('isObject', idiS)) then if not tsBool(_bllua_ts.call('isObject', idiS)) then
--error('toTsObject: object \''..idiS..'\' does not exist', 2) end --error('toTsObject: object \''..idiS..'\' does not exist', 2) end
return nil return nil end
end
local className = _bllua_ts.callobj(idiS, 'getClassName') local className = _bllua_ts.callobj(idiS, 'getClassName')
local obj = { local obj = {
@@ -520,6 +482,31 @@ toTsObject = function(idiS)
return obj return obj
end end
-- Allow bl['namespaced::function']()
local function safeNamespaceName(name)
return tostring(name:gsub(':', '_'))
end
local nscallArgStr = '%a,%b,%c,%d,%e,%f,%g,%h'
bl._cachedNamespaceCalls = {}
local function tsNamespacedCallTfname(name)
local tfname = bl._cachedNamespaceCalls[name]
if not tfname then
tfname = '_bllua_nscall_'..safeNamespaceName(name)
local tfcode = 'function '..tfname..'('..nscallArgStr..'){'..
name..'('..nscallArgStr..');}'
_bllua_ts.eval(tfcode)
bl._cachedNamespaceCalls[name] = tfname
end
return tfname
end
local function tsCallGen(name)
return function(...)
local args = {...}
local argsS = arglistToTs(args)
return valFromTs(_bllua_ts.call(name, unpack(argsS)), name)
end
end
-- Metatable for the global bl library -- Metatable for the global bl library
-- Allows accessing Torque objects, variables, and functions by indexing it -- Allows accessing Torque objects, variables, and functions by indexing it
local tsMeta = { local tsMeta = {
@@ -540,16 +527,12 @@ local tsMeta = {
local ns, rest = name:match('^([^:]+)::(.+)$') local ns, rest = name:match('^([^:]+)::(.+)$')
if not ns then error('ts index: invalid name \''..name..'\'', 2) end if not ns then error('ts index: invalid name \''..name..'\'', 2) end
if not rest:find('::') and tsIsFunctionNs(ns, rest) then if not rest:find('::') and tsIsFunctionNs(ns, rest) then
error('ts index: can\'t call a namespaced function from lua', 2) return tsCallGen(tsNamespacedCallTfname(name))
else else
return valFromTs(_bllua_ts.getvar(name), name) return valFromTs(_bllua_ts.getvar(name), name)
end end
elseif tsIsFunction(name) then elseif tsIsFunction(name) then
return function(...) return tsCallGen(name)
local args = { ... }
local argsS = arglistToTs(args)
return valFromTs(_bllua_ts.call(name, unpack(argsS)), name)
end
elseif tsIsObject(name) then elseif tsIsObject(name) then
return toTsObject(name) return toTsObject(name)
else else
@@ -571,15 +554,12 @@ function bl.call(func, ...)
local argsS = arglistToTs(args) local argsS = arglistToTs(args)
return _bllua_ts.call(func, unpack(argsS)) return _bllua_ts.call(func, unpack(argsS))
end end
function bl.eval(code) function bl.eval(code)
return valFromTs(_bllua_ts.eval(code)) return valFromTs(_bllua_ts.eval(code))
end end
function bl.exec(file) function bl.exec(file)
return valFromTs(_bllua_ts.call('exec', file)) return valFromTs(_bllua_ts.call('exec', file))
end end
function bl.boolean(val) function bl.boolean(val)
return val~=nil and return val~=nil and
val~=false and val~=false and
@@ -587,29 +567,25 @@ function bl.boolean(val)
--val~='0' and --val~='0' and
val~=0 val~=0
end end
function bl.object(id) function bl.object(id)
if type(id)=='table' and id._tsObjectId then if type(id)=='table' and id._tsObjectId then
return id return id
elseif type(id)=='string' or type(id)=='number' then elseif type(id)=='string' or type(id)=='number' then
return toTsObject(tostring(id)) return toTsObject(tostring(id))
else else
error('bl.toobject: id must be a ts object, number, or string', 2) error('bl.object: id must be a ts object, number, or string', 2)
end end
end end
function bl.array(name, ...) function bl.array(name, ...)
local rest = {...} local rest = {...}
return name..table.concat(rest, '_') return name..table.concat(rest, '_')
end end
function _bllua_call(fnameS, ...) function _bllua_call(fnameS, ...)
local args = arglistFromTs(fnameS:lower(), { ... }) local args = arglistFromTs('lua:'..fnameS:lower(), {...}) -- todo: allow this though bl.type
if not _G[fnameS] then if not _G[fnameS] then
error('luacall: no global lua function named \'' .. fnameS .. '\'') error('luacall: no global lua function named \''..fnameS..'\'') end
end
-- todo: library fields and object methods -- todo: library fields and object methods
local res = _G[fnameS](args) local res = _G[fnameS](unpack(args))
return valToTs(res) return valToTs(res)
end end
@@ -639,7 +615,6 @@ function bl.schedule(time, cb, ...)
bl._scheduleTable[id] = sch bl._scheduleTable[id] = sch
return sch return sch
end end
function _bllua_schedule_callback(idS) function _bllua_schedule_callback(idS)
local id = tonumber(idS) or local id = tonumber(idS) or
error('_bllua_schedule_callback: invalid id: '..tostring(idS)) error('_bllua_schedule_callback: invalid id: '..tostring(idS))
@@ -658,11 +633,9 @@ function _bllua_process_cmd(cmdS, ...)
if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end
func(unpack(args)) --pcall(func, unpack(args)) func(unpack(args)) --pcall(func, unpack(args))
end end
local function addCmd(cmd, func) local function addCmd(cmd, func)
if not isValidFuncName(cmd) then if not isValidFuncName(cmd) then
error('addCmd: invalid function name \'' .. tostring(cmd) .. '\'') error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
end
bl._cmds[cmd] = func bl._cmds[cmd] = func
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p'
_bllua_ts.eval('function '..cmd..'('..arglist..'){'.. _bllua_ts.eval('function '..cmd..'('..arglist..'){'..
@@ -673,7 +646,6 @@ function bl.addServerCmd(name, func)
addCmd('servercmd'..name, func) addCmd('servercmd'..name, func)
bl._forceType['servercmd'..name..':1'] = 'object' bl._forceType['servercmd'..name..':1'] = 'object'
end end
function bl.addClientCmd(name, func) function bl.addClientCmd(name, func)
name = name:lower() name = name:lower()
addCmd('clientcmd'..name, func) addCmd('clientcmd'..name, func)
@@ -685,13 +657,11 @@ function bl.commandToServer(cmd, ...)
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...}))) unpack(arglistToTs({...})))
end end
function bl.commandToClient(cmd, ...) function bl.commandToClient(cmd, ...)
_bllua_ts.call('commandToClient', _bllua_ts.call('commandToClient',
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...}))) unpack(arglistToTs({...})))
end end
function bl.commandToAll(cmd, ...) function bl.commandToAll(cmd, ...)
_bllua_ts.call('commandToAll', _bllua_ts.call('commandToAll',
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
@@ -719,16 +689,14 @@ local function deactivatePackage(pkg)
end end
local hookNargs = 8 local hookNargs = 8
local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h' local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h'
local hookArglistGlobal = local hookArglistGlobal = '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8'
'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8'
bl._hooks = bl._hooks or {} bl._hooks = bl._hooks or {}
function _bllua_process_hook_before(pkgS, nameS, ...) function _bllua_process_hook_before(pkgS, nameS, ...)
local args = arglistFromTs(nameS, {...}) local args = arglistFromTs(nameS, {...})
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].before bl._hooks[pkgS][nameS].before
if not func then if not func then
error('_bllua_process_hook_before: no hook for ' .. pkgs .. ':' .. nameS) error('_bllua_process_hook_before: no hook for '..pkgs..':'..nameS) end
end
_bllua_ts.setvar('_bllua_hook_abort', '0') _bllua_ts.setvar('_bllua_hook_abort', '0')
func(args) --pcall(func, args) func(args) --pcall(func, args)
if args._return then if args._return then
@@ -739,7 +707,6 @@ function _bllua_process_hook_before(pkgS, nameS, ...)
_bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i])) _bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i]))
end end
end end
function _bllua_process_hook_after(pkgS, nameS, resultS, ...) function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
nameS = nameS:lower() nameS = nameS:lower()
local args = arglistFromTs(nameS, {...}) local args = arglistFromTs(nameS, {...})
@@ -747,12 +714,10 @@ function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].after bl._hooks[pkgS][nameS].after
if not func then if not func then
error('_bllua_process_hook_after: no hook for ' .. pkgs .. ':' .. nameS) error('_bllua_process_hook_after: no hook for '..pkgs..':'..nameS) end
end
func(args) --pcall(func, args) func(args) --pcall(func, args)
return valToTs(args._return) return valToTs(args._return)
end end
local function updateHook(pkg, name, hk) local function updateHook(pkg, name, hk)
local beforeCode = hk.before and local beforeCode = hk.before and
('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name.. ('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name..
@@ -778,17 +743,13 @@ local function updateHook(pkg, name, hk)
end end
function bl.hook(pkg, name, time, func) function bl.hook(pkg, name, time, func)
if not isValidFuncName(pkg) then if not isValidFuncName(pkg) then
error('bl.hook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
end
if not isValidFuncNameNs(name) then if not isValidFuncNameNs(name) then
error('bl.hook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
end
if time~='before' and time~='after' then if time~='before' and time~='after' then
error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end
end
if type(func)~='function' then if type(func)~='function' then
error('bl.hook: argument #4: expected a function', 2) error('bl.hook: argument #4: expected a function', 2) end
end
bl._hooks[pkg] = bl._hooks[pkg] or {} bl._hooks[pkg] = bl._hooks[pkg] or {}
bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} bl._hooks[pkg][name] = bl._hooks[pkg][name] or {}
@@ -797,20 +758,16 @@ function bl.hook(pkg, name, time, func)
updateHook(pkg, name, bl._hooks[pkg][name]) updateHook(pkg, name, bl._hooks[pkg][name])
activatePackage(pkg) activatePackage(pkg)
end end
local function tableEmpty(t) local function tableEmpty(t)
return next(t)~=nil return next(t)~=nil
end end
function bl.unhook(pkg, name, time) function bl.unhook(pkg, name, time)
if not isValidFuncName(pkg) then if not isValidFuncName(pkg) then
error('bl.unhook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
end
if not isValidFuncNameNs(name) then if not isValidFuncNameNs(name) then
error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
end
if time~='before' and time~='after' then if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end
end
if not name then if not name then
if bl._hooks[pkg] then if bl._hooks[pkg] then
@@ -834,8 +791,7 @@ function bl.unhook(pkg, name, time)
updateHook(pkg, name, {}) updateHook(pkg, name, {})
else else
if time~='before' and time~='after' then if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end
end
bl._hooks[pkg][name][time] = nil bl._hooks[pkg][name][time] = nil
if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil bl._hooks[pkg] = nil
@@ -855,16 +811,14 @@ end
-- Container search/raycast -- Container search/raycast
local function vecToTs(v) local function vecToTs(v)
if not isTsVector(v) then if not isTsVector(v) then
error('vecToTs: argument is not a vector', 3) error('vecToTs: argument is not a vector', 3) end
end
return table.concat(v, ' ') return table.concat(v, ' ')
end end
local function maskToTs(mask) local function maskToTs(mask)
if type(mask)=='string' then if type(mask)=='string' then
local val = tsTypesByName[mask:lower()] local val = tsTypesByName[mask:lower()]
if not val then if not val then
error('maskToTs: invalid mask \'' .. mask .. '\'', 3) error('maskToTs: invalid mask \''..mask..'\'', 3) end
end
return tostring(val) return tostring(val)
elseif type(mask)=='table' then elseif type(mask)=='table' then
local tval = 0 local tval = 0
@@ -874,8 +828,7 @@ local function maskToTs(mask)
local val = tsTypesByName[v:lower()] local val = tsTypesByName[v:lower()]
if not val then if not val then
error('maskToTs: invalid mask \''..v.. error('maskToTs: invalid mask \''..v..
'\' at index ' .. i .. ' in mask list', 3) '\' at index '..i..' in mask list', 3) end
end
tval = tval + val tval = tval + val
seen[v] = true seen[v] = true
end end
@@ -917,7 +870,6 @@ function bl.raycast(start, stop, mask, ignores)
return hit, pos, norm return hit, pos, norm
end end
end end
local function tsContainerSearchIterator() local function tsContainerSearchIterator()
local retS = _bllua_ts.call('containerSearchNext') local retS = _bllua_ts.call('containerSearchNext')
if retS=='0' then if retS=='0' then
@@ -934,12 +886,10 @@ function bl.boxSearch(pos, size, mask)
_bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS)
return tsContainerSearchIterator return tsContainerSearchIterator
end end
function bl.radiusSearch(pos, radius, mask) function bl.radiusSearch(pos, radius, mask)
local posS = vecToTs(pos) local posS = vecToTs(pos)
if type(radius)~='number' then if type(radius)~='number' then
error('bl.radiusSearch: argument #2: radius must be a number', 2) error('bl.radiusSearch: argument #2: radius must be a number', 2) end
end
local radiusS = tostring(radius) local radiusS = tostring(radius)
local maskS = maskToTs(mask) local maskS = maskToTs(mask)
@@ -974,12 +924,13 @@ end
-- bl.new and bl.datablock -- bl.new and bl.datablock
local function createTsObj(keyword, class, name, inherit, props) local function createTsObj(keyword, class, name, inherit, props)
local propsT = {} local propsT = {}
if props then
for k,v in pairs(props) do for k,v in pairs(props) do
if not isValidFuncName(k) then if not isValidFuncName(k) then
error('bl.new/bl.datablock: invalid property name \'' .. k .. '\'') error('bl.new/bl.datablock: invalid property name \''..k..'\'') end
end
table.insert(propsT, k..'="'..valToTs(v)..'";') table.insert(propsT, k..'="'..valToTs(v)..'";')
end end
end
local objS = _bllua_ts.eval( local objS = _bllua_ts.eval(
'return '..keyword..' '..class..'('.. 'return '..keyword..' '..class..'('..
@@ -987,8 +938,7 @@ local function createTsObj(keyword, class, name, inherit, props)
table.concat(propsT)..'};') table.concat(propsT)..'};')
local obj = toTsObject(objS) local obj = toTsObject(objS)
if not obj then if not obj then
error('bl.new/bl.datablock: failed to create object', 3) error('bl.new/bl.datablock: failed to create object', 3) end
end
return obj return obj
end end
@@ -1013,15 +963,13 @@ local function parseTsDecl(decl)
(inherit==nil or isValidFuncName(inherit)) ) then (inherit==nil or isValidFuncName(inherit)) ) then
error('bl.new/bl.datablock: invalid decl \''..decl..'\'\n'.. error('bl.new/bl.datablock: invalid decl \''..decl..'\'\n'..
'must be of the format: \'className\', \'className name\', '.. 'must be of the format: \'className\', \'className name\', '..
'\'className :inherit\', or \'className name:inherit\'', 3) '\'className :inherit\', or \'className name:inherit\'', 3) end
end
return class, name, inherit return class, name, inherit
end end
function bl.new(decl, props) function bl.new(decl, props)
local class, name, inherit = parseTsDecl(decl) local class, name, inherit = parseTsDecl(decl)
return createTsObj('new', class, name, inherit, props) return createTsObj('new', class, name, inherit, props)
end end
function bl.datablock(decl, props) function bl.datablock(decl, props)
local class, name, inherit = parseTsDecl(decl) local class, name, inherit = parseTsDecl(decl)
if not name then error('bl.datablock: must specify a name', 2) end if not name then error('bl.datablock: must specify a name', 2) end