This commit is contained in:
Redo
2025-10-01 16:26:18 -07:00
commit 36ba54b248
43 changed files with 9810 additions and 0 deletions

15
.compileInject.bat Normal file
View File

@@ -0,0 +1,15 @@
@for /F "tokens=2" %%K in ('
tasklist /FI "ImageName eq Blockland.exe" /FO LIST ^| findstr /B "PID:"
') do (
DllRemover32 %%K BlockLua.dll
)
@call %~dp0compile.bat || exit
@for /F "tokens=2" %%K in ('
tasklist /FI "ImageName eq Blockland.exe" /FO LIST ^| findstr /B "PID:"
') do (
RemoteDllInjector32 %%K %~dp0BlockLua.dll
)
@pause

BIN
BlockLua-Unsafe.dll Normal file

Binary file not shown.

BIN
BlockLua.dll Normal file

Binary file not shown.

13
compile.bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
cd /d %~dp0
set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 -static-libgcc -static-libstdc++
echo on
g++ src/bllua4.cpp %buildargs% -o BlockLua.dll && g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o BlockLua-Unsafe.dll
@echo off
rem objdump -d BlockLua.dll > BlockLua.dll.dump.txt
rem objdump -d BlockLua-Unsafe.dll > BlockLua-Unsafe.dll.dump.txt
pause

161
inc/lua/lauxlib.h Normal file
View File

@@ -0,0 +1,161 @@
/*
** $Id: lauxlib.h,v 1.88.1.1 2007/12/27 13:02:25 roberto Exp $
** Auxiliary functions for building Lua libraries
** See Copyright Notice in lua.h
*/
#ifndef lauxlib_h
#define lauxlib_h
#include <stddef.h>
#include <stdio.h>
#include "lua.h"
/* extra error code for `luaL_load' */
#define LUA_ERRFILE (LUA_ERRERR+1)
typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;
LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname,
const luaL_Reg *l, int nup);
LUALIB_API void (luaL_register) (lua_State *L, const char *libname,
const luaL_Reg *l);
LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e);
LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e);
LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname);
LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg);
LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg,
size_t *l);
LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg,
const char *def, size_t *l);
LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg);
LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg);
LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg,
lua_Integer def);
LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg);
LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t);
LUALIB_API void (luaL_checkany) (lua_State *L, int narg);
LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname);
LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname);
LUALIB_API void (luaL_where) (lua_State *L, int lvl);
LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...);
LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def,
const char *const lst[]);
/* pre-defined references */
#define LUA_NOREF (-2)
#define LUA_REFNIL (-1)
LUALIB_API int (luaL_ref) (lua_State *L, int t);
LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref);
LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename);
LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz,
const char *name);
LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s);
LUALIB_API lua_State *(luaL_newstate) (void);
LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p,
const char *r);
LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx,
const char *fname, int szhint);
/* From Lua 5.2. */
LUALIB_API int luaL_fileresult(lua_State *L, int stat, const char *fname);
LUALIB_API int luaL_execresult(lua_State *L, int stat);
LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename,
const char *mode);
LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz,
const char *name, const char *mode);
LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg,
int level);
LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname,
int sizehint);
LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname);
LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname);
/*
** ===============================================================
** some useful macros
** ===============================================================
*/
#define luaL_argcheck(L, cond,numarg,extramsg) \
((void)((cond) || luaL_argerror(L, (numarg), (extramsg))))
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
#define luaL_dofile(L, fn) \
(luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_dostring(L, s) \
(luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
/* From Lua 5.2. */
#define luaL_newlibtable(L, l) \
lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
#define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_setfuncs(L, l, 0))
/*
** {======================================================
** Generic Buffer manipulation
** =======================================================
*/
typedef struct luaL_Buffer {
char *p; /* current position in buffer */
int lvl; /* number of strings in the stack (level) */
lua_State *L;
char buffer[LUAL_BUFFERSIZE];
} luaL_Buffer;
#define luaL_addchar(B,c) \
((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \
(*(B)->p++ = (char)(c)))
/* compatibility only */
#define luaL_putchar(B,c) luaL_addchar(B,c)
#define luaL_addsize(B,n) ((B)->p += (n))
LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B);
LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B);
LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s);
LUALIB_API void (luaL_addvalue) (luaL_Buffer *B);
LUALIB_API void (luaL_pushresult) (luaL_Buffer *B);
/* }====================================================== */
#endif

402
inc/lua/lua.h Normal file
View File

@@ -0,0 +1,402 @@
/*
** $Id: lua.h,v 1.218.1.5 2008/08/06 13:30:12 roberto Exp $
** Lua - An Extensible Extension Language
** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
** See Copyright Notice at the end of this file
*/
#ifndef lua_h
#define lua_h
#include <stdarg.h>
#include <stddef.h>
#include "luaconf.h"
#define LUA_VERSION "Lua 5.1"
#define LUA_RELEASE "Lua 5.1.4"
#define LUA_VERSION_NUM 501
#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio"
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes"
/* mark for precompiled code (`<esc>Lua') */
#define LUA_SIGNATURE "\033Lua"
/* option for multiple returns in `lua_pcall' and `lua_call' */
#define LUA_MULTRET (-1)
/*
** pseudo-indices
*/
#define LUA_REGISTRYINDEX (-10000)
#define LUA_ENVIRONINDEX (-10001)
#define LUA_GLOBALSINDEX (-10002)
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i))
/* thread status */
#define LUA_OK 0
#define LUA_YIELD 1
#define LUA_ERRRUN 2
#define LUA_ERRSYNTAX 3
#define LUA_ERRMEM 4
#define LUA_ERRERR 5
typedef struct lua_State lua_State;
typedef int (*lua_CFunction) (lua_State *L);
/*
** functions that read/write blocks when loading/dumping Lua chunks
*/
typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);
typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);
/*
** prototype for memory-allocation functions
*/
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
/*
** basic types
*/
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
/* minimum Lua stack available to a C function */
#define LUA_MINSTACK 20
/*
** generic extra include file
*/
#if defined(LUA_USER_H)
#include LUA_USER_H
#endif
/* type of numbers in Lua */
typedef LUA_NUMBER lua_Number;
/* type for integer functions */
typedef LUA_INTEGER lua_Integer;
/*
** state manipulation
*/
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
LUA_API void (lua_close) (lua_State *L);
LUA_API lua_State *(lua_newthread) (lua_State *L);
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
/*
** basic stack manipulation
*/
LUA_API int (lua_gettop) (lua_State *L);
LUA_API void (lua_settop) (lua_State *L, int idx);
LUA_API void (lua_pushvalue) (lua_State *L, int idx);
LUA_API void (lua_remove) (lua_State *L, int idx);
LUA_API void (lua_insert) (lua_State *L, int idx);
LUA_API void (lua_replace) (lua_State *L, int idx);
LUA_API int (lua_checkstack) (lua_State *L, int sz);
LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n);
/*
** access functions (stack -> C)
*/
LUA_API int (lua_isnumber) (lua_State *L, int idx);
LUA_API int (lua_isstring) (lua_State *L, int idx);
LUA_API int (lua_iscfunction) (lua_State *L, int idx);
LUA_API int (lua_isuserdata) (lua_State *L, int idx);
LUA_API int (lua_type) (lua_State *L, int idx);
LUA_API const char *(lua_typename) (lua_State *L, int tp);
LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2);
LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2);
LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2);
LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx);
LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx);
LUA_API int (lua_toboolean) (lua_State *L, int idx);
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t (lua_objlen) (lua_State *L, int idx);
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
LUA_API const void *(lua_topointer) (lua_State *L, int idx);
/*
** push functions (C -> stack)
*/
LUA_API void (lua_pushnil) (lua_State *L);
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);
LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l);
LUA_API void (lua_pushstring) (lua_State *L, const char *s);
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
va_list argp);
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
LUA_API void (lua_pushboolean) (lua_State *L, int b);
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
LUA_API int (lua_pushthread) (lua_State *L);
/*
** get functions (Lua -> stack)
*/
LUA_API void (lua_gettable) (lua_State *L, int idx);
LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k);
LUA_API void (lua_rawget) (lua_State *L, int idx);
LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n);
LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec);
LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz);
LUA_API int (lua_getmetatable) (lua_State *L, int objindex);
LUA_API void (lua_getfenv) (lua_State *L, int idx);
/*
** set functions (stack -> Lua)
*/
LUA_API void (lua_settable) (lua_State *L, int idx);
LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k);
LUA_API void (lua_rawset) (lua_State *L, int idx);
LUA_API void (lua_rawseti) (lua_State *L, int idx, int n);
LUA_API int (lua_setmetatable) (lua_State *L, int objindex);
LUA_API int (lua_setfenv) (lua_State *L, int idx);
/*
** `load' and `call' functions (load and run Lua code)
*/
LUA_API void (lua_call) (lua_State *L, int nargs, int nresults);
LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc);
LUA_API int (lua_cpcall) (lua_State *L, lua_CFunction func, void *ud);
LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt,
const char *chunkname);
LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data);
/*
** coroutine functions
*/
LUA_API int (lua_yield) (lua_State *L, int nresults);
LUA_API int (lua_resume) (lua_State *L, int narg);
LUA_API int (lua_status) (lua_State *L);
/*
** garbage-collection function and options
*/
#define LUA_GCSTOP 0
#define LUA_GCRESTART 1
#define LUA_GCCOLLECT 2
#define LUA_GCCOUNT 3
#define LUA_GCCOUNTB 4
#define LUA_GCSTEP 5
#define LUA_GCSETPAUSE 6
#define LUA_GCSETSTEPMUL 7
#define LUA_GCISRUNNING 9
LUA_API int (lua_gc) (lua_State *L, int what, int data);
/*
** miscellaneous functions
*/
LUA_API int (lua_error) (lua_State *L);
LUA_API int (lua_next) (lua_State *L, int idx);
LUA_API void (lua_concat) (lua_State *L, int n);
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud);
/*
** ===============================================================
** some useful macros
** ===============================================================
*/
#define lua_pop(L,n) lua_settop(L, -(n)-1)
#define lua_newtable(L) lua_createtable(L, 0, 0)
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
#define lua_strlen(L,i) lua_objlen(L, (i))
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)
#define lua_pushliteral(L, s) \
lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)
#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s))
#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
/*
** compatibility macros and functions
*/
#define lua_open() luaL_newstate()
#define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX)
#define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0)
#define lua_Chunkreader lua_Reader
#define lua_Chunkwriter lua_Writer
/* hack */
LUA_API void lua_setlevel (lua_State *from, lua_State *to);
/*
** {======================================================================
** Debug API
** =======================================================================
*/
/*
** Event codes
*/
#define LUA_HOOKCALL 0
#define LUA_HOOKRET 1
#define LUA_HOOKLINE 2
#define LUA_HOOKCOUNT 3
#define LUA_HOOKTAILRET 4
/*
** Event masks
*/
#define LUA_MASKCALL (1 << LUA_HOOKCALL)
#define LUA_MASKRET (1 << LUA_HOOKRET)
#define LUA_MASKLINE (1 << LUA_HOOKLINE)
#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT)
typedef struct lua_Debug lua_Debug; /* activation record */
/* Functions to be called by the debuger in specific events */
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar);
LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);
LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);
LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);
LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n);
LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n);
LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count);
LUA_API lua_Hook lua_gethook (lua_State *L);
LUA_API int lua_gethookmask (lua_State *L);
LUA_API int lua_gethookcount (lua_State *L);
/* From Lua 5.2. */
LUA_API void *lua_upvalueid (lua_State *L, int idx, int n);
LUA_API void lua_upvaluejoin (lua_State *L, int idx1, int n1, int idx2, int n2);
LUA_API int lua_loadx (lua_State *L, lua_Reader reader, void *dt,
const char *chunkname, const char *mode);
LUA_API const lua_Number *lua_version (lua_State *L);
LUA_API void lua_copy (lua_State *L, int fromidx, int toidx);
LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *isnum);
LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *isnum);
/* From Lua 5.3. */
LUA_API int lua_isyieldable (lua_State *L);
struct lua_Debug {
int event;
const char *name; /* (n) */
const char *namewhat; /* (n) `global', `local', `field', `method' */
const char *what; /* (S) `Lua', `C', `main', `tail' */
const char *source; /* (S) */
int currentline; /* (l) */
int nups; /* (u) number of upvalues */
int linedefined; /* (S) */
int lastlinedefined; /* (S) */
char short_src[LUA_IDSIZE]; /* (S) */
/* private part */
int i_ci; /* active function */
};
/* }====================================================================== */
/******************************************************************************
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************/
#endif

9
inc/lua/lua.hpp Normal file
View File

@@ -0,0 +1,9 @@
// C++ wrapper for LuaJIT header files.
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "luajit.h"
}

BIN
inc/lua/lua5.1.lib Normal file

Binary file not shown.

152
inc/lua/luaconf.h Normal file
View File

@@ -0,0 +1,152 @@
/*
** Configuration header.
** Copyright (C) 2005-2017 Mike Pall. See Copyright Notice in luajit.h
*/
#ifndef luaconf_h
#define luaconf_h
#ifndef WINVER
#define WINVER 0x0501
#endif
#include <limits.h>
#include <stddef.h>
/* Default path for loading Lua and C modules with require(). */
#if defined(_WIN32)
/*
** In Windows, any exclamation mark ('!') in the path is replaced by the
** path of the directory of the executable file of the current process.
*/
#define LUA_LDIR "!\\lua\\"
#define LUA_CDIR "!\\"
#define LUA_PATH_DEFAULT \
".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;"
#define LUA_CPATH_DEFAULT \
".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll"
#else
/*
** Note to distribution maintainers: do NOT patch the following lines!
** Please read ../doc/install.html#distro and pass PREFIX=/usr instead.
*/
#ifndef LUA_MULTILIB
#define LUA_MULTILIB "lib"
#endif
#ifndef LUA_LMULTILIB
#define LUA_LMULTILIB "lib"
#endif
#define LUA_LROOT "/usr/local"
#define LUA_LUADIR "/lua/5.1/"
#define LUA_LJDIR "/luajit-2.1.0-beta3/"
#ifdef LUA_ROOT
#define LUA_JROOT LUA_ROOT
#define LUA_RLDIR LUA_ROOT "/share" LUA_LUADIR
#define LUA_RCDIR LUA_ROOT "/" LUA_MULTILIB LUA_LUADIR
#define LUA_RLPATH ";" LUA_RLDIR "?.lua;" LUA_RLDIR "?/init.lua"
#define LUA_RCPATH ";" LUA_RCDIR "?.so"
#else
#define LUA_JROOT LUA_LROOT
#define LUA_RLPATH
#define LUA_RCPATH
#endif
#define LUA_JPATH ";" LUA_JROOT "/share" LUA_LJDIR "?.lua"
#define LUA_LLDIR LUA_LROOT "/share" LUA_LUADIR
#define LUA_LCDIR LUA_LROOT "/" LUA_LMULTILIB LUA_LUADIR
#define LUA_LLPATH ";" LUA_LLDIR "?.lua;" LUA_LLDIR "?/init.lua"
#define LUA_LCPATH1 ";" LUA_LCDIR "?.so"
#define LUA_LCPATH2 ";" LUA_LCDIR "loadall.so"
#define LUA_PATH_DEFAULT "./?.lua" LUA_JPATH LUA_LLPATH LUA_RLPATH
#define LUA_CPATH_DEFAULT "./?.so" LUA_LCPATH1 LUA_RCPATH LUA_LCPATH2
#endif
/* Environment variable names for path overrides and initialization code. */
#define LUA_PATH "LUA_PATH"
#define LUA_CPATH "LUA_CPATH"
#define LUA_INIT "LUA_INIT"
/* Special file system characters. */
#if defined(_WIN32)
#define LUA_DIRSEP "\\"
#else
#define LUA_DIRSEP "/"
#endif
#define LUA_PATHSEP ";"
#define LUA_PATH_MARK "?"
#define LUA_EXECDIR "!"
#define LUA_IGMARK "-"
#define LUA_PATH_CONFIG \
LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" \
LUA_EXECDIR "\n" LUA_IGMARK "\n"
/* Quoting in error messages. */
#define LUA_QL(x) "'" x "'"
#define LUA_QS LUA_QL("%s")
/* Various tunables. */
#define LUAI_MAXSTACK 65500 /* Max. # of stack slots for a thread (<64K). */
#define LUAI_MAXCSTACK 8000 /* Max. # of stack slots for a C func (<10K). */
#define LUAI_GCPAUSE 200 /* Pause GC until memory is at 200%. */
#define LUAI_GCMUL 200 /* Run GC at 200% of allocation speed. */
#define LUA_MAXCAPTURES 32 /* Max. pattern captures. */
/* Configuration for the frontend (the luajit executable). */
#if defined(luajit_c)
#define LUA_PROGNAME "luajit" /* Fallback frontend name. */
#define LUA_PROMPT "> " /* Interactive prompt. */
#define LUA_PROMPT2 ">> " /* Continuation prompt. */
#define LUA_MAXINPUT 512 /* Max. input line length. */
#endif
/* Note: changing the following defines breaks the Lua 5.1 ABI. */
#define LUA_INTEGER ptrdiff_t
#define LUA_IDSIZE 60 /* Size of lua_Debug.short_src. */
/*
** Size of lauxlib and io.* on-stack buffers. Weird workaround to avoid using
** unreasonable amounts of stack space, but still retain ABI compatibility.
** Blame Lua for depending on BUFSIZ in the ABI, blame **** for wrecking it.
*/
#define LUAL_BUFFERSIZE (BUFSIZ > 16384 ? 8192 : BUFSIZ)
/* The following defines are here only for compatibility with luaconf.h
** from the standard Lua distribution. They must not be changed for LuaJIT.
*/
#define LUA_NUMBER_DOUBLE
#define LUA_NUMBER double
#define LUAI_UACNUMBER double
#define LUA_NUMBER_SCAN "%lf"
#define LUA_NUMBER_FMT "%.14g"
#define lua_number2str(s, n) sprintf((s), LUA_NUMBER_FMT, (n))
#define LUAI_MAXNUMBER2STR 32
#define LUA_INTFRMLEN "l"
#define LUA_INTFRM_T long
/* Linkage of public API functions. */
#if defined(LUA_BUILD_AS_DLL)
#if defined(LUA_CORE) || defined(LUA_LIB)
#define LUA_API __declspec(dllexport)
#else
#define LUA_API __declspec(dllimport)
#endif
#else
#define LUA_API extern
#endif
#define LUALIB_API LUA_API
/* Support for internal assertions. */
#if defined(LUA_USE_ASSERT) || defined(LUA_USE_APICHECK)
#include <assert.h>
#endif
#ifdef LUA_USE_ASSERT
#define lua_assert(x) assert(x)
#endif
#ifdef LUA_USE_APICHECK
#define luai_apicheck(L, o) { (void)L; assert(o); }
#else
#define luai_apicheck(L, o) { (void)L; }
#endif
#endif

79
inc/lua/luajit.h Normal file
View File

@@ -0,0 +1,79 @@
/*
** LuaJIT -- a Just-In-Time Compiler for Lua. http://luajit.org/
**
** Copyright (C) 2005-2017 Mike Pall. All rights reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
*/
#ifndef _LUAJIT_H
#define _LUAJIT_H
#include "lua.h"
#define LUAJIT_VERSION "LuaJIT 2.1.0-beta3"
#define LUAJIT_VERSION_NUM 20100 /* Version 2.1.0 = 02.01.00. */
#define LUAJIT_VERSION_SYM luaJIT_version_2_1_0_beta3
#define LUAJIT_COPYRIGHT "Copyright (C) 2005-2017 Mike Pall"
#define LUAJIT_URL "http://luajit.org/"
/* Modes for luaJIT_setmode. */
#define LUAJIT_MODE_MASK 0x00ff
enum {
LUAJIT_MODE_ENGINE, /* Set mode for whole JIT engine. */
LUAJIT_MODE_DEBUG, /* Set debug mode (idx = level). */
LUAJIT_MODE_FUNC, /* Change mode for a function. */
LUAJIT_MODE_ALLFUNC, /* Recurse into subroutine protos. */
LUAJIT_MODE_ALLSUBFUNC, /* Change only the subroutines. */
LUAJIT_MODE_TRACE, /* Flush a compiled trace. */
LUAJIT_MODE_WRAPCFUNC = 0x10, /* Set wrapper mode for C function calls. */
LUAJIT_MODE_MAX
};
/* Flags or'ed in to the mode. */
#define LUAJIT_MODE_OFF 0x0000 /* Turn feature off. */
#define LUAJIT_MODE_ON 0x0100 /* Turn feature on. */
#define LUAJIT_MODE_FLUSH 0x0200 /* Flush JIT-compiled code. */
/* LuaJIT public C API. */
/* Control the JIT engine. */
LUA_API int luaJIT_setmode(lua_State *L, int idx, int mode);
/* Low-overhead profiling API. */
typedef void (*luaJIT_profile_callback)(void *data, lua_State *L,
int samples, int vmstate);
LUA_API void luaJIT_profile_start(lua_State *L, const char *mode,
luaJIT_profile_callback cb, void *data);
LUA_API void luaJIT_profile_stop(lua_State *L);
LUA_API const char *luaJIT_profile_dumpstack(lua_State *L, const char *fmt,
int depth, size_t *len);
/* Enforce (dynamic) linker error for version mismatches. Call from main. */
LUA_API void LUAJIT_VERSION_SYM(void);
#endif

43
inc/lua/lualib.h Normal file
View File

@@ -0,0 +1,43 @@
/*
** Standard library header.
** Copyright (C) 2005-2017 Mike Pall. See Copyright Notice in luajit.h
*/
#ifndef _LUALIB_H
#define _LUALIB_H
#include "lua.h"
#define LUA_FILEHANDLE "FILE*"
#define LUA_COLIBNAME "coroutine"
#define LUA_MATHLIBNAME "math"
#define LUA_STRLIBNAME "string"
#define LUA_TABLIBNAME "table"
#define LUA_IOLIBNAME "io"
#define LUA_OSLIBNAME "os"
#define LUA_LOADLIBNAME "package"
#define LUA_DBLIBNAME "debug"
#define LUA_BITLIBNAME "bit"
#define LUA_JITLIBNAME "jit"
#define LUA_FFILIBNAME "ffi"
LUALIB_API int luaopen_base(lua_State *L);
LUALIB_API int luaopen_math(lua_State *L);
LUALIB_API int luaopen_string(lua_State *L);
LUALIB_API int luaopen_table(lua_State *L);
LUALIB_API int luaopen_io(lua_State *L);
LUALIB_API int luaopen_os(lua_State *L);
LUALIB_API int luaopen_package(lua_State *L);
LUALIB_API int luaopen_debug(lua_State *L);
LUALIB_API int luaopen_bit(lua_State *L);
LUALIB_API int luaopen_jit(lua_State *L);
LUALIB_API int luaopen_ffi(lua_State *L);
LUALIB_API void luaL_openlibs(lua_State *L);
#ifndef lua_assert
#define lua_assert(x) ((void)0)
#endif
#endif

238
inc/tsfuncs/BlFuncs.cpp Normal file
View File

@@ -0,0 +1,238 @@
//////////////////////////////////////////////////
// BlFuncs Version 1.0
// Includes
#include "BlHooks.hpp"
#include "BlFuncs.hpp"
#include <stdio.h>
// Scanned structures
ADDR tsf_mCacheSequence;
ADDR tsf_mCacheAllocator;
ADDR tsf_gIdDictionary;
ADDR tsf_gEvalState_globalVars;
BlFunctionDefIntern(tsf_BlStringTable__insert );
BlFunctionDefIntern(tsf_BlNamespace__find );
BlFunctionDefIntern(tsf_BlNamespace__createLocalEntry);
BlFunctionDefIntern(tsf_BlDataChunker__freeBlocks );
BlFunctionDefIntern(tsf_BlCon__evaluate );
BlFunctionDefIntern(tsf_BlCon__executef );
BlFunctionDefIntern(tsf_BlCon__executefSimObj );
BlFunctionDefIntern(tsf_BlCon__getVariable );
BlFunctionDefIntern(tsf_BlDictionary__addVariable );
BlFunctionDefIntern(tsf_BlSim__findObject_name );
BlFunctionDefIntern(tsf_BlStringStack__getArgBuffer );
BlFunctionDefIntern(tsf_BlSimObject__getDataField );
BlFunctionDefIntern(tsf_BlSimObject__setDataField );
BlFunctionDefIntern(tsf_BlCon__getReturnBuffer );
// C->TS Args
char* tsf_GetIntArg(signed int value) {
char* ret = tsf_BlStringStack__getArgBuffer(16);
snprintf(ret, 16, "%d", value);
return ret;
}
char* tsf_GetFloatArg(float value) {
char* ret = tsf_BlStringStack__getArgBuffer(32);
snprintf(ret, 32, "%g", value);
return ret;
}
char* tsf_GetStringArg(char* value) {
int len = strlen(value)+1;
char* ret = tsf_BlStringStack__getArgBuffer(len);
memcpy(ret, value, len);
return ret;
}
char* tsf_GetThisArg(ADDR obj) {
return tsf_GetIntArg(*(signed int *)(obj + 32));
}
// Eval
const char* tsf_Eval(const char *code) {
const char *argv[] = {nullptr, code};
return tsf_BlCon__evaluate(0, 2, argv);
}
const char* tsf_Evalf(const char *fmt, ...) {
va_list args;
char code[4096];
va_start(args, fmt);
vsnprintf(code, 4096, fmt, args);
va_end(args);
return tsf_Eval((const char*)code);
}
// Objects
ADDR tsf_FindObject(unsigned int id) {
ADDR obj = *(ADDR*)(*(ADDR*)(tsf_gIdDictionary) + 4*(id & 0xFFF));
if(!obj) return 0;
while(obj && *(unsigned int *)(obj + 32) != id) {
obj = *(ADDR*)(obj + 16);
if(!obj) return 0;
}
return obj;
}
ADDR tsf_FindObject(const char* name) {
return (ADDR)tsf_BlSim__findObject_name(name);
}
ADDR tsf_LookupNamespace(const char* ns, const char* package) {
const char* ste_package;
if(package) {
ste_package = tsf_BlStringTable__insert(package, 0);
} else {
ste_package = nullptr;
}
if(ns) {
const char* ste_namespace = tsf_BlStringTable__insert(ns, 0);
return tsf_BlNamespace__find(ste_namespace, ste_package);
} else {
return tsf_BlNamespace__find(nullptr, ste_package);
}
}
ADDR tsf_LookupNamespace(const char* ns) {
return tsf_LookupNamespace(ns, nullptr);
}
// Object Fields
const char* tsf_GetDataField(ADDR simObject, const char* slotName, const char* array) {
const char *ste_slotName;
if(slotName) {
ste_slotName = tsf_BlStringTable__insert(slotName, 0);
} else {
ste_slotName = nullptr;
}
return tsf_BlSimObject__getDataField(simObject, ste_slotName, array);
}
void tsf_SetDataField(ADDR simObject, const char* slotName, const char* array, const char* value) {
const char* ste_slotName;
if(slotName) {
ste_slotName = tsf_BlStringTable__insert(slotName, 0);
} else {
ste_slotName = nullptr;
}
tsf_BlSimObject__setDataField(simObject, ste_slotName, array, value);
}
// TS Global Variables
const char *tsf_GetVar(const char* name) {
return tsf_BlCon__getVariable(name);
}
void tsf_AddVarInternal(const char* name, signed int varType, void* data) {
tsf_BlDictionary__addVariable((ADDR *)tsf_gEvalState_globalVars, name, varType, data);
}
void tsf_AddVar(const char* name, const char** data) {
tsf_AddVarInternal(name, 10, data);
}
void tsf_AddVar(const char* name, signed int* data) {
tsf_AddVarInternal(name, 4, data);
}
void tsf_AddVar(const char* name, float* data) {
tsf_AddVarInternal(name, 8, data);
}
void tsf_AddVar(const char* name, bool* data) {
tsf_AddVarInternal(name, 6, data);
}
// TS->C Functions
ADDR tsf_AddConsoleFuncInternal(const char* pname, const char* cname, const char* fname, signed int cbtype, const char* usage, signed int mina, signed int maxa) {
const char *ste_fname = tsf_BlStringTable__insert(fname, 0);
ADDR ns = tsf_LookupNamespace(cname, pname);
ADDR ent = tsf_BlNamespace__createLocalEntry(ns, ste_fname);
*(signed int *)tsf_mCacheSequence += 1;
tsf_BlDataChunker__freeBlocks(*(ADDR *)tsf_mCacheAllocator);
*(const char**)(ent + 24) = usage ;
*(signed int* )(ent + 16) = mina ;
*(signed int* )(ent + 20) = maxa ;
*(signed int* )(ent + 12) = cbtype;
return ent;
}
void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_StringCallback sc, const char* usage, signed int mina, signed int maxa) {
ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 1, usage, mina, maxa);
*(tsf_StringCallback *)(ent + 40) = sc;
}
void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_IntCallback ic, const char* usage, signed int mina, signed int maxa) {
ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 2, usage, mina, maxa);
*(tsf_IntCallback *)(ent + 40) = ic;
}
void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_FloatCallback fc, const char* usage, signed int mina, signed int maxa) {
ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 3, usage, mina, maxa);
*(tsf_FloatCallback *)(ent + 40) = fc;
}
void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_VoidCallback vc, const char* usage, signed int mina, signed int maxa) {
ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 4, usage, mina, maxa);
*(tsf_VoidCallback *)(ent + 40) = vc;
}
void tsf_AddConsoleFunc(const char* pname, const char* cname, const char* fname, tsf_BoolCallback bc, const char* usage, signed int mina, signed int maxa) {
ADDR ent = tsf_AddConsoleFuncInternal(pname, cname, fname, 5, usage, mina, maxa);
*(tsf_BoolCallback *)(ent + 40) = bc;
}
// Initialization
bool tsf_InitInternal() {
BlScanFunctionText(tsf_BlStringTable__insert , "83 EC 0C 80 3D ? ? ? ? ?" );
BlScanFunctionText(tsf_BlNamespace__find , "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 83 EC 0C 53 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B DA 8B D1" );
BlScanFunctionText(tsf_BlNamespace__createLocalEntry, "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 83 EC 08 53 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 89 4D F0" );
BlScanFunctionText(tsf_BlDataChunker__freeBlocks , "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 51 53 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B D9 8B 33" );
BlScanFunctionText(tsf_BlCon__evaluate , "55 8B EC 6A FF 68 ? ? ? ? 64 A1 ? ? ? ? 50 56 57 A1 ? ? ? ? 33 C5 50 8D 45 F4 64 A3 ? ? ? ? 8B 75 10" );
BlScanFunctionText(tsf_BlCon__executef , "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 55 56 8B B4 24 ? ? ? ? 33 C9" );
BlScanFunctionText(tsf_BlCon__executefSimObj , "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 53 56 8B B4 24 ? ? ? ? 33 C9" );
BlScanFunctionText(tsf_BlCon__getVariable , "53 56 8B F1 57 85 F6 0F 84 ? ? ? ?" );
BlScanFunctionText(tsf_BlDictionary__addVariable , "8B 44 24 04 56 57 8B F9" );
BlScanFunctionText(tsf_BlSim__findObject_name , "57 8B F9 8A 17" );
BlScanFunctionText(tsf_BlStringStack__getArgBuffer , "55 8B EC 83 E4 F8 8B 0D ? ? ? ? A1 ? ? ? ? 56 57 8B 7D 08 8D 14 01 03 D7 3B 15 ? ? ? ? 72 2C 8B 0D" );
BlScanFunctionText(tsf_BlSimObject__getDataField , "51 53 8B D9 55 56 8B 74 24 14" );
BlScanFunctionText(tsf_BlSimObject__setDataField , "81 EC ? ? ? ? A1 ? ? ? ? 33 C4 89 84 24 ? ? ? ? 8B 84 24 ? ? ? ? 53 8B D9 89 44 24 04" );
BlScanFunctionText(tsf_BlCon__getReturnBuffer , "81 F9 ? ? ? ? 76 2B" );
ADDR BlScanText (tsf_mCacheSequenceLoc , "FF 05 ? ? ? ? B9 ? ? ? ? 8B F8 E8 ? ? ? ? 8B 44 24 1C 89 47 18 8B 44 24 14" );
ADDR BlScanText (tsf_mCacheAllocatorLoc , "89 35 ? ? ? ? C7 06 ? ? ? ? A1 ? ? ? ? 68 ? ? ? ? C7 40 ? ? ? ? ? E8 ? ? ? ? 83 C4 04 8B 4D F4 64 89 0D ? ? ? ? 59 5E 8B E5 5D C3");
ADDR BlScanText (tsf_gIdDictionaryLoc , "89 15 ? ? ? ? E8 ? ? ? ? 8B F0 89 75 F0" );
ADDR BlScanText (tsf_gEvalState_globalVarsLoc , "B9 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? 6A 0A 68 ? ? ? ? B9 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ?" );
tsf_mCacheSequence = *(ADDR*)(tsf_mCacheSequenceLoc + 2);
tsf_mCacheAllocator = *(ADDR*)(tsf_mCacheAllocatorLoc + 2);
tsf_gIdDictionary = *(ADDR*)(tsf_gIdDictionaryLoc + 2);
tsf_gEvalState_globalVars = *(ADDR*)(tsf_gEvalState_globalVarsLoc + 1);
return true;
}
bool tsf_DeinitInternal() {
return true;
}

109
inc/tsfuncs/BlFuncs.hpp Normal file
View File

@@ -0,0 +1,109 @@
//////////////////////////////////////////////////
// BlFuncs Version 1.0
#ifndef _H_BLFUNCS
#define _H_BLFUNCS
// Require BlHooks to be included before this header
#ifndef _H_BLHOOKS
#error "BlFuncs.hpp: You must include BlHooks.hpp first"
#else
typedef const char * (*tsf_StringCallback)(ADDR, signed int, const char *[]);
typedef signed int (*tsf_IntCallback )(ADDR, signed int, const char *[]);
typedef float (*tsf_FloatCallback )(ADDR, signed int, const char *[]);
typedef void (*tsf_VoidCallback )(ADDR, signed int, const char *[]);
typedef bool (*tsf_BoolCallback )(ADDR, signed int, const char *[]);
/* These functions are used for tsf_BlCon__executefSimObj.
They refer to a special buffer for the argument stack.
For tsf_BlCon__executef, you need to use your own buffers. */
char *tsf_GetIntArg(int);
char *tsf_GetFloatArg(float);
char *tsf_ScriptThis(ADDR);
const char *tsf_Eval(const char *);
const char *tsf_Evalf(const char *, ...);
ADDR tsf_FindObject(unsigned int);
ADDR tsf_FindObject(const char *);
ADDR tsf_LookupNamespace(const char *, const char *);
const char *tsf_GetDataField(ADDR, const char *, const char *);
void tsf_SetDataField(ADDR, const char *, const char *, const char *);
const char *tsf_GetVar(const char *);
void tsf_AddVarInternal(const char *, signed int, void *);
void tsf_AddVar(const char *, const char **);
void tsf_AddVar(const char *, signed int *);
void tsf_AddVar(const char *, float *);
void tsf_AddVar(const char *, bool *);
ADDR tsf_AddConsoleFuncInternal(const char *, const char *, const char *, signed int, const char *, signed int, signed int);
void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_StringCallback, const char *, signed int, signed int);
void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_IntCallback, const char *, signed int, signed int);
void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_FloatCallback, const char *, signed int, signed int);
void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_VoidCallback, const char *, signed int, signed int);
void tsf_AddConsoleFunc(const char *, const char *, const char *, tsf_BoolCallback, const char *, signed int, signed int);
bool tsf_InitInternal();
extern ADDR tsf_mCacheSequence;
extern ADDR tsf_mCacheAllocator;
extern ADDR tsf_gIdDictionary;
extern ADDR tsf_gEvalState_globalVars;
BlFunctionDefExtern(const char *, __stdcall, tsf_BlStringTable__insert, const char *, bool);
BlFunctionDefExtern(ADDR, __fastcall, tsf_BlNamespace__find, const char *, const char *);
BlFunctionDefExtern(ADDR, __thiscall, tsf_BlNamespace__createLocalEntry, ADDR, const char *);
BlFunctionDefExtern(void, __thiscall, tsf_BlDataChunker__freeBlocks, ADDR);
BlFunctionDefExtern(const char *, , tsf_BlCon__evaluate, ADDR, signed int, const char **);
BlFunctionDefExtern(const char *, , tsf_BlCon__executef, signed int, ...);
BlFunctionDefExtern(const char *, , tsf_BlCon__executefSimObj, ADDR *, signed int, ...);
BlFunctionDefExtern(const char *, __thiscall, tsf_BlCon__getVariable, const char *);
BlFunctionDefExtern(void, __thiscall, tsf_BlDictionary__addVariable, ADDR *, const char *, signed int, void *);
BlFunctionDefExtern(ADDR *, __thiscall, tsf_BlSim__findObject_name, const char *);
BlFunctionDefExtern(char *, __stdcall, tsf_BlStringStack__getArgBuffer, unsigned int);
BlFunctionDefExtern(const char *, __thiscall, tsf_BlSimObject__getDataField, ADDR, const char *, const char *);
BlFunctionDefExtern(void, __thiscall, tsf_BlSimObject__setDataField, ADDR, const char *, const char *, const char *);
BlFunctionDefExtern(char *, __fastcall, tsf_BlCon__getReturnBuffer, unsigned int);
// Function short names
#define BlEval tsf_Eval
#define BlEvalf tsf_Evalf
#define BlIntArg tsf_GetIntArg
#define BlFloatArg tsf_GetFloatArg
#define BlThisArg tsf_GetThisArg
#define BlStringArg(x) tsf_GetStringArg((char*)x)
#define BlReturnBuffer tsf_BlCon__getReturnBuffer
#define BlObject tsf_FindObject
#define BlNamespace tsf_LookupNamespace
#define BlGetField tsf_GetDataField
#define BlSetField tsf_SetDataField
#define BlGetVar tsf_GetVar
#define BlAddVar tsf_AddVar
#define BlAddFunction tsf_AddConsoleFunc
#define __22ND_ARGUMENT(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, ...) a22
#define __NUM_LIST(...) __22ND_ARGUMENT(dummy, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define BlCall(...) \
tsf_BlCon__executef(__NUM_LIST(__VA_ARGS__), __VA_ARGS__)
#define BlCallObj(obj, ...) \
tsf_BlCon__executefSimObj((ADDR*)obj, __NUM_LIST(__VA_ARGS__), __VA_ARGS__)
#define BlFuncsInit() if(!tsf_InitInternal()) { return false; }
#define BlFuncsDeinit() if(!tsf_DeinitInternal()) { return false; }
#endif
#endif

207
inc/tsfuncs/BlHooks.cpp Normal file
View File

@@ -0,0 +1,207 @@
//////////////////////////////////////////////////
// RedoBlHooks Version 3.0
// Includes
#include <Windows.h>
#include <Psapi.h>
//#include <map>
#include "BlHooks.hpp"
// Scanned structures
BlFunctionDefIntern(tsh_BlPrintf);
// Sig Scanning
ADDR ImageBase;
ADDR ImageSize;
void tsh_i_InitScanner(){
HMODULE module = GetModuleHandle(NULL);
if(module) {
MODULEINFO info;
GetModuleInformation(GetCurrentProcess(), module, &info, sizeof(MODULEINFO));
ImageBase = (ADDR)info.lpBaseOfDll;
ImageSize = info.SizeOfImage;
}
}
bool tsh_i_CompareData(BYTE *data, BYTE *pattern, char* mask){
for (; *mask; ++data, ++pattern, ++mask){
if (*mask=='x' && *data!=*pattern)
return false;
}
return (*mask)==0;
}
ADDR tsh_i_FindPattern(ADDR imageBase, ADDR imageSize, BYTE *pattern, char *mask){
for (ADDR i=imageBase; i < imageBase+imageSize; i++){
if(tsh_i_CompareData((PBYTE)i, pattern, mask)){
return i;
}
}
return 0;
}
// Convert a text-style pattern into code-style
void tsh_i_PatternTextToCode(char* text, char** opatt, char** omask) {
unsigned int len = strlen(text);
char* patt = (char*)malloc(len);
char* mask = (char*)malloc(len);
int outidx = 0;
int val = 0;
bool uk = false;
for(unsigned int i=0; i<len; i++){
char c = text[i];
if(c=='?'){
uk = true;
}else if(c>='0' && c<='9'){
val = (val<<4) + (c-'0');
}else if(c>='A' && c<='F'){
val = (val<<4) + (c-'A'+10);
}else if(c>='a' && c<='f'){
val = (val<<4) + (c-'a'+10);
}else if(c==' '){
patt[outidx] = uk ? 0 : val;
mask[outidx] = uk ? '?' : 'x';
val = 0;
uk = false;
outidx++;
}
}
patt[outidx] = uk ? 0 : val;
mask[outidx] = uk ? '?' : 'x';
outidx++;
patt[outidx] = 0;
mask[outidx] = 0;
*opatt = patt;
*omask = mask;
}
// Public functions for sig scanning
// Scan using code-style pattern
ADDR tsh_ScanCode(char* pattern, char* mask) {
return tsh_i_FindPattern(ImageBase, ImageSize-strlen(mask), (BYTE*)pattern, mask);
}
// Scan using a text-style pattern
ADDR tsh_ScanText(char* text) {
char* patt;
char* mask;
tsh_i_PatternTextToCode(text, &patt, &mask);
ADDR res = tsh_ScanCode(patt, mask);
free(patt);
free(mask);
return res;
}
// Call Patching and Hooking
// Remove protection from address
//std::map<ADDR, std::pair<ADDR, ADDR>> tsh_DeprotectedAddresses;
void tsh_DeprotectAddress(ADDR length, ADDR location) {
DWORD oldProtection;
VirtualProtect((void*)location, length, PAGE_EXECUTE_READWRITE, &oldProtection);
//tsh_DeprotectedAddresses[location] = {length, oldProtection};
}
// Patch a string of bytes by deprotecting and then overwriting
void tsh_PatchBytes(ADDR length, ADDR location, BYTE* repl) {
tsh_DeprotectAddress(length, location);
memcpy((void*)location, (void*)repl, (size_t)length);
}
void tsh_PatchByte(ADDR location, BYTE value) {
tsh_PatchBytes(location, 1, &value);
}
void tsh_ReplaceInt(ADDR addr, int rval) {
tsh_PatchBytes(4, addr, (BYTE*)(&rval));
}
int tsh_i_CallOffset(ADDR instr, ADDR func) {
return func - (instr+4);
}
void tsh_i_ReplaceCall(ADDR instr, ADDR target) {
tsh_ReplaceInt(instr, tsh_i_CallOffset(instr, target));
}
void tsh_i_PatchCopy(ADDR dest, ADDR src, unsigned int len) {
for(unsigned int i=0; i<len; i++){
tsh_PatchByte(dest+i, *((BYTE*)(src+i)));
}
}
void tsh_HookFunction(ADDR victim, ADDR detour, BYTE* origbytes) {
memcpy(origbytes, (BYTE*)victim, 6); //save old data
*(BYTE*)victim = 0xE9; //jmp rel32
*(ADDR*)(victim+1) = (detour - (victim+5)); // jump offset
*(BYTE*)(victim+5) = 0xC3; //retn
}
void tsh_UnhookFunction(ADDR victim, BYTE* origbytes){
tsh_i_PatchCopy(victim, (ADDR)origbytes, 6); //restore old data
}
int tsh_PatchAllMatchesCode(ADDR len, char* patt, char* mask, char* replace, bool debugprint){
int numpatched = 0;
for(ADDR i=ImageBase; i<ImageBase+ImageSize-len; i++){
if(tsh_i_CompareData((BYTE*)i, (BYTE*)patt, mask)){
if(debugprint) BlPrintf("RedoBlHooks: Patching call at %08x", i);
numpatched++;
tsh_DeprotectAddress(i, len);
for(ADDR c=0; c<len; c++){
tsh_PatchByte(i+c, replace[c]);
}
}
}
return numpatched;
}
int tsh_PatchAllMatchesHex(ADDR len, char* text, char* replace, bool debugprint) {
char* patt;
char* mask;
tsh_i_PatternTextToCode(text, &patt, &mask);
int res = tsh_PatchAllMatchesCode(len, patt, mask, replace, debugprint);
free(patt);
free(mask);
return res;
}
// Initialization
bool tsh_InitInternal(){
tsh_i_InitScanner();
BlScanFunctionText(tsh_BlPrintf, "8D 44 24 08 33 D2 50 FF 74 24 08 33 C9 E8 ? ? ? ? 83 C4 08 C3");
return true;
}
bool tsh_DeinitInternal() {
return true;
}

122
inc/tsfuncs/BlHooks.hpp Normal file
View File

@@ -0,0 +1,122 @@
//////////////////////////////////////////////////
// RedoBlHooks Version 3.0
#ifndef _H_BLHOOKS
#define _H_BLHOOKS
// Typedefs
typedef unsigned char BYTE;
typedef unsigned int ADDR;
// Prototypes
bool tsh_InitInternal();
bool tsh_DeinitInternal();
ADDR tsh_ScanCode(char*, char*);
ADDR tsh_ScanText(char*);
void tsh_HookFunction(ADDR, ADDR, BYTE*);
void tsh_UnhookFunction(ADDR, BYTE*);
int tsh_PatchAllMatches(unsigned int, char*, char*, char*, bool);
void tsh_PatchByte(ADDR, BYTE);
void tsh_PatchBytes(unsigned int, ADDR, BYTE*);
void tsh_PatchInt(ADDR, int);
// Debug print settings
#ifndef TSH_NO_DEBUG_PRINT
#define tsh_DEBUGPRINT false
#else
#define tsh_DEBUGPRINT true
#endif
// Function short names
// Use in code when the def is not shared in a header
#define BlFunctionDef(returnType, convention, name, ...) \
typedef returnType (convention *tsh_##name##FnT)(__VA_ARGS__); \
tsh_##name##FnT name;
// Use in header for shared function defs when a BlFunctionDefIntern exists in code
#define BlFunctionDefExtern(returnType, convention, name, ...) \
typedef returnType (convention *tsh_##name##FnT)(__VA_ARGS__); \
extern tsh_##name##FnT name;
// Use in code for shared function defs when a BlFunctionDefExtern exists in header
#define BlFunctionDefIntern(name) \
tsh_##name##FnT name;
// Scan for and assign the pattern to the variable, or err and return if not found
#define BlScanFunctionCode(target, patt, mask) \
target = (tsh_##target##FnT)tsh_ScanCode((char*)patt, (char*)mask); \
if(!target){ \
BlPrintf("RedoBlHooks | Cannot find function "#target"!"); \
return false; \
}else{ \
if(tsh_DEBUGPRINT) BlPrintf("RedoBlHooks | Found function "#target" at %08x", (int)target); \
}
#define BlScanFunctionText(target, text) \
target = (tsh_##target##FnT)tsh_ScanText((char*)text); \
if(!target){ \
BlPrintf("RedoBlHooks | Cannot find function "#target"!"); \
return false; \
}else{ \
if(tsh_DEBUGPRINT) BlPrintf("RedoBlHooks | Found function "#target" at %08x", (int)target); \
}
#define BlScanCode(target, patt, mask) \
target = tsh_ScanCode((char*)patt, (char*)mask); \
if(!target){ \
BlPrintf("RedoBlHooks | Cannot find pattern "#target"!"); \
return false; \
}else{ \
if(tsh_DEBUGPRINT) BlPrintf("RedoBlHooks | Found "#target" at %08x", (int)target); \
}
#define BlScanText(target, text) \
target = tsh_ScanText((char*)text); \
if(!target){ \
BlPrintf("RedoBlHooks | Cannot find "#target"!"); \
return false; \
}else{ \
if(tsh_DEBUGPRINT) BlPrintf("RedoBlHooks | Found "#target" at %08x", (int)target); \
}
// Use in code to define the data and functions for hooking a function
#define BlFunctionHookDef(func) \
BYTE tsh_BlFunctionHook##func##Data[6]; \
void func##HookOn(){ tsh_HookFunction((ADDR)func, (ADDR)func##Hook, tsh_BlFunctionHook##func##Data); } \
void func##HookOff(){ tsh_UnhookFunction((ADDR)func, tsh_BlFunctionHook##func##Data); }
// Use in code to initialize the hook once
#define BlFunctionHookInit(func) \
tsh_DeprotectAddress(6, (ADDR)func); \
func##HookOn();
// Replace all matches of the pattern with the given byte string
#define BlPatchAllMatchesCode(len, patt, mask, repl) \
tsh_PatchAllMatchesCode((ADDR)len, (char*)patt, (char*)mask, (char*)repl, tsh_DEBUGPRINT);
#define BlPatchAllMatchesText(len, text, repl) \
tsh_PatchAllMatchesCode((ADDR)len, (char*)text, (char*)repl, tsh_DEBUGPRINT);
// Deprotect and replace one byte
#define BlPatchByte(addr, byte) \
tsh_PatchByte((ADDR)addr, (BYTE)byte);
// Deprotect and replace a byte string
#define BlPatchBytes(len, addr, repl) \
tsh_PatchBytes((ADDR)len, (ADDR)addr, (BYTE*)repl);
// BlPrintf(char* format, ...)
#define BlPrintf(...) if(tsh_BlPrintf) { tsh_BlPrintf(__VA_ARGS__); }
// BlHooksInit() -> bool: success
#define BlHooksInit() if(!tsh_InitInternal()) { BlPrintf("BlHooksInit failed"); return false; }
// BlHooksDeinit() -> bool: success
#define BlHooksDeinit() if(!tsh_DeinitInternal()) { BlPrintf("BlHooksDeinit failed"); return false; }
// Scanned structures
BlFunctionDefExtern(void, , tsh_BlPrintf, const char*, ...);
#endif

BIN
lua5.1.dll Normal file

Binary file not shown.

BIN
lualib/https.dll Normal file

Binary file not shown.

292
lualib/ltn12.lua Normal file
View File

@@ -0,0 +1,292 @@
-----------------------------------------------------------------------------
-- 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 = table.getn(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

87
lualib/mime.lua Normal file
View File

@@ -0,0 +1,87 @@
-----------------------------------------------------------------------------
-- 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

BIN
lualib/mime/core.dll Normal file

Binary file not shown.

133
lualib/socket.lua Normal file
View File

@@ -0,0 +1,133 @@
-----------------------------------------------------------------------------
-- 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)

BIN
lualib/socket/core.dll Normal file

Binary file not shown.

281
lualib/socket/ftp.lua Normal file
View File

@@ -0,0 +1,281 @@
-----------------------------------------------------------------------------
-- 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)

350
lualib/socket/http.lua Normal file
View File

@@ -0,0 +1,350 @@
-----------------------------------------------------------------------------
-- 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)

251
lualib/socket/smtp.lua Normal file
View File

@@ -0,0 +1,251 @@
-----------------------------------------------------------------------------
-- 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)

123
lualib/socket/tp.lua Normal file
View File

@@ -0,0 +1,123 @@
-----------------------------------------------------------------------------
-- 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

297
lualib/socket/url.lua Normal file
View File

@@ -0,0 +1,297 @@
-----------------------------------------------------------------------------
-- 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, table.getn(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 = table.getn(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

36
readme.md Normal file
View File

@@ -0,0 +1,36 @@
# BlockLua
## How to Install
- Install RedBlocklandLoader
- Copy `BlockLua.dll`, `lua5.1.dll`, into the `modules` folder next to `Blockland.exe`
- Optional: Copy the `lualib` folder into `modules`
## Quick Reference
### From TorqueScript
`'print('hello world')` - Execute Lua code in the console by prepending a `'` (single quote)
`luaeval("code");` - Eval Lua code from Torque
`luacall("funcName", %args);` - Eval Lua code from Torque
`luaexec("fileName");` - Execute a Lua file from Torque
`luaget("varName");` - Read a Lua global variable
`luaset("varName");` - Write a Lua global variable
### From Lua
`bl.funcName(args)` - Call a TorqueScript function
`bl.varName` - Read a TorqueScript global variable
`bl['varName']` - Read a TorqueScript global variable (with special characters in the name, or from an array)
`bl.set('varName', value)` - Write a TorqueScript global variable
### Accessing Torque Objects from Lua
`bl.objectName` - Access a Torque object by name
`bl[objectID]` - Access a Torque object by ID (or name)
`bl.objectName.field` - Read a field or Lua key from a Torque object
`bl.objectName:set('field', value)` - Write a field on a Torque object
`bl.objectName.key = value` - Associate Lua data with a Torque object
`bl.objectName:method(args)` - Call a Torque object method
### Unsafe Mode
BlockLua-Unsafe.dll can be used in place of BlockLua.dll, to remove the sandboxing of Lua code.
This allows Lua code to access any file and use any library, including ffi.

105
src/bllua4.cpp Normal file
View File

@@ -0,0 +1,105 @@
// BlockLua (bllua4): Simple Lua interface for TorqueScript
// Includes
#include <Windows.h>
#include <Psapi.h>
#include "lua.hpp"
#include "BlHooks.cpp"
#include "BlFuncs.cpp"
#include "luainterp.cpp"
#include "lualibts.cpp"
lua_State* gL;
#include "tsliblua.cpp"
// Global variables
// Setup
// Hack to encode the contents of text files as static strings
#define INCLUDE_BIN(varname, filename) \
asm("_" #varname ": .incbin \"" filename "\""); \
asm(".byte 0"); \
extern char varname[];
INCLUDE_BIN(bll_fileLuaEnvSafe, "lua-env-safe.lua");
INCLUDE_BIN(bll_fileLuaEnv , "lua-env.lua");
INCLUDE_BIN(bll_fileTsEnv , "ts-env.cs" );
INCLUDE_BIN(bll_fileLuaStd , "util/std.lua");
INCLUDE_BIN(bll_fileLuaVector , "util/vector.lua");
INCLUDE_BIN(bll_fileLuaLibts , "util/libts.lua");
INCLUDE_BIN(bll_fileTsLibtsSupport, "util/libts-support.cs");
INCLUDE_BIN(bll_fileLuaLibbl , "util/libbl.lua" );
INCLUDE_BIN(bll_fileLuaLibblTypes , "util/libbl-types.lua");
INCLUDE_BIN(bll_fileTsLibblSupport, "util/libbl-support.cs");
INCLUDE_BIN(bll_fileLoadaddons , "util/loadaddons.cs");
#define BLL_LOAD_LUA(lstate, vname) \
if(!bll_LuaEval(lstate, vname)) { \
BlPrintf(" Error executing " #vname); \
return false; \
}
bool init() {
BlHooksInit();
BlPrintf("BlockLua: Loading");
BlFuncsInit();
// Initialize Lua environment
gL = lua_open();
luaL_openlibs(gL);
// Expose TS API to Lua
llibbl_init(gL);
// Set up Lua environment
BLL_LOAD_LUA(gL, bll_fileLuaEnv);
#ifndef BLLUA_UNSAFE
BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe);
#endif
// Expose Lua API to TS
BlAddFunction(NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20);
BlEval(bll_fileTsEnv);
// Load utilities
BLL_LOAD_LUA(gL, bll_fileLuaStd);
BLL_LOAD_LUA(gL, bll_fileLuaVector);
BLL_LOAD_LUA(gL, bll_fileLuaLibts);
BlEval(bll_fileTsLibtsSupport);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
BlEval(bll_fileTsLibblSupport);
BlEval(bll_fileLoadaddons);
BlEval("$_bllua_active = 1;");
BlPrintf(" BlockLua: Done Loading");
return true;
}
bool deinit() {
BlPrintf("BlockLua: Unloading");
BlEval("deactivatePackage(_bllua_main);");
BlEval("$_bllua_active = 0;");
bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end");
lua_close(gL);
BlHooksDeinit();
BlFuncsDeinit();
BlPrintf(" BlockLua: Done Unloading");
return true;
}
bool __stdcall DllMain(HINSTANCE hinstance, DWORD reason, void* reserved) {
switch(reason) {
case DLL_PROCESS_ATTACH: return init();
case DLL_PROCESS_DETACH: return deinit();
default : return true;
}
}

142
src/lua-env-safe.lua Normal file
View File

@@ -0,0 +1,142 @@
-- Sanitize the Lua environment to:
-- Prevent scripts from accessing files outside the game directory
-- Prevent usage of libraries other than
-- Utility: Convert a list of strings into a map of string->true
local function tmap(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
-- Save banned global variables for wrapping with safe functions
local old_io = io
local old_require = require
local old_os = os
local old_debug = debug
local old_package = package
-- Remove all global variables except a whitelist
local ok_names = tmap {
'_G', '_bllua_ts', '_bllua_on_unload', '_bllua_on_error',
'string', 'table', 'math', 'coroutine', 'bit',
'pairs', 'ipairs', 'next', 'unpack', 'select',
'error', 'assert', 'pcall', 'xpcall',
'type', 'tostring', 'tonumber',
'loadstring',
'getmetatable', 'setmetatable',
'rawget', 'rawset', 'rawequal', 'rawlen',
'module', '_VERSION',
}
local not_ok_names = {}
for n, _ in pairs(_G) do
if not ok_names[n] then
table.insert(not_ok_names, n)
end
end
for _, n in ipairs(not_ok_names) do
_G[n] = nil
end
-- Sanitize file paths to point only to allowed files within the game directory
-- List of allowed directories for reading/writing
local allowed_dirs = tmap {
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
}
-- List of allowed directories for reading only
local allowed_dirs_readonly = tmap {
'lualib'
}
-- List of disallowed file extensions - basically executable file extensions
-- Note that even without this protection, exploiting would still require somehow
-- getting a file within the allowed directories to autorun,
-- so this is just a precaution.
local disallowed_exts = tmap {
-- windows
'bat','bin','cab','cmd','com','cpl','ex_','exe','gadget','inf','ins','inx','isu',
'job','jse','lnk','msc','msi','msp','mst','paf','pif','ps1','reg','rgs','scr',
'sct','shb','shs','u3p','vb','vbe','vbs','vbscript','ws','wsf','wsh',
-- linux
'csh','ksh','out','run','sh',
-- mac/other
'action','apk','app','command','ipa','osx','prg','workflow',
}
-- Arguments: file name (relative to game directory), boolean true if only reading
-- Return: clean file path if allowed (or nil if disallowed),
-- error string (or nil if allowed)
local function safe_path(fn, readonly)
fn = fn:gsub('\\', '/')
fn = fn:gsub('^ +', '')
fn = fn:gsub(' +$', '')
-- whitelist characters
local ic = fn:find('[^a-zA-Z0-9_%-/ %.]')
if ic then
return nil, 'Filename \''..fn..'\' contains invalid character \''..
fn:sub(ic, ic)..'\' at position '..ic
end
-- disallow up-dirs, absolute paths, and relative paths
-- './' and '../' are possible in scripts, because they're processed into
-- absolute paths in util.lua before reaching here
if fn:find('^%.') or fn:find('%.%.') or fn:find(':') or fn:find('^/') then
return nil, 'Filename \''..fn..'\' contains invalid sequence'
end
-- allow only whitelisted dirs
local dir = fn:match('^([^/]+)/')
if (not dir) or (
(not allowed_dirs[dir:lower()]) and
((not readonly) or (not allowed_dirs_readonly[dir:lower()])) ) then
return nil, 'filename is in disallowed directory '..(dir or 'nil')
end
-- disallow blacklisted extensions or no extension
local ext = fn:match('%.([^/%.]+)$')
if (not ext) or (disallowed_exts[ext:lower()]) then
return nil, 'Filename \''..fn..'\' has disallowed extension \''..
(ext or '')..'\''
end
return fn, nil
end
-- Wrap io.open with path sanitization
function _bllua_io_open(fn, md)
md = md or 'r'
local readonly = md=='r' or md=='rb'
local fns, err = safe_path(fn, readonly)
if fns then
return old_io.open(fns, md)
else
return nil, err
end
end
-- Allow io.type (works on file handles returned by io.open)
function _bllua_io_type(f)
return old_io.type(f)
end
-- Wrap require with a blacklist for unsafe built-in modules
-- List of allowed packages to require
-- Note that util.lua wraps this and provides 'require',
-- only falling back here if the package is not found in user files
local disallowed_packages = tmap {
'ffi', 'debug', 'package', 'io', 'os',
'_bllua_ts',
}
function _bllua_requiresecure(name)
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
name:find('^%.') or name:find('%.$') then
error('require: package name contains invalid character', 3)
elseif disallowed_packages[name] then
error('require: attempt to require disallowed module \''..name..'\'', 3)
else
-- todo: reimplement require to not use package.* stuff?
return old_require(name)
end
end
package = {
seeall = old_package.seeall,
}
-- Provide limited debug
debug = {
traceback = old_debug.traceback,
getinfo = old_debug.getinfo,
getfilename = old_debug.getfilename, -- defined in lua.env.lua
}
_bllua_ts.echo(' Executed bllua-env-safe.lua')

40
src/lua-env.lua Normal file
View File

@@ -0,0 +1,40 @@
-- Set up the Lua environment
-- Point old require to modules/lua in game directory
package.path = 'modules\\lualib\\?.lua;modules\\lualib\\?\\init.lua;'
package.cpath = 'modules\\lualib\\?.dll;'
-- Set up table of unload/cleanup callbacks
_bllua_on_unload = {}
-- Utility for getting the current filename
function debug.getfilename(level)
if type(level) == 'number' then level = level+1 end
local info = debug.getinfo(level)
if not info then return nil end
local filename = info.source:match('^%-%-%[%[([^%]]+)%]%]')
return filename
end
-- Called when pcall fails on a ts->lua call, used to print detailed error info
function _bllua_on_error(err)
err = err:match(': (.+)$') or err
local tracelines = {err}
local level = 2
while true do
local info = debug.getinfo(level)
if not info then break end
local filename = debug.getfilename(level) or info.short_src
local funcname = info.name
if funcname=='dofile' then break end
table.insert(tracelines, string.format('%s:%s in function \'%s\'',
filename,
info.currentline==-1 and '' or info.currentline..':',
funcname
))
level = level+1
end
return table.concat(tracelines, '\n')
end
_bllua_ts.echo(' Executed bllua-env.lua')

68
src/luainterp.cpp Normal file
View File

@@ -0,0 +1,68 @@
// Handle errors with a Lua function, defined in lua-env.lua
int bll_error_handler(lua_State *L) {
lua_getfield(L, LUA_GLOBALSINDEX, "_bllua_on_error");
if (!lua_isfunction(L, -1)) {
BlPrintf(" Lua error handler: _bllua_on_error not defined.");
lua_pop(L, 1);
return 1;
}
// move function to before arg
int hpos = lua_gettop(L) - 1;
lua_insert(L, hpos);
lua_pcall(L, 1, 1, 0);
return 1;
}
int bll_pcall(lua_State* L, int nargs, int nret) {
// calculate stack position for message handler
int hpos = lua_gettop(L) - nargs;
// push custom error message handler
lua_pushcfunction(L, bll_error_handler);
// move it before function and arguments
lua_insert(L, hpos);
// call lua_pcall function with custom handler
int ret = lua_pcall(L, nargs, nret, hpos);
// remove custom error message handler from stack
lua_remove(L, hpos);
return ret;
}
// Display the last Lua error in the BL console
void bll_printError(lua_State* L, const char* operation, const char* item) {
//error_handler(L);
BlPrintf("\x03Lua error: %s", lua_tostring(L, -1));
BlPrintf("\x03 (%s: %s)", operation, item);
lua_pop(L, 1);
}
// Eval a string of Lua code
bool bll_LuaEval(lua_State* L, const char* str) {
if(luaL_loadbuffer(L, str, strlen(str), "input") || bll_pcall(L, 0, 1)) {
bll_printError(L, "eval", str);
return false;
}
return true;
}
// Convert a Lua stack entry into a string for providing to TS
// Use static buffer to avoid excessive malloc
#define BLL_ARG_COUNT 20
#define BLL_ARG_MAX 8192
char bll_arg_buffer[BLL_ARG_COUNT][BLL_ARG_MAX];
bool bll_toarg(lua_State* L, char* buf, int i, bool err) {
if(lua_isstring(L, i)) {
const char* str = lua_tostring(L, i);
if(strlen(str) >= BLL_ARG_MAX) {
if(err) luaL_error(L, "argument to TS is too long - max length is 8192");
return true;
} else {
strcpy(buf, str);
return false;
}
} else {
if(err) luaL_error(L, "argument to TS must be a string");
return true;
}
}

159
src/lualibts.cpp Normal file
View File

@@ -0,0 +1,159 @@
//run ../compile
// TS -> Lua API
// Call a TS function from Lua, push the result to Lua stack
int bll_TsCall(lua_State* L, const char* oname, const char* fname, int argc, int ofs) {
ADDR obj = (ADDR)NULL;
if(oname) {
obj = BlObject(oname);
if(!obj) { return luaL_error(L, "Lua->TS call: Object not found"); }
}
if(argc > BLL_ARG_COUNT) { return luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)"); }
char* argv[BLL_ARG_COUNT];
for(int i=0; i<argc; i++) {
char* argbuf = bll_arg_buffer[i];
argv[i] = argbuf;
bll_toarg(L, argbuf, i+ofs+1, true);
}
// /:^| /
const char* res;
if(obj) {
switch(argc) {
case 0: res = BlCallObj(obj, fname); break; // no idea why this happens sometimes, it shouldnt be possible
case 1: res = BlCallObj(obj, fname); break;
case 2: res = BlCallObj(obj, fname, argv[0]); break;
case 3: res = BlCallObj(obj, fname, argv[0], argv[1]); break;
case 4: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2]); break;
case 5: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3]); break;
case 6: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break;
case 7: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break;
case 8: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break;
case 9: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break;
case 10: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break;
case 11: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break;
case 12: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break;
case 13: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break;
case 14: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break;
case 15: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break;
case 16: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break;
case 17: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break;
case 18: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break;
case 19: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break;
case 20: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break;
default: res = ""; luaL_error(L, "Lua->TS object call: Too many arguments (Max is 19)");
}
} else {
switch(argc) {
case 0: res = BlCall(fname); break;
case 1: res = BlCall(fname, argv[0]); break;
case 2: res = BlCall(fname, argv[0], argv[1]); break;
case 3: res = BlCall(fname, argv[0], argv[1], argv[2]); break;
case 4: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3]); break;
case 5: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break;
case 6: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break;
case 7: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break;
case 8: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break;
case 9: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break;
case 10: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break;
case 11: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break;
case 12: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break;
case 13: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break;
case 14: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break;
case 15: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break;
case 16: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break;
case 17: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break;
case 18: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break;
case 19: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break;
default: res = ""; luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)");
}
}
lua_pushstring(L, res);
return 1;
}
// Lua lib function: ts.call
int bll_lua_tscall(lua_State* L) {
int argc = lua_gettop(L)-1; // number of arguments after function name
if(argc < 0) return luaL_error(L, "_bllua_ts.call: Must provide a function name");
const char* fname = luaL_checkstring(L, 1);
return bll_TsCall(L, NULL, fname, argc, 1);
}
// Lua lib function: ts.callobj
int bll_lua_tscallobj(lua_State* L) {
int argc = lua_gettop(L)-2; // number of arguments after function name and object?
if(argc < 0) return luaL_error(L, "_bllua_ts.callobj: Must provide an object and function name");
const char* oname = luaL_checkstring(L, 1);
const char* fname = luaL_checkstring(L, 2);
return bll_TsCall(L, oname, fname, argc, 2);
}
// Lua lib function: ts.getvar
int bll_lua_tsgetvar(lua_State* L) {
const char* vname = luaL_checkstring(L, 1);
const char* var = BlGetVar(vname);
lua_pushstring(L, var);
return 1;
}
// Lua lib function: ts.getfield
int bll_lua_tsgetfield(lua_State* L) {
const char* oname = luaL_checkstring(L, 1);
const char* vname = luaL_checkstring(L, 2);
ADDR obj = BlObject(oname);
if(!obj) { return luaL_error(L, "_bllua_ts.getfield: Object not found"); }
const char* val = BlGetField(obj, vname, NULL);
lua_pushstring(L, val);
return 1;
}
// Lua lib function: ts.setfield
int bll_lua_tssetfield(lua_State* L) {
const char* oname = luaL_checkstring(L, 1);
const char* vname = luaL_checkstring(L, 2);
const char* val = luaL_checkstring(L, 3);
ADDR obj = BlObject(oname);
if(!obj) { return luaL_error(L, "_bllua_ts.setfield: Object not found"); }
BlSetField(obj, vname, NULL, val);
return 0;
}
// Lua lib function: ts.eval
int bll_lua_tseval(lua_State* L) {
const char* str = luaL_checkstring(L, 1);
const char* res = BlEval(str);
lua_pushstring(L, res);
return 1;
}
// Lua lib function: ts.echo
// Print to BL console - used in Lua print implementation
int bll_lua_tsecho(lua_State* L) {
const char* str = luaL_checkstring(L, 1);
BlPrintf("%s", str);
return 0;
}
const luaL_Reg bll_lua_reg[] = {
{"call" , bll_lua_tscall },
{"callobj" , bll_lua_tscallobj },
{"getvar" , bll_lua_tsgetvar },
{"getfield", bll_lua_tsgetfield},
{"setfield", bll_lua_tssetfield},
{"eval" , bll_lua_tseval },
{"echo" , bll_lua_tsecho },
{NULL, NULL},
};
void llibbl_init(lua_State* L) {
luaL_register(L, "_bllua_ts", bll_lua_reg);
}

26
src/ts-env.cs Normal file
View File

@@ -0,0 +1,26 @@
// Built-in functions
// Eval'd after BLLua4 has loaded the Lua environment and API
// Public Lua library for TS
function luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p) {
if($_bllua_active)
return _bllua_luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p);
}
function luaexec(%fn) {
if($_bllua_active)
return _bllua_luacall("_bllua_exec", %fn);
}
function luaeval(%code) {
if($_bllua_active)
return _bllua_luacall("_bllua_eval", %code);
}
function luaget(%name) {
if($_bllua_active)
return _bllua_luacall("_bllua_getvar", %name);
}
function luaset(%name, %val) {
if($_bllua_active)
_bllua_luacall("_bllua_setvar", %name, %val);
}
echo(" Executed bllua-env.cs");

25
src/tsliblua.cpp Normal file
View File

@@ -0,0 +1,25 @@
// Call a Lua function from TS, return true if success - result will be on Lua stack
bool bll_LuaCall(const char* fname, int argc, const char* argv[]) {
lua_getglobal(gL, fname);
for(int i=0; i<argc; i++) {
lua_pushstring(gL, argv[i]);
}
if(bll_pcall(gL, argc, 1)) {
bll_printError(gL, "call", fname);
return false;
}
return true;
}
// TS lib function: luacall
const char* bll_ts_luacall(ADDR obj, int argc, const char* argv[]) {
if(argc<2) return "";
if(!bll_LuaCall(argv[1], argc-2, &argv[2])) { return ""; }
char* retbuf = BlReturnBuffer(BLL_ARG_MAX);
bll_toarg(gL, retbuf, -1, false); // provide returned value to ts
lua_pop(gL, 1); // pop returned value
return retbuf;
}

44
src/util/libbl-support.cs Normal file
View File

@@ -0,0 +1,44 @@
// Private - Utilities used by libbl from the Lua side
package _bllua_smartEval {
// 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 }
function ConsoleEntry::eval() {
%text = ConsoleEntry.getValue();
if(getSubStr(%text, 0, 1)$="\'") { // lua eval
if($_bllua_active) {
%text = getSubStr(%text, 1, strLen(%text));
echo("Lua ==> " @ %text);
luacall("_bllua_smarteval", %text);
} else {
echo("Lua: not loaded");
}
ConsoleEntry.setValue("");
} else {
%textT = trim(%text);
if(strLen(%textT)>0) {
%lastChar = getSubStr(%textT, strLen(%textT)-1, 1);
if(%lastChar!$=";" && %lastChar!$="}") {
%text = "echo(" @ %text @ ");";
ConsoleEntry.setValue(%text);
}
}
parent::eval();
}
}
};
activatePackage(_bllua_smartEval);
package _bllua_objectDeletionHook {
// Hook object deletion to clean up its lua data
function SimObject::onRemove(%obj) {
// note: no parent function exists by default,
// and this is loaded before any addons
//parent::onRemove(%obj);
if($_bllua_active) luacall("_bllua_objectDeleted", %obj);
}
};
activatePackage(_bllua_objectDeletionHook);
echo(" Executed libbl-support.cs");

3706
src/util/libbl-types.lua Normal file

File diff suppressed because it is too large Load Diff

840
src/util/libbl.lua Normal file
View File

@@ -0,0 +1,840 @@
-- bl library
-- Main lua-side functionality of bllua,
-- provided through the global table 'bl.'
-- todo: set
local _bllua_ts = ts
bl = bl or {}
-- Misc
-- Apply a function to each element in a list, building a new list from the returns
local function map(t,f)
local u = {}
for i,v in ipairs(t) do
u[i] = f(v)
end
return u
end
local function isValidFuncName(name)
return type(name)=='string' and name:find('^[a-zA-Z0-9_]+$')
end
local function isValidFuncNameNs(name)
return type(name)=='string' and (
name:find('^[a-zA-Z0-9_]+$') or
name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') )
end
local function isValidFuncNameNsArgn(name)
return type(name)=='string' and (
name:find('^[a-zA-Z0-9_]+$') or
name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or
name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or
name:find('^[a-zA-Z0-9_]+:[0-9]+$') or
name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$') )
end
-- Whether a var can be converted into a TS vector
local function isTsVector(val)
if type(val)~='table' then return false end
if #val~=3 and #val~=2 then return false end
if val.__is_vector then return true end
for _,v in ipairs(val) do
if type(v)~='number' then return false end
end
return true
end
-- Use strings for object types instead of integer bitmasks like in TS
local tsTypesByName = {
['all'] = -1,
['static'] = 1,
['environment'] = 2,
['terrain'] = 4,
['water'] = 16,
['trigger'] = 32,
['marker'] = 64,
['gamebase'] = 1024,
['shapebase'] = 2048,
['camera'] = 4096,
['staticshape'] = 8192,
['player'] = 16384,
['item'] = 32768,
['vehicle'] = 65536,
['vehicleblocker'] = 131072,
['projectile'] = 262144,
['explosion'] = 524288,
['corpse'] = 1048576,
['debris'] = 4194304,
['physicalzone'] = 8388608,
['staticts'] = 16777216,
['brick'] = 33554432,
['brickalways'] = 67108864,
['staticrendered'] = 134217728,
['damagableitem'] = 268435456,
}
local tsTypesByNum = {}
for k,v in pairs(tsTypesByName) do
tsTypesByNum[v] = k
end
-- Type conversion
local toTsObject
-- Convert a string from TS into a boolean
-- Note: Nonempty nonnumeric strings evaluate to 1, unlike in TS
local function tsBool(v) return v~='' and v~='0' end
-- Convert a Lua var into a TS string, or error if not possible
local function valToTs(val)
if val==nil then -- nil -> ''
return ''
elseif type(val)=='boolean' then -- bool -> 0 or 1
return val and '1' or '0'
elseif type(val)=='number' then -- number
return tostring(val)
elseif type(val)=='string' then -- string
return val
elseif type(val)=='table' then
if val._tsObjectId then -- object -> object id
return tostring(val._tsObjectId)
elseif isTsVector(val) then -- vector - > 3 numbers
return table.concat(val, ' ')
elseif #val==2 and isTsVector(val[1]) and isTsVector(val[2]) then
-- box - > 6 numbers
return table.concat(val[1], ' ')..' '..table.concat(val[2], ' ')
else
error('valToTs: could not convert table', 3)
end
else
error('valToTs: could not convert '..type(val), 3)
end
end
bl._forceType = bl._forceType or {}
local function valFromTs(val, name)
if type(val)~='string' then
error('valFromTs: expected string, got '..type(val), 3) end
if name then
local nameL = name:lower()
if bl._forceType[nameL] then
local typ = bl._forceType[nameL]
if typ=='boolean' then
return tsBool(val)
elseif typ=='object' then
return toTsObject(val)
else
error('valFromTs: invalid force type '..typ, 3)
end
end
end
-- '' -> nil
if val=='' then return nil end
-- number
local num = tonumber(val)
if num then return num end
-- vector
local xS,yS,zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
if xS then return vector{tonumber(xS),tonumber(yS),tonumber(zS)} end
local x1S,y1S,z1S,x2S,y2S,z2S = val:match(
'^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '..
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
-- box (2 vectors)
if x1S then return {
vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)},
vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} } end
-- string
return val
end
local function arglistFromTs(name, argsS)
local args = {}
for i,arg in ipairs(argsS) do
args[i] = valFromTs(arg, name..':'..i)
end
return args
end
local function arglistToTs(args)
return map(args, valToTs)
end
function bl.type(name,typ)
if typ~='bool' and typ~='boolean' and typ~='object' and typ~=nil then
error('bl.type: can only set type to \'bool\' or \'object\' or nil', 2) end
if not isValidFuncNameNsArgn(name) then
error('bl.type: invalid function or variable name \''..name..'\'', 2) end
if typ=='bool' then typ='boolean' end
bl._forceType[name:lower()] = typ
end
-- Value detection
local function isTsObject(t)
return type(t)=='table' and t._tsObjectId~=nil
end
local function tsIsObject(name) return tsBool(_bllua_ts.call('isObject', name)) end
local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', name)) end
local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end
local function tsIsFunctionNsname(nsname)
local ns, name = nsname:match('^([^:]+)::([^:]+)$')
if ns then return tsIsFunctionNs(ns, name)
else return tsIsFunction(nsname) end
end
function bl.isObject(obj)
if isTsObject(obj) then
obj = obj._tsObjectId
elseif type(obj)=='number' then
obj = tostring(obj)
elseif type(obj)~='string' then
error('bl.isObject: argument #1: expected torque object, number, or string', 2)
end
return tsIsObject(obj)
end
function bl.isFunction(a1, a2)
if type(a1)~='string' then
error('bl.isFunction: argument #1: expected string', 2) end
if a2 then
if type(a2)~='string' then
error('bl.isFunction: argument #2: expected string', 2) end
return tsIsFunctionNs(a1, a2)
else
return tsIsFunction(a1)
end
end
-- Torque object pseudo-class
bl._objectUserMetas = bl._objectUserMetas or {}
function bl.class(name, inherit)
if not ( type(name)=='string' and isValidFuncName(name) ) then
error('bl.class: argument #1: invalid class name', 2) end
if not ( inherit==nil or (type(inherit)=='string' and isValidFuncName(inherit)) ) then
error('bl.class: argument #2: inherit name must be a string or nil', 2) end
name = name:lower()
local met = bl._objectUserMetas[name] or {}
bl._objectUserMetas[name] = met
met._name = name
if inherit then
inherit = inherit:lower()
local inh = bl._objectUserMetas[inherit]
if not inh then error('bl.class: argument #2: \''..inherit..'\' is not the '..
'name of an existing class', 2) end
local inhI = bl._objectUserMetas[name]._inherit
if inhI and inhI~=inh then
error('bl.class: argument #2: class already exists and '..
'inherits a different parent.', 2) end
bl._objectUserMetas[name]._inherit = inh
end
end
local function objectInheritedMetas(name)
local inh = bl._objectUserMetas[name:lower()]
return function()
local inhP = inh
if inhP==nil then return nil end
inh = inh._inherit
return inhP
end
end
local tsObjectMeta = {
-- __index: Called when accessing fields that don't exist in the object itself
-- Return torque member function or value
__index = function(t, name)
if rawget(t,'_deleted') then
error('ts object index: object no longer exists', 2) end
if type(name)~='string' and type(name)~='number' then
error('ts object index: index must be a string or number', 2) end
if getmetatable(t)[name] then
return getmetatable(t)[name]
elseif type(name)=='number' then
if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then
error('ts object __index: index is number, but object does not have '..
'getObject method', 2) end
return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject',
tostring(name)))
else
for inh in objectInheritedMetas(rawget(t,'_tsClassName')) do
if inh[name] then return inh[name] end
end
if tsIsFunctionNs(rawget(t,'_tsNamespace'), name) then
return function(t, ...)
local args = {...}
local argsS = arglistToTs(args)
return valFromTs(_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
rawget(t,'_tsNamespace')..'::'..name)
end
else
return valFromTs(_bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
rawget(t,'_tsNamespace')..'.'..name)
end
end
end,
-- __newindex: Called when setting fields on the object
-- Set lua data
-- Use :set() to set Torque data
__newindex = function(t, name, val)
if rawget(t,'_deleted') then
error('ts object newindex: object no longer exists', 2) end
if type(name)~='string' then
error('ts object newindex: index must be a string', 2) end
rawset(t, name, val)
-- create strong reference since it's now storing lua data
bl._objectsStrong[rawget(t,'_tsObjectId')] = t
end,
-- object:set(fieldName, value)
-- Use to set torque data
set = function(t, name, val)
if t==nil or type(t)~='table' or not t._tsObjectId then
error('ts object method: be sure to use :func() not .func()', 2) end
if t._deleted then
error('ts object method: object no longer exists', 2) end
if type(name)~='string' then
error('ts object :set(): index must be a string', 2) end
_bllua_ts.setfield(t._tsObjectId, name, valToTs(val))
end,
-- __tostring: Called when printing
-- Display a nice info string
__tostring = function(t)
return 'torque:'..t._tsNamespace..':'..t._tsObjectId..
(t._tsName~='' and ('('..t._tsName..')') or '')
end,
-- #object
-- If the object has a getCount method, return its count
__len = function(t)
if t._deleted then
error('ts object __len: object no longer exists', 2) end
if not tsIsFunctionNs(t._tsNamespace, 'getCount') then
error('ts object __len: object has no getCount method', 2) end
return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
end,
-- object:iterate()
-- Return an iterator for Torque objects with the getCount and getObject methods
-- for index, object in group:iterate() do ... end
iterate = function(t)
if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) end
if t._deleted then
error('ts object method: object no longer exists', 2) end
if not (
tsIsFunctionNs(t._tsNamespace, 'getCount' ) and
tsIsFunctionNs(t._tsNamespace, 'getObject')) then
error('ts object :iterate() - '..
'Object does not have getCount and getObject methods', 2) end
local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
local idx = 0
return function()
if idx < count then
local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId,
'getObject', tostring(idx)))
idx = idx+1
return idx-1, obj
else
return nil
end
end
end,
-- Wrap some Torque functions for performance and error checking
getName = function(t)
if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) end
if t._deleted then
error('ts object method: object no longer exists', 2) end
return t._tsName
end,
getId = function(t)
if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) end
if t._deleted then
error('ts object method: object no longer exists', 2) end
return tonumber(t._tsObjectId)
end,
getType = function(t)
if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) end
if t._deleted then
error('ts object method: object no longer exists', 2) end
return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')]
end,
---- Schedule method for objects
--schedule = function(t, time, cb, ...)
-- if type(t)~='table' or not t._tsObjectId then
-- error('ts object method: be sure to use :func() not .func()', 2) end
-- if t._deleted then
-- error('ts object method: object no longer exists', 2) end
-- if type(time)~='number' then
-- error('ts object schedule: argument #2: time must be a number', 2) end
-- if type(cb)~='function' then
-- error('ts object schedule: argument #3: callback must be a function', 2) end
-- local args = {...}
-- bl.schedule(time, function()
-- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then
-- pcall(cb, unpack(args))
-- end
-- end)
--end,
exists = function(t)
if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) end
if t._deleted then
return false end
return tsIsObject(t._tsObjectId)
end,
}
-- Weak-values table for caching Torque object references
-- Objects in this table can be garbage collected if there are no other refs to them
if not bl._objectsWeak then
bl._objectsWeak = {}
setmetatable(bl._objectsWeak, { __mode='v' })
end
-- Strong table for preserving Torque object references containing lua data
-- If an object in this table, it will remain here and in the Weak table until deleted
if not bl._objectsStrong then
bl._objectsStrong = {}
end
-- Hook object deletion to clean up its lua data
-- idS is expected to be the object ID number, NOT the object name
function _bllua_objectDeleted(idS)
local obj = bl._objectsWeak[idS]
if obj then
obj._deleted = true
bl._objectsStrong[idS] = nil
bl._objectsWeak[idS] = nil
bl._objectsWeak[obj._tsName:lower()] = nil
end
end
-- Return a Torque object for the object ID string, or create one if none exists
toTsObject = function(idiS)
if type(idiS)~='string' then
error('toTsObject: input must be a string', 2) end
idiS = idiS:lower()
if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end
if not tsBool(_bllua_ts.call('isObject', idiS)) then
--error('toTsObject: object \''..idiS..'\' does not exist', 2) end
return nil end
local className = _bllua_ts.callobj(idiS, 'getClassName')
local obj = {
_tsObjectId = _bllua_ts.callobj(idiS, 'getId' ),
_tsName = _bllua_ts.callobj(idiS, 'getName' ),
_tsNamespace = className,
_tsClassName = className:lower()
}
setmetatable(obj, tsObjectMeta)
bl._objectsWeak[obj._tsObjectId ] = obj
bl._objectsWeak[obj._tsName:lower()] = obj
return obj
end
-- Metatable for the global bl library
-- Allows accessing Torque objects, variables, and functions by indexing it
local tsMeta = {
-- __index: Called when accessing fields that don't exist in the table itself
-- Allow indexing by object id: bl[1234]
-- by object name: bl.mainMenuGui
-- by function name: bl.quit()
-- by variable name: bl.iAmAdmin
__index = function(t, name)
if getmetatable(t)[name] then
return getmetatable(t)[name]
elseif bl._objectUserMetas[name:lower()] then
return bl._objectUserMetas[name:lower()]
else
if type(name)=='number' then
return toTsObject(tostring(name))
elseif name:find('::') then
local ns, rest = name:match('^([^:]+)::(.+)$')
if not ns then error('ts index: invalid name \''..name..'\'', 2) end
if not rest:find('::') and tsIsFunction(ns, rest) then
error('ts index: can\'t call a namespaced function from lua', 2)
else
return valFromTs(_bllua_ts.getvar(name), name)
end
elseif tsIsFunction(name) then
return function(...)
local args = {...}
local argsS = arglistToTs(args)
return valFromTs(_bllua_ts.call(name, unpack(argsS)), name)
end
elseif tsIsObject(name) then
return toTsObject(name)
else
return valFromTs(_bllua_ts.getvar(name), name)
end
end
end,
}
-- bl.set(name, value)
-- Used to set global variables
function bl.set(t, name, val)
_bllua_ts.call('_bllua_set_var', name, valToTs(val))
end
-- Utility functions
function bl.call(func, ...)
local args = {...}
local argsS = arglistToTs(args)
return _bllua_ts.call(func, unpack(argsS))
end
function bl.eval(code)
return valFromTs(_bllua_ts.call('eval', code))
end
function bl.exec(file)
return valFromTs(_bllua_ts.call('exec', file))
end
function bl.tobool(val)
return val~=nil and
val~=false and
--val~='' and
--val~='0' and
val~=0
end
function bl.toobject(id)
if type(id)=='table' and id._tsObjectId then
return id
elseif type(id)=='string' or type(id)=='number' then
return toTsObject(tostring(id))
else
error('bl.toobject: id must be a ts object, number, or string', 2)
end
end
function bl.array(name, ...)
local rest = {...}
return name..table.concat(rest, '_')
end
function _bllua_call(name, ...)
-- todo: call ts->lua using this instead of directly
end
-- bl.schedule: Use TS's schedule function to schedule lua calls
-- bl.schedule(time, function, args...)
bl._scheduleTable = bl._scheduleTable or {}
bl._scheduleNextId = bl._scheduleNextId or 1
local function cancelTsSched(sched)
if not (sched and sched.handle) then
error('schedule:cancel() - invalid object', 2)
end
_bllua_ts.call('cancel', sched.handle)
bl._scheduleTable[id] = nil
end
function bl.schedule(time, cb, ...)
local id = bl._scheduleNextId
bl._scheduleNextId = bl._scheduleNextId+1
local args = {...}
local handle = tonumber(_bllua_ts.call('schedule',
time, 0, 'luacall', '_bllua_schedule_callback', id))
local sch = {
callback = cb,
args = args,
handle = handle,
cancel = cancelTsSched,
}
bl._scheduleTable[id] = sch
return sch
end
function _bllua_schedule_callback(id)
id = tonumber(id)
local sch = bl._scheduleTable[id]
if not sch then error('_ts_schedule_callback: no schedule with id '..id) end
bl._scheduleTable[sched_id] = nil
sch.callback(unpack(sch.args))
end
-- serverCmd and clientCmd
-- bl.serverCmd('suicide', function(client) client.player:kill() end)
bl._cmds = bl._cmds or {}
function _bllua_process_cmd(cmdS, clientS, ...)
local client = toTsObject(clientS)
local argsS = {...}
local args = arglistFromTs(cmdS, argsS)
local func = bl._cmds[cmdS]
if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end
pcall(func, client, unpack(args))
end
local function addCmd(cmd, func)
if not isValidFuncName(cmd) then
error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
bl._servercmds[cmd] = func
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
_bllua_ts.eval('function '..cmd..'(%cl,'..arglist..'){'..
'luacall(_bllua_process_cmd,"'..cmd..'",%cl,'..arglist..');}')
end
function bl.serverCmd(name, func) addCmd('serverCmd'..name, func) end
function bl.clientCmd(name, func) addCmd('clientCmd'..name, func) end
-- Hooks (using TS packages)
local function isPackageActive(pkg)
local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages'))
for i = 0, numpkgs-1 do
local apkg = _bllua_ts.call('getActivePackage', tostring(i))
if apkg==pkg then return true end
end
return false
end
local function activatePackage(pkg)
if not isPackageActive(pkg) then
_bllua_ts.call('activatePackage', pkg)
end
end
local function deactivatePackage(pkg)
if isPackageActive(pkg) then
_bllua_ts.call('deactivatePackage', pkg)
end
end
bl._hooks = bl._hooks or {}
function _bllua_process_hook(pkgS, nameS, timeS, ...)
local argsS = {...}
local args = arglistFromTs(nameS, argsS)
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS][timeS]
if not func then
error('_bllua_process_hook: no hook for '..pkgS..':'..nameS..':'..timeS) end
pcall(func, args)
end
local function updateHook(pkg, name, hk)
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
local beforeCode = hk.before and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "before", '..arglist..');') or ''
local parentCode = hk.override and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "override", '..arglist..');') or
(tsIsFunctionNsname(name) and
('parent::'..name:match('[^:]+$')..'('..arglist..');') or '')
local afterCode = hk.after and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "after", '..arglist..');') or ''
bl.eval('package '..pkg..'{function '..name..'('..arglist..'){'..
beforeCode..parentCode..afterCode..'}};')
end
function bl.hook(pkg, name, time, func)
if not isValidFuncName(pkg) then
error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
if not isValidFuncNameNs(name) then
error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
if time~='before' and time~='after' and time~='override' then
error('bl.hook: argument #3: time must be one of '..
'\'before\' \'after\' \'override\'', 2) end
if type(func)~='function' then
error('bl.hook: argument #4: expected a function', 2) end
bl._hooks[pkg] = bl._hooks[pkg] or {}
bl._hooks[pkg][name] = bl._hooks[pkg][name] or {}
bl._hooks[pkg][name][time] = func
updateHook(pkg, name, bl._hooks[pkg][name])
activatePackage(pkg)
end
function bl.unhook(pkg, name, time)
if not isValidFuncName(pkg) then
error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
if not isValidFuncNameNs(name) then
error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
if time~='before' and time~='after' and time~='override' then
error('bl.unhook: argument #3: time must be one of '..
'\'before\' \'after\' \'override\'', 2) end
if not name then
if bl._hooks[pkg] then
for name,hk in pairs(bl._hooks[pkg]) do
updateHook(pkg, name, {})
end
bl._hooks[pkg] = nil
else
--error('bl.unhook: no hooks registered under package name \''..
-- pkg..'\'', 2)
end
deactivatePackage(pkg)
else
if bl._hooks[pkg][name] then
if not time then
bl._hooks[pkg][name] = nil
if table.isempty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil
deactivatePackage(pkg)
end
updateHook(pkg, name, {})
else
if time~='before' and time~='after' and time~='override' then
error('bl.unhook: argument #3: time must be nil or one of '..
'\'before\' \'after\' \'override\'', 2) end
bl._hooks[pkg][name][time] = nil
if table.isempty(bl._hooks[pkg][name]) and table.empty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil
deactivatePackage(pkg)
end
updateHook(pkg, name, bl._hooks[pkg][name])
end
else
--error('bl.unhook: no hooks registered for function \''..name..
-- '\' under package name \''..pkg..'\'', 2)
end
end
end
-- Container search/raycast
local function vecToTs(v)
if not isTsVector(v) then
error('vecToTs: argument is not a vector', 3) end
return table.concat(v, ' ')
end
local function maskToTs(mask)
if type(mask)=='string' then
local val = tsTypesByName[mask:lower()]
if not val then
error('maskToTs: invalid mask \''..mask..'\'', 3) end
return tostring(val)
elseif type(mask)=='table' then
local tval = 0
local seen = {}
for i,v in ipairs(mask) do
if not seen[v] then
local val = tsTypesByName[v:lower()]
if not val then
error('maskToTs: invalid mask \''..v..
'\' at index '..i..' in mask list', 3) end
tval = tval + val
seen[v] = true
end
end
return tostring(tval)
else
error('maskToTs: mask must be a string or table', 3)
end
end
local function objToTs(obj)
if type(obj)=='number' or type(obj)=='string' then
return tostring(obj)
elseif type(obj)=='table' and obj._tsObjectId then
return tostring(obj._tsObjectId)
else
error('objToTs: invalid object \''..tostring(obj)..'\'', 3)
end
end
function bl.raycast(start, stop, mask, ignores)
local startS = vecToTs(start)
local stopS = vecToTs(start)
local maskS = maskToTs(mask)
local ignoresS = {}
for _,v in ipairs(ignores) do
table.insert(ignoresS, objToTs(v))
end
local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS))
if retS=='0' then
return nil
else
local hitS, pxS,pyS,pzS, nxS,nyS,nzS = retS:match('^([0-9]+) '..
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '..
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
local hit = toTsObject(hitS)
local pos = vector{tonumber(pxS),tonumber(pyS),tonumber(pzS)}
local norm = vector{tonumber(nxS),tonumber(nyS),tonumber(nzS)}
return hit, pos, norm
end
end
local function tsContainerSearchIterator()
local retS = _bllua_ts.call('containerSearchNext')
if retS=='0' then
return nil
else
return toTsObject(retS)
end
end
function bl.boxSearch(pos, size, mask)
local posS = vecToTs(pos)
local sizeS = vecToTs(size)
local maskS = maskToTs(mask)
_bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS)
return tsContainerSearchIterator
end
function bl.radiusSearch(pos, radius, mask)
local posS = vecToTs(pos)
if type(radius)~='number' then
error('bl.radiusSearch: argument #2: radius must be a number', 2) end
local radiusS = tostring(radius)
local maskS = maskToTs(mask)
_bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS)
return tsContainerSearchIterator
end
-- Print/Talk/Echo
local function valsToString(vals)
local strs = {}
for i,v in ipairs(vals) do
strs[i] = table.tostring(v)
end
return table.concat(strs, ' ')
end
bl.echo = function(...)
local str = valsToString({...})
_bllua_ts.call('echo', str)
end
print = bl.echo
bl.talk = function(...)
local str = valsToString({...})
_bllua_ts.call('echo', str)
_bllua_ts.call('talk', str)
end
local function createTsObj(keyword, class, name, inherit, props)
local propsT = {}
for k,v in pairs(props) do
if not isValidFuncName(k) then
error('bl.new/datablock: invalid property name \''..k..'\'') end
table.insert(propsT, k..'="'..valToTs(v)..'";')
end
local objS = _bllua_ts.eval(
'return '..keyword..' '..class..'('..
(name or '')..(inherit and (':'..inherit) or '')..'){'..
table.concat(propsT)..'};')
local obj = toTsObject(objS)
if not obj then
error('bl.new/datablock: failed to create object', 3) end
return obj
end
local function parseTsDecl(decl)
local class, name, inherit
if decl:find(' ') then -- class ...
local cl, rest = decl:match('^([^ ]+) ([^ ]+)$')
class = cl
if rest:find(':') then -- class name:inherit
name, inherit = rest:match('^([^:]*):([^:]+)$')
if not name then class = nil end -- error
if name=='' then name = nil end -- class :inherit
else
name = rest
end
else -- class
class = decl
end
if not (
isValidFuncName(class) and
(name==nil or isValidFuncName(name)) and
(inherit==nil or isValidFuncName(inherit)) ) then
error('bl.new/datablock: invalid decl \''..decl..'\'\n'..
'must be of the format: \'className\', \'className name\', '..
'\'className :inherit\', or \'className name:inherit\'', 3) end
return class, name, inherit
end
function bl.new(decl, props)
local class, name, inherit = parseTsDecl(decl)
return createTsObj('new', class, name, inherit, props)
end
function bl.datablock(decl, props)
local class, name, inherit = parseTsDecl(decl)
return createTsObj('datablock', class, name, inherit, props)
end
setmetatable(bl, tsMeta)
print(' Executed libbl.lua')

52
src/util/libts-support.cs Normal file
View File

@@ -0,0 +1,52 @@
// Read an entire file as text and return its contents as a string
// Used for reading files from zips
function _bllua_ReadEntireFile(%fn) {
%text = "";
%file = new FileObject();
%file.openForRead(%fn);
while (!%file.isEOF()) { %text = %text @ %file.readLine() @ "\r\n"; }
%file.close();
%file.delete();
return %text;
}
// Hack to create/set global variables
// since there's no easy way to do this from the DLL directly
function _bllua_set_var(%name, %val) {
%first = strLwr(getSubStr(%name, 0, 1));
%rest = getSubStr(%name, 1, strLen(%name));
switch$(%first) {
case "a": $a[%rest] = %val; return;
case "b": $b[%rest] = %val; return;
case "c": $c[%rest] = %val; return;
case "d": $d[%rest] = %val; return;
case "e": $e[%rest] = %val; return;
case "f": $f[%rest] = %val; return;
case "g": $g[%rest] = %val; return;
case "h": $h[%rest] = %val; return;
case "i": $i[%rest] = %val; return;
case "j": $j[%rest] = %val; return;
case "k": $k[%rest] = %val; return;
case "l": $l[%rest] = %val; return;
case "m": $m[%rest] = %val; return;
case "n": $n[%rest] = %val; return;
case "o": $o[%rest] = %val; return;
case "p": $p[%rest] = %val; return;
case "q": $q[%rest] = %val; return;
case "r": $r[%rest] = %val; return;
case "s": $s[%rest] = %val; return;
case "t": $t[%rest] = %val; return;
case "u": $u[%rest] = %val; return;
case "v": $v[%rest] = %val; return;
case "w": $w[%rest] = %val; return;
case "x": $x[%rest] = %val; return;
case "y": $y[%rest] = %val; return;
case "z": $z[%rest] = %val; return;
case "_": $_[%rest] = %val; return;
}
error("_bllua_set_var: invalid variable name " @ %name);
return "";
}
echo(" Executed libts-support.cs");

212
src/util/libts.lua Normal file
View File

@@ -0,0 +1,212 @@
-- This Lua code provides some built-in utilities for writing Lua add-ons
-- It is eval'd automatically once BLLua3 has loaded the TS API and environment
-- It only has access to the sandboxed lua environment, just like user code.
ts = _bllua_ts
-- Provide limited OS functions
os = os or {}
function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime'))/1000) end
function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end
-- Virtual file class, emulating a file object as returned by io.open
-- Used to wrap io.open to allow reading from zips (using TS)
-- Not perfect because TS file I/O sucks
-- Can't read nulls, can't distinguish between CRLF and LF.
-- Todo someday: actually read the zip in lua?
local file_meta = {
read = function(file, mode)
file:_init()
if not file or type(file)~='table' or not file._is_file then error('File:read: Not a file', 2) end
if file._is_open ~= true then error('File:read: File is closed', 2) end
if mode=='*n' then
local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos)
if n then
file.pos = file.pos + #ws + #n
return n
else
return nil
end
elseif mode=='*a' then
local d = file.data:sub(file.pos, #file.data)
file.pos = #file.data + 1
return d
elseif mode=='*l' then
local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos)
if not l then
l = file.data:match('^([^\r\n]*)$', file.pos); ws = '';
if l=='' then return nil end
end
if l then
file.pos = file.pos + #l + #ws
return l
else
return nil
end
elseif type(mode)=='number' then
local d = file.data:sub(file.pos, file.pos+mode)
file.pos = file.pos + #d
return d
else
error('File:read: Invalid mode \''..mode..'\'', 2)
end
end,
lines = function(file)
file:_init()
return function()
return file:read('*l')
end
end,
close = function(file)
if not file._is_open then error('File:close: File is not open', 2) end
file._is_open = false
end,
__index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end,
_init = function(f)
if not f.data then
f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename)
end
end,
}
local function new_file_obj(fn)
local file = {
_is_file = true,
_is_open = true,
pos = 1,
__index = file_meta.__index,
filename = fn,
data = nil,
}
setmetatable(file, file_meta)
return file
end
local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
local allowed_zip_dirs = tflip{
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
}
local function io_open_absolute(fn, mode)
-- if file exists, use original mode
local res, err = _bllua_io_open(fn, mode)
if res then return res end
-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
local dir = fn:match('^[^/]+')
if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end
local exist = _bllua_ts.call('isFile', fn) == '1'
if not exist then return nil, err end
if mode~=nil and mode~='r' and mode~='rb' then
return nil, 'Files in zips can only be opened in read mode' end
-- return a temp lua file object with the data
local fi = new_file_obj(fn)
return fi
end
io = io or {}
function io.open(fn, mode, errn)
errn = errn or 1
-- try to open the file with relative path, otherwise use absolute path
local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File')
if curfn == '' then curfn = nil end
if fn:find('^%.') then
local relfn = curfn and fn:find('^%./') and
curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '')
if relfn then
local fi, err = io_open_absolute(relfn, mode, errn+1)
return fi, err, relfn
else
return nil, 'Invalid path', fn
end
else
local fi, err = io_open_absolute(fn, mode, errn+1)
return fi, err, fn
end
end
function io.lines(fn)
local fi, err, fn2 = io.open(fn, nil, 2)
if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end
return fi:lines()
end
function io.type(f)
if type(f)=='table' and f._is_file then
return f._is_open and 'file' or 'closed file'
else
return _bllua_io_type(f)
end
end
-- provide dofile
function dofile(fn, errn)
errn = errn or 1
local fi, err, fn2 = io.open(fn, 'r', errn+1)
if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end
print('Executing '..fn2)
local text = fi:read('*a')
fi:close()
return assert(loadstring('--[['..fn2..']]'..text))()
end
-- provide require (just a wrapper for dofile)
-- searches for ?.lua and ?/init.lua in the following directories:
-- location of current file
-- blockland directory
-- current add-on
local function file_exists(fn, errn)
local fi, err, fn2 = io.open(fn, 'r', errn+1)
if fi then
fi:close()
return fn2
else
return nil
end
end
function require(mod)
local fp = mod:gsub('%.', '/')
local fns = {
'./'..fp..'.lua', -- local file
'./'..fp..'/init.lua', -- local library
fp..'.lua', -- global file
fp..'/init.lua', -- global library
}
if fp:lower():find('^add-ons/') then
local addonpath = fp:lower():match('^add-ons/[^/]+')..'/'
table.insert(fns, addonpath..fp..'.lua') -- add-on file
table.insert(fns, addonpath..fp..'/init.lua') -- add-on library
end
for _,fn in ipairs(fns) do
local fne = file_exists(fn, 2)
if fne then
return dofile(fne, 2)
end
end
return _bllua_requiresecure(mod)
end
-- Exposure to TS
function _bllua_getvar(name) return _G[name] end
function _bllua_setvar(name, val) _G[name] = val end
function _bllua_eval(code) return loadstring(code)() end
function _bllua_exec(fn) return dofile(fn, 2) end
local function isValidCode(code)
local f,e = loadstring(code)
return f~=nil
end
function _bllua_smarteval(code)
if (not code:find('^print%(')) and isValidCode('print('..code..')') then
code = 'print('..code..')' end
local f,e = loadstring(code)
if f then
return f()
else
print(e)
end
end
_bllua_ts.call('echo', ' Executed libts.lua')

412
src/util/loadaddons.cs Normal file
View File

@@ -0,0 +1,412 @@
// Package to allow add-ons to use server.lua or client.lua
// instead of or in addition to server.cs or client.cs
// Relevant .lua files are is executed before .cs files.
function _bllua_strEndsWith(%str, %sch) {
%schL = strLen(%sch);
return getSubStr(%str, strLen(%str)-%schL, %schL) $= %sch;
}
//function _bllua_strRemoveEnd(%str, %sch) {
// %schL = strLen(%sch);
// return getSubStr(%str, 0, strLen(%str)-%schL);
//}
function _bllua_fileIsExecCs(%fn) {
return
_bllua_strEndsWith(%fn, "/server.cs" ) ||
_bllua_strEndsWith(%fn, "/server.lua") ||
_bllua_strEndsWith(%fn, "/client.cs" ) ||
_bllua_strEndsWith(%fn, "/client.lua");
}
function _bllua_execAddon(%dirName, %type) {
%i = 0;
%fnLua = "Add-Ons/" @ %dirName @ "/" @ %type @ ".lua";
if(isFile(%fnLua)) { luaexec(%fnLua); %i++; }
%fnCs = "Add-Ons/" @ %dirName @ "/" @ %type @ ".cs";
if(isFile(%fnCs )) { exec(%fnCs ); %i++; }
if(%i==0) {
error("Error Loading Add-On " @ %dirName @ ": Neither " @
%type @ ".cs nor " @ %type @ ".lua exist");
}
}
// Rewrite built-in functions that scan for server.cs or client.cs
// and make them scan for server.lua or client.lua as well
// Note: I had to completely override several large functions,
// many of which are highly redundant, because Badspot didn't know
// what functional decomposition was when he wrote this shit.
package _bllua_addon_exec {
function CustomGameGuiServer::populateAddOnList() {
deleteVariables("$CustomGameGuiServer::AddOn*");
$CustomGameGuiServer::AddOnCount = 0;
%pattern = "Add-Ons/*/server.*";
%filename = findFirstFile(%pattern);
while(isFile(%filename)) {
if(_bllua_fileIsExecCs(%filename)) {
%path = filePath(%filename);
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
if(!%seenDirName[%dirName]) {
%seenDirName[%dirName] = 1;
%varName = getSafeVariableName(%dirName);
if(isValidAddOn(%dirName, 1)) {
$CustomGameGuiServer::AddOn[$CustomGameGuiServer::AddOnCount] = %dirName;
$CustomGameGuiServer::AddOnCount++;
}
}
}
%filename = findNextFile(%pattern);
}
}
function GameModeGuiServer::GetMissingAddOns(%filename) {
if(!isFile(%filename)) {
error("ERROR: GameModeGuiServer::GetMissingAddOns(" @ %filename @ ") - file does not exist");
return 0;
}
%path = filePath(%filename);
%missingAddons = "";
%descriptionFile = %path @ "/description.txt";
%previewFile = %path @ "/preview.jpg";
%thumbFile = %path @ "/thumb.jpg";
%saveFile = %path @ "/save.bls";
%colorSetFile = %path @ "/colorSet.txt";
if(!isFile(%descriptionFile))
%missingAddons = %missingAddons TAB %descriptionFile;
if(!isFile(%previewFile))
%missingAddons = %missingAddons TAB %previewFile;
if(!isFile(%thumbFile))
%missingAddons = %missingAddons TAB %thumbFile;
if(!isFile(%saveFile))
%missingAddons = %missingAddons TAB %saveFile;
if(!isFile(%colorSetFile))
%missingAddons = %missingAddons TAB %colorSetFile;
%file = new FileObject(""){};
%file.openForRead(%filename);
while(!%file.isEOF()) {
%line = %file.readLine();
%label = getWord(%line, 0);
%value = trim(getWords(%line, 1, 999));
if(%label !$= "") {
if(getSubStr(%label, 0, 2) !$= "//") {
if(%label $= "ADDON") {
if(!isFile("Add-Ons/" @ %value @ "/description.txt") ||
(!isFile("Add-Ons/" @ %value @ "/server.cs" ) &&
!isFile("Add-Ons/" @ %value @ "/server.lua") ) ) {
if(strlen(%missingAddons) > 0)
%missingAddons = %missingAddons TAB %value;
else
%missingAddons = %value;
}
} else {
if(%label $= "MUSIC") {
if(!isFile("Add-Ons/Music/" @ %value @ ".ogg")) {
if(strlen(%missingAddons) > 0)
%missingAddons = %missingAddons TAB %value @ ".ogg";
else
%missingAddons = %value;
}
}
}
}
}
}
%file.close();
%file.delete();
return %missingAddons;
}
function loadAddOns() {
echo("");
updateAddOnList();
echo("--------- Loading Add-Ons (+BlockLua) ---------");
deleteVariables("$AddOnLoaded__*");
%dir = "Add-Ons/*/server.*";
%filename = findFirstFile(%dir);
%dirCount = 0;
if(isFile("Add-Ons/System_ReturnToBlockland/server.cs")) {
%dirNameList[%dirCount] = "System_ReturnToBlockland";
%dirCount++;
}
while(%filename !$= "") {
if(_bllua_fileIsExecCs(%filename)) {
%path = filePath(%filename);
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
if(!%seenDirName[%dirName]) {
%seenDirName[%dirName] = 1;
if(%dirName !$= "System_ReturnToBlockland") {
%dirNameList[%dirCount] = %dirName;
%dirCount++;
}
}
}
%filename = findNextFile(%dir);
}
for(%addOnItr = 0; %addOnItr < %dirCount; %addOnItr++) {
%dirName = %dirNameList[%addOnItr];
%varName = getSafeVariableName(%dirName);
if(!$Server::Dedicated) {
if(getRealTime() - $lastProgressBarTime > 200) {
LoadingProgress.setValue(%addOnItr / %dirCount);
$lastProgressBarTime = getRealTime();
Canvas.repaint();
}
}
if($AddOn__[%varName] $= 1 && isValidAddOn(%dirName)) {
if(%dirName $= "JVS_Content" && $AddOn__["Support_LegacyDoors"] $= 1) {
echo(" Skipping JVS_Content in favor of Support_LegacyDoors");
} else if(!$AddOnLoaded__[%varName]) {
$AddOnLoaded__[%varName] = 1;
%zipFile = "Add-Ons/" @ %dirName @ ".zip";
if(isFile(%zipFile)) {
%zipCRC = getFileCRC(%zipFile);
echo("\c5Loading Add-On: " @ %dirName @ " \c2(CRC:" @ %zipCRC @ ")");
} else {
echo("\c5Loading Add-On: " @ %dirName);
}
if(VerifyAddOnScripts(%dirName)==0) {
echo("\c3ADD-ON " @ %dirName @ " CONTAINS SYNTAX ERRORS\n");
} else {
%oldDBCount = DataBlockGroup.getCount();
_bllua_execAddon(%dirName, "server");
%dbDiff = DataBlockGroup.getCount() - %oldDBCount;
echo("\c2" @ %dbDiff @ " datablocks added.");
echo("");
}
}
}
}
echo("");
}
function loadGameModeAddOns() {
echo("");
echo("--------- Loading Add-Ons (Game Mode) (+BlockLua) ---------");
deleteVariables("$AddOnLoaded__*");
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
%dirName = $GameMode::AddOn[%i];
%varName = getSafeVariableName(%dirName);
if(!$Server::Dedicated) {
if(getRealTime() - $lastProgressBarTime > 200) {
LoadingProgress.setValue(%i / $GameMode::AddOnCount);
$lastProgressBarTime = getRealTime();
Canvas.repaint();
}
}
if(!isValidAddOn(%dirName)) {
error("ERROR: Invalid add-on \'" @ %dirName @ "\' specified for game mode \'" @ $GameModeArg @ "\'");
} else {
$AddOnLoaded__[%varName] = 1;
%zipFile = "Add-Ons/" @ %dirName @ ".zip";
if(isFile(%zipFile)) {
%zipCRC = getFileCRC(%zipFile);
echo("\c5Loading Add-On: " @ %dirName @ " \c2(CRC:" @ %zipCRC @ ")");
} else {
echo("\c5Loading Add-On: " @ %dirName);
}
if(VerifyAddOnScripts(%dirName) == 0) {
echo("\c3ADD-ON " @ %dirName @ " CONTAINS SYNTAX ERRORS\n");
} else {
%oldDBCount = DataBlockGroup.getCount();
_bllua_execAddon(%dirName, "server");
%dbDiff = DataBlockGroup.getCount() - %oldDBCount;
echo("\c2" @ %dbDiff @ " datablocks added.");
echo("");
}
}
}
echo("");
}
function loadClientAddOns() {
echo("");
echo("--------- Loading Client Add-Ons (+BlockLua) ---------");
if(isFile("base/server/crapOns_Cache.cs"))
exec("base/server/crapOns_Cache.cs");
%dir = "Add-Ons/*/client.*";
%filename = findFirstFile(%dir);
%dirCount = 0;
if(isFile("Add-Ons/System_ReturnToBlockland/client.cs")) {
%dirNameList[%dirCount] = "System_ReturnToBlockland";
%dirCount++;
}
while(%filename !$= "") {
if(_bllua_fileIsExecCs(%filename)) {
%path = filePath(%filename);
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
if(!%seenDirName[%dirName]) {
%seenDirName[%dirName] = 1;
if(%dirName !$= "System_ReturnToBlockland") {
%dirNameList[%dirCount] = %dirName;
%dirCount++;
}
}
}
%filename = findNextFile(%dir);
}
for(%i=0; %i<%dirCount; %i++) {
%dirName = %dirNameList[%i];
%varName = getSafeVariableName(%dirName);
echo("");
echo("Client checking Add-On: " @ %dirName);
if(!clientIsValidAddOn(%dirName, 1)) {
//deleteVariables("$AddOn__" @ %varName); // wtf
} else {
%name = %dirName;
%zipFile = "Add-Ons/" @ %dirName @ ".zip";
if(isFile(%zipFile)) {
%zipCRC = getFileCRC(%zipFile);
echo("\c5Loading Add-On: " @ %dirName @ " \c2(CRC:" @ %zipCRC @ ")");
} else {
echo("\c5Loading Add-On: " @ %dirName);
}
if(ClientVerifyAddOnScripts(%dirName)==0)
echo("\c3ADD-ON " @ %dirName @ " CONTAINS SYNTAX ERRORS\n");
else
_bllua_execAddon(%dirName, "client");
}
}
echo("");
}
function updateAddOnList() {
echo("\n--------- Updating Add-On List (+BlockLua) ---------");
deleteVariables("$AddOn__*");
if(isFile("config/server/ADD_ON_LIST.cs")) {
exec("config/server/ADD_ON_LIST.cs");
} else {
exec("base/server/defaultAddOnList.cs");
}
if(isFile("base/server/crapOns_Cache.cs")) {
exec("base/server/crapOns_Cache.cs");
}
%dir = "Add-Ons/*/server.*";
%fileCount = getFileCount(%dir);
%filename = findFirstFile(%dir);
while(%filename !$= "") {
if(_bllua_fileIsExecCs(%filename)) {
%path = filePath(%filename);
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
if(!%seenDirName[%dirName]) {
%seenDirName[%dirName] = 1;
%varName = getSafeVariableName(%dirName);
echo("Checking Add-On " @ %dirName);
if(!isValidAddOn(%dirName, 1)) {
deleteVariables("$AddOn__" @ %varName);
} else {
if (mFloor($AddOn__[%varName]) <= 0)
$AddOn__[%varName] = -1;
else
$AddOn__[%varName] = 1;
}
}
}
%filename = findNextFile(%dir);
}
echo("");
export("$AddOn__*", "config/server/ADD_ON_LIST.cs");
}
};
activatePackage(_bllua_addon_exec);
// Have to make new versions of these because packaging them is blocked FSFR
function forceRequiredAddOn_L(%dirName) {
if(%dirName $= "JVS_Content") {
if($GameModeArg $= "") {
if($AddOn__["Support_LegacyDoors"] == 1 || !isFile("add-ons/JVS_Content/server.cs") || ($AddOn__["Support_LegacyDoors"] != 1 && $AddOn__["JVS_Content"] != 1)) {
%dirName = "Support_LegacyDoors";
}
} else {
%foundJVSContent = 0;
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
if ($GameMode::AddOn[%i] $= "JVS_Content") {
%foundJVSContent = 1;
}
}
if(!%foundJVSContent)
%dirName = "Support_LegacyDoors";
}
}
if(strstr(%dirName, " ") != -1)
%dirName = strreplace(%dirName, " ", "_");
//if(strstr(%dirName, "/") != -1)
// return $Error::AddOn_Nested;
%varName = getSafeVariableName(%dirName);
if($GameModeArg !$= "") {
%foundIt = 0;
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
if ($GameMode::AddOn[%i] $= %dirName) {
%foundIt = 1;
break;
}
}
if(!%foundIt) {
error("ERROR: ForceRequiredAddOn_L(\'" @ %dirName @ "\') - you can\'t force load an add-on that is not included in gamemode.txt");
if (GameWindowExists() && !$Server::Dedicated) {
schedule(11, 0, MessageBoxOK, "Game Mode Error", "Required add-on " @ %dirName @ " should be added to gamemode.txt");
}
if (!isEventPending($disconnectEvent)) {
$disconnectEvent = schedule(10, 0, disconnect);
}
return $Error::AddOn_NotFound;
}
}
if($AddOnLoaded__[%varName] == 1)
return $Error::None;
if($AddOn__[%varName] $= "" && $GameModeArg $= "" || !isValidAddOn(%dirName)) {
error("ERROR: ForceRequiredAddOn() - " @ %dirName @ " is not a valid add-on");
return $Error::AddOn_NotFound;
}
echo(" Loading Add-On " @ %dirName @ "");
_bllua_execAddon(%dirName, "server");
$AddOnLoaded__[%varName] = 1;
if($AddOn__[%varName] $= 1)
return $Error::None;
else
return $Error::AddOn_Disabled;
}
function loadRequiredAddOn_L(%dirName) {
if(%dirName $= "JVS_Content") {
if($GameModeArg $= "") {
if($AddOn__["Support_LegacyDoors"] == 1 || !isFile("add-ons/JVS_Content/server.cs") || ($AddOn__["Support_LegacyDoors"] != 1 && $AddOn__["JVS_Content"] != 1)) {
%dirName = "Support_LegacyDoors";
}
} else {
%foundJVSContent = 0;
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
if ($GameMode::AddOn[%i] $= "JVS_Content") {
%foundJVSContent = 1;
}
}
if(!%foundJVSContent)
%dirName = "Support_LegacyDoors";
}
}
if(strstr(%dirName, " ") != -1)
%dirName = strreplace(%dirName, " ", "_");
//if(strstr(%dirName, "/") != -1)
// return $Error::AddOn_Nested;
%varName = getSafeVariableName(%dirName);
if ($GameModeArg !$= "") {
%foundIt = 0;
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
if ($GameMode::AddOn[%i] $= %dirName) {
%foundIt = 1;
break;
}
}
if(!%foundIt) {
error("ERROR: LoadRequiredAddOn_L(\'" @ %dirName @ "\') - you can\'t force load an add-on that is not included in gamemode.txt");
if (GameWindowExists() && !$Server::Dedicated)
schedule(11, 0, MessageBoxOK, "Game Mode Error", "Required add-on " @ %dirName @ " should be added to gamemode.txt");
if (!isEventPending($disconnectEvent))
$disconnectEvent = schedule(10, 0, disconnect);
return $Error::AddOn_NotFound;
}
}
if($AddOnLoaded__[%varName] == 1)
return $Error::None;
if($AddOn__[%varName] $= 1) {
echo(" Loading Add-On " @ %dirName @ "");
_bllua_execAddon(%dirName, "server");
$AddOnLoaded__[%varName] = 1;
return $Error::None;
} else {
return $Error::AddOn_Disabled;
}
}
echo(" Executed loadaddons.cs");

360
src/util/std.lua Normal file
View File

@@ -0,0 +1,360 @@
-- Basic functionality that should be standard in Lua
-- Table / List
-- Whether a table contains no keys
function table.empty(t)
for _,_ in pairs(t) do return false end
return true
end
-- Apply a function to each key in a table
function table.map(f, ...)
local ts = {...}
local u = {}
for k,_ in pairs(ts[1]) do
local args = {}
for j=1,#ts do args[j] = ts[j][i] end
u[i] = f(unpack(args))
end
return u
end
function table.map_list(f, ...)
local ts = {...}
local u = {}
for i=1,#ts[1] do
local args = {}
for j=1,#ts do args[j] = ts[j][i] end
u[i] = f(unpack(args))
end
return u
end
-- Swap keys/values
function table.swap(t)
local u = {}
for k,v in pairs(t) do u[v] = k end
return u
end
-- Reverse a list
function table.reverse(l)
local m = {}
for i=1,#l do m[#l-i+1] = l[i] end
return m
end
-- Convert i->v to v->true
function table.values(l)
local u = {}
for _,v in ipairs(l) do u[v] = true end
return u
end
-- Make a list of keys
function table.keys(t)
local u = {}
for k,_ in pairs(t) do table.insert(u, k) end
return u
end
-- Whether a table is a list/array (has only monotonic integer keys)
function table.islist(t)
local n = 0
for i,_ in pairs(t) do
if type(i)~='number' or i%1~=0 then return false end
n = n+1
end
return n==#t
end
-- Append contents of other tables to first table
function table.append(t, ...)
local a = {...}
for _,u in ipairs(a) do
for _,v in ipairs(u) do table.insert(t,v) end
end
return t
end
-- Create a new table containing all keys from any number of tables
-- latter tables in the arg list override prior ones
-- overlaps, NOT appends, integer keys
function table.join(...)
local ts = {...}
local w = {}
for _,t in ipairs(ts) do
for k,v in pairs(t) do w[k] = v end
end
return w
end
-- Whether a table contains a certain value in any key
function table.contains(t,s)
for _,v in pairs(t) do
if v==s then return true end
end
return false
end
function table.contains_list(t,s)
for _,v in ipairs(t) do
if v==s then return true end
end
return false
end
-- Copy a table to another table
function table.copy(t)
local u = {}
for k,v in pairs(t) do u[k] = v end
return u
end
function table.copy_list(l)
local m = {}
for i,v in ipairs(l) do m[i] = v end
return m
end
-- Sort a table in a new copy
function table.sortcopy(t, f)
local u = table.copy_list(t)
table.sort(u, f)
return u
end
-- Remove a value from a table
function table.removevalue(t, r)
local rem = {}
for k,v in pairs(t) do
if v==r then table.insert(rem, k) end
end
for _,k in ipairs(rem) do t[k] = nil end
end
function table.removevalue_list(t, r)
for i = #t, 1, -1 do
if t[i]==r then
table.remove(t, i)
end
end
end
-- Export tables into formatted executable strings
local function tabs(tabLevel)
return (' '):rep(tabLevel)
end
local valueToString
local function tableToString(t, tabLevel, seen)
if type(t)~='table' or (getmetatable(t) and getmetatable(t).__tostring) then
return tostring(t)
elseif table.islist(t) then
if #t==0 then
return '{}'
else
local strs = {}
local containsTables = false
for _,v in ipairs(t) do
if type(v)=='table' then containsTables = true end
table.insert(strs, valueToString(v, tabLevel+1, seen)..',')
end
if containsTables or #t>3 then
return '{\n'..tabs(tabLevel+1)
..table.concat(strs, '\n'..tabs(tabLevel+1))
..'\n'..tabs(tabLevel)..'}'
else
return '{ '..table.concat(strs, ' ')..' }'
end
end
else
local containsNonStringKeys = false
for k,v in pairs(t) do
if type(k)~='string' or k:find('[^a-zA-Z0-9_]') then
containsNonStringKeys = true
elseif type(k)=='table' then
error('table.tostring: table contains a table as key, cannot serialize')
end
end
local strs = {}
if containsNonStringKeys then
for k,v in pairs(t) do
table.insert(strs, '\n'..tabs(tabLevel+1)
..'['..valueToString(k, tabLevel+1, seen)..'] = '
..valueToString(v, tabLevel+1, seen)..',')
end
else
for k,v in pairs(t) do
table.insert(strs, '\n'..tabs(tabLevel+1)
..k..' = '..valueToString(v, tabLevel+1, seen)..',')
end
end
return '{'..table.concat(strs)..'\n'..tabs(tabLevel)..'}'
end
end
valueToString = function(v, tabLevel, seen)
local t = type(v)
if t=='table' then
if seen[v] then
return 'nil --[[ already seen: '..tostring(v)..' ]]'
else
seen[v] = true
return tableToString(v, tabLevel, seen)
end
elseif t=='string' then
return '\''..string.escape(v)..'\''
elseif t=='number' or t=='boolean' then
return tostring(v)
else
--error('table.tostring: table contains a '..t..' value, cannot serialize')
return 'nil --[[ cannot serialize '..t..': '..tostring(v)..' ]]'
end
end
function table.tostring(t)
return tableToString(t, 0, {})
end
-- String
-- Split string into table by separator
-- or by chars if no separator given
-- if regex is not true, sep is treated as a regex pattern
function string.split(str, sep, noregex)
if type(str)~='string' then
error('string.split: argument #1: expected string, got '..type(str), 2) end
if sep==nil or sep=='' then
local t = {}
local ns = #str
for x = 1, ns do
table.insert(t, str:sub(x, x))
end
return t
elseif type(sep)=='string' then
local t = {}
if #str>0 then
local first = 1
while true do
local last, newfirst = str:find(sep, first, noregex)
if not last then break end
table.insert(t, str:sub(first, last-1))
first = newfirst+1
end
table.insert(t, str:sub(first, #str))
end
return t
else
error(
'string.split: argument #2: expected string or nil, got '..type(sep), 2)
end
end
-- Split string to a list of char bytes
function string.bytes(s)
local b = {}
for i=1,#s do
local c = s:sub(i,i)
table.insert(b, c:byte())
end
return b
end
-- Trim leading and trailing whitespace
function string.trim(s, ws)
ws = ws or '[ \t\r\t]'
return s:gsub('^'..ws..'+', ''):gsub(ws..'+$', '')..''
end
-- String slicing and searching using [] operator
local str_meta = getmetatable('')
local str_meta_index_old= str_meta.__index
function str_meta.__index(s,k)
if type(k)=='string' then
return str_meta_index_old[k]
elseif type(k)=='number' then
if k<0 then k = #s+k+1 end
return string.sub(s,k,k)
elseif type(k)=='table' then
local a = k[1]<0 and (#s+k[1]+1) or k[1]
local b = k[2]<0 and (#s+k[2]+1) or k[2]
return string.sub(s,a,b)
end
end
-- String iterator
function string.chars(s)
local i = 0
return function()
i = i+1
if i<=#s then return s:sub(i,i)
else return nil end
end
end
-- Escape sequences
local defaultEscapes = {
['\\'] = '\\\\',
['\''] = '\\\'',
['\"'] = '\\\"',
['\t'] = '\\t',
['\r'] = '\\r',
['\n'] = '\\n',
['\0'] = '\\0',
}
function string.escape(s, escapes)
escapes = escapes or defaultEscapes
local t = {}
for i=1,#s do
local c = s:sub(i,i)
table.insert(t, escapes[c] or c)
end
return table.concat(t)
end
local defaultEscapeChar = '\\'
local defaultUnescapes = {
['\\'] = '\\',
['\''] = '\'',
['\"'] = '\"',
['t'] = '\t',
['r'] = '\r',
['n'] = '\n',
['0'] = '\0',
}
function string.unescape(s, escapeChar, unescapes)
escapeChar = escapeChar or defaultEscapeChar
unescapes = unescapes or defaultUnescapes
local t = {}
local inEscape = false
for i=1,#s do
local c = s:sub(i,i)
if inEscape then
table.insert(t, unescapes[c]
or error('string.unescape: invalid escape sequence: \''
..escapeChar..c..'\''))
elseif c==escapeChar then
inEscape = true
else
table.insert(t, c)
end
end
return table.concat(t)
end
-- IO
io = io or {}
-- Read entire file at once, return nil,err if access failed
function io.readfile(filename)
local fi,err = io.open(filename, 'rb')
if not fi then return nil,err end
local s = fi:read("*a")
fi:close()
return s
end
-- Write data to file all at once, return true if success / false,err if failure
function io.writefile(filename, data)
local fi,err = io.open(filename, 'wb')
if not fi then return false,err end
fi:write(data)
fi:close()
return true,nil
end
-- Math
-- Round
function math.round(x)
return math.floor(x+0.5)
end
-- Mod that accounts for floating point inaccuracy
function math.mod(a,b)
local m = a%b
if m==0 or math.abs(m)<1e-15 or math.abs(m-b)<1e-15 then return 0
else return m end
end
-- Clamp value between min and max
function math.clamp(v, n, x)
return math.min(x, math.max(v, n))
end

219
src/util/vector.lua Normal file
View File

@@ -0,0 +1,219 @@
-- Vector math class with operators
local vector_meta
local vector_new
local function vector_check(v, n, name, argn)
if not v.__is_vector then
error('vector '..name..': argument #'..(argn or 1)
..': expected vector, got '..type(v), n+1) end
end
local function vector_checksamelen(v1, v2, name)
vector_check(v1, 3, name, 1)
vector_check(v2, 3, name, 2)
if #v1~=#v2 then
error('vector '..name..': vector lengths do not match (lengths are '
..#v1..' and '..#v2..')', 3) end
return #v1
end
local function vector_checklen(v1, v2, name, len)
vector_check(v1, 3, name, 1)
vector_check(v2, 3, name, 2)
if #v1~=len or #v2~=len then
error('vector '..name..': vector lengths are not '..len..' (lengths are '
..#v1..' and '..#v2..')', 3) end
end
local function vector_opnnn(name, op)
return function(v1, v2)
local len = vector_checksamelen(v1, v2, name)
local v3 = {}
for i = 1, len do
v3[i] = op(v1[i], v2[i])
end
return vector_new(v3)
end
end
local function vector_opnxn(name, op)
return function(v1, v2)
local v1v = type(v1)=='table' and v1.__is_vector
local v2v = type(v2)=='table' and v2.__is_vector
if v1v and v2v then
local len = vector_checksamelen(v1, v2, name)
local v3 = {}
for i = 1, len do
v3[i] = op(v1[i], v2[i])
end
return vector_new(v3)
else
if v2v then v1,v2 = v2,v1 end
local len = #v1
local v3 = {}
for i = 1, len do
v3[i] = op(v1[i], v2)
end
return vector_new(v3)
end
end
end
local function vector_opn0n(name, op)
return function(v1)
--vector_check(v1, 1, name)
local len = #v1
local v2 = {}
for i = 1, len do
v2[i] = op(v1[i])
end
return vector_new(v2)
end
end
local vector_indices = {x = 1, y = 2, z = 3, w = 4, r = 1, g = 2, b = 3, a = 4}
local vector_meta = {
__is_vector = true,
__index = function(t, k)
if tonumber(k) then return rawget(t, k)
elseif vector_indices[k] then return rawget(t, vector_indices[k])
else return getmetatable(t)[k]
end
end,
__newindex = function(t, k, v)
if tonumber(k) then rawset(t, k, v)
elseif vector_indices[k] then rawset(t, vector_indices[k], v)
else return
end
end,
__add = vector_opnnn('add', function(x1, x2) return x1+x2 end),
__sub = vector_opnnn('sub', function(x1, x2) return x1-x2 end),
__mul = vector_opnxn('mul', function(x1, x2) return x1*x2 end),
__div = vector_opnxn('div', function(x1, x2) return x1/x2 end),
__pow = vector_opnxn('pow', function(x1, x2) return x1^x2 end),
__unm = vector_opn0n('inv', function(x1) return -x1 end),
__concat = nil,
--__len = function(v1) return #v1 end,
__len = nil,
__eq = function(v1, v2)
local len = vector_checksamelen(v1, v2, 'equals')
for i = 1, len do
if v1[i]~=v2[i] then return false end
end
return true
end,
__lt = nil,
__le = nil,
__call = nil,
abs = vector_opn0n('abs', math.abs),
length = function(v1)
--vector_check(v1, 2, 'length')
local len = #v1
local l = 0
for i = 1, len do
l = l + v1[i]^2
end
return math.sqrt(l)
end,
normalize = function(v1)
--vector_check(v1, 2, 'normal')
local length = v1:length()
local len = #v1
local v3 = {}
for i = 1, len do
if length==0 then v3[i] = 0
else v3[i] = v1[i]/length end
end
return vector_new(v3)
end,
__tostring = function(v1)
--vector_check(v1, 2, 'tostring')
local st = {}
local len = #v1
for i = 1, len do
table.insert(st, tostring(v1[i]))
end
return '{ '..table.concat(st, ', ')..' }'
end,
unpack = function(v1) return unpack(v1) end,
floor = vector_opn0n('floor', function(x1) return math.floor(x1) end),
ceil = vector_opn0n('ceil' , function(x1) return math.ceil (x1) end),
round = vector_opn0n('round', function(x1) return math.floor(x1+0.5) end),
dot = function(v1, v2)
local len = vector_checksamelen(v1, v2, 'dot')
local x = 0
for i = 1, len do
x = x + v1[i]*v2[i]
end
return x
end,
cross = function(v1, v2)
vector_checklen(v1, v2, 'cross', 3)
return vector_new{
v1[2]*v2[3] - v1[3]*v2[2],
v1[3]*v2[1] - v1[1]*v2[3],
v1[1]*v2[2] - v1[2]*v2[1],
}
end,
rotateByAngleId = function(v1, r)
--vector_check(v1, 2, 'rotate')
if type(r)~='number' or r%1~=0 then
error('vector rotateByAngleId: invalid rotation '..tostring(r), 2) end
r = r%4
local v2
if r==0 then v2 = vector_new{ v1[1], v1[2], v1[3] }
elseif r==1 then v2 = vector_new{ v1[2], -v1[1], v1[3] }
elseif r==2 then v2 = vector_new{ -v1[1], -v1[2], v1[3] }
elseif r==3 then v2 = vector_new{ -v1[2], v1[1], v1[3] }
else error('vector rotateByAngleId: invalid rotation '..r, 2) end
return v2
end,
rotate2d = function(v, r)
--vector_check(v, 2, 'rotate2d')
if type(r)~='number' then
error('vector rotate2d: invalid rotation '..tostring(r), 2) end
local len = math.sqrt(v[1]^2 + v[2]^2)
local ang = math.atan2(v[2], v[1]) + r
local v2 = vector_new{ math.cos(ang)*len, math.sin(ang)*len }
return v2
end,
tsString = function(v)
--vector_check(v, 2, 'tsString')
return table.concat(v, ' ')
end,
distance = function(v1, v2)
local len = vector_checksamelen(v1, v2, 'distance')
local sum = 0
for i=1,len do
sum = sum + (v1[i] - v2[i])^2
end
return math.sqrt(sum)
end,
copy = function(v)
--vector_check(v, 2, 'copy')
return vector_new(v)
end,
}
vector_new = function(vi)
if vi then
if type(vi)=='string' then
local vi2 = {}
for val in vi:gmatch('[0-9%.%-e]+') do
table.insert(vi2, tonumber(val))
end
vi = vi2
elseif type(vi)~='table' then
error('vector: argument #1: expected input table, got '..type(vi), 2)
end
local v = {}
if #vi>0 then
for i = 1, #vi do v[i] = vi[i] end
else
for n, i in pairs(vector_indices) do v[i] = vi[n] end
if #v==0 then
error('vector: argument #1: table contains no values', 2)
end
end
setmetatable(v, vector_meta)
return v
else
error('vector: argument #1: expected input table, got nil', 2)
end
end
vector = vector_new
return vector_new