1
0
forked from redo/BlockLua

Compare commits

...

16 Commits

12 changed files with 1222 additions and 1044 deletions

Binary file not shown.

View File

@@ -5,13 +5,13 @@ project(BlockLua CXX)
# Export compile_commands.json for VSCode IntelliSense
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Output directories to mirror compile.bat's build folder
# Output directories to mirror build.bat's build folder
set(OUTPUT_DIR ${CMAKE_SOURCE_DIR}/build)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR})
# Global compile options to mirror compile.bat
# Global build options to mirror build.bat
add_compile_options(
-Wall
-Werror
@@ -27,14 +27,14 @@ include_directories(
${CMAKE_SOURCE_DIR}/inc/lua
)
# Link directories (for -L.) and libraries from compile.bat
# Link directories (for -L.) and libraries from build.bat
link_directories(
${CMAKE_SOURCE_DIR}
)
# Safe DLL
add_library(BlockLua SHARED src/bllua4.cpp)
# Ensure output name matches compile.bat
# Ensure output name matches build.bat
set_target_properties(BlockLua PROPERTIES OUTPUT_NAME "BlockLua")
# Linker flags and libraries
if(MSVC)

View File

@@ -1,7 +1,7 @@
@echo off
cd /d %~dp0
REM Ensure MinGW32 toolchain is first in PATH (matches compile.bat)
REM Ensure MinGW32 toolchain is first in PATH
set "PATH=C:\msys64\mingw32\bin;%PATH%"
REM Configure CMake (generate into build/)

View File

@@ -1,13 +0,0 @@
@echo off
cd /d %~dp0
set "PATH=C:\msys64\mingw32\bin;%PATH%"
if not exist build mkdir build
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 build\BlockLua.dll
g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o build\BlockLua-Unsafe.dll
@echo off

View File

@@ -27,7 +27,7 @@ What these packages are for:
- Run the script:
```powershell
compile.bat
build.bat
```
What the script does:
@@ -48,5 +48,5 @@ You should see `architecture: i386` in the output.
### Notes
- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages wont work for a 32-bit build.
- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages won't work for a 32-bit build.
- If the linker cannot find `-llua5.1`, confirm `mingw-w64-i686-lua51` is installed and you are using the `mingw32` toolchain (not `x86_64`).

349
readme.md
View File

@@ -1,215 +1,240 @@
# BlockLua
Lua scripting for Blockland
## How to Install
- Install RedBlocklandLoader
- Copy `lua5.1.dll` into your Blockland install folder, next to `Blockland.exe`
- Copy `BlockLua.dll` into the `modules` folder within the Blockland folder
- Install RedBlocklandLoader
- Copy `lua5.1.dll` into your Blockland install folder, next to `Blockland.exe`
- Copy `BlockLua.dll` into the `modules` folder within the Blockland folder
## Quick Reference
### From TorqueScript
`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
`luaeval("code");` - Execute Lua code
`luacall("funcName", %args...);` - Call a Lua global function
`luaexec("fileName");` - Execute a Lua file. Path rules are the same as executing .cs files.
`luaget("varName");` - Read a Lua global variable
`luaset("varName", %value);` - Write a Lua global variable
`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
`luaeval("code");` - Execute Lua code
`luacall("funcName", %args...);` - Call a Lua function (supports indexing tables and object methods)
`luaexec("fileName");` - Execute a Lua file. Path rules are the same as when executing .cs files, relative paths are allowed.
`luaget("varName");` - Read a Lua global variable (supports indexing tables)
`luaset("varName", %value);` - Write a Lua global variable (supports indexing tables)
### From Lua
`bl.eval('code')` - Eval TorqueScript code
`bl.funcName(args)` - Call a TorqueScript function
`bl.varName` - Read a TorqueScript global variable
`bl['varName']` - Read a TorqueScript global variable (i.e. with special characters in the name, or from an array)
`bl.set('varName', value)` - Write a TorqueScript global variable
`bl.eval('code')` - Eval TorqueScript code
`bl.funcName(args)` - Call a TorqueScript function
`bl.varName` - Read a TorqueScript global variable
`bl['varName']` - Read a TorqueScript global variable (i.e. with special characters in the name, or from an array)
`bl.set('varName', value)` - Write a TorqueScript global variable
`bl['namespaceName::funcName'](args)` - Call a namespaced TorqueScript function
### Accessing Torque Objects from Lua
`bl.objectName` - Access a Torque object by name
`bl[objectID]` - Access a Torque object by ID (or name)
`object.fieldOrKey` - Read a field or Lua key from a Torque object
`object:set('field', value)` - Write a field on a Torque object
`object.key = value` - Associate Lua data with a Torque object
`object:method(args)` - Call a Torque object method
`object[index]` - Access a member of a Torque set or group
`for childIndex, child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque.
`bl.isObject(object, objectID, or 'objectName')` - Check if an object exists
`object:exists()` - Check if an object exists
`bl.objectName` - Access a Torque object by name
`bl[objectID]` - Access a Torque object by ID (or name)
`object.fieldOrKey` - Read a field or Lua key from a Torque object
`object:set('field', value)` - Write a field on a Torque object
`object.key = value` - Associate Lua data with a Torque object
`object:method(args)` - Call a Torque object method
`object[index]` - Access a member of a Torque set or group
`for child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque.
`bl.isObject(object, objectID, or 'objectName')` - Check if an object exists
`object:exists()` - Check if an object exists
### Timing/Schedules
`sched = bl.schedule(timeMs, function, args...)` - Schedule a Lua function to be called later, similar to schedule in Torque
`sched:cancel()` - Cancel a previously scheduled timer
`sched = bl.schedule(timeMs, function, args...)` - Schedule a Lua function to be called later, similar to schedule in Torque
`sched:cancel()` - Cancel a previously scheduled timer
### Raycasts and Searches
`hitObject, hitPos, hitNormal = bl.raycast(vector{startPosX,y,z}, vector{endPosX,y,z}, 'objtype'/{'objtypes',...}, ignoreObjects...?)` - Cast a ray in the world over objects of the specified type(s) (possibly excluding some objects), and return the object hit, the position of the hit, and the normal vector to the surface hit. See the Types section for a list of valid object types.
`for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types.
`for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types.
`hitObject, hitPos, hitNormal = bl.raycast(vector{startPosX,y,z}, vector{endPosX,y,z}, 'objtype'/{'objtypes',...}, ignoreObjects...?)` - Cast a ray in the world over objects of the specified type(s) (possibly excluding some objects), and return the object hit, the position of the hit, and the normal vector to the surface hit. See the Types section for a list of valid object types.
`for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types.
`for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types.
### List of Object Classes (for raycasts and searches)
`'all'` - Any object
`'player'` - Players or bots
`'item'` - Items
`'vehicle'` - Vehicles
`'projectile'` - Projectiles
`'brick'` - Bricks with raycasting enabled
`'brickalways'` - All bricks including those with raycasting disabled
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
### Server-Client Communication
`bl.addServerCmd('commandName', function(client, args...) yourCode end)` - Register a /command on the server
`bl.addClientCmd('commandName', function(args...) yourCode end)` - Register a client command on the client
`bl.commandToServer('commandName', args...)` - Execute a server command as a client
`bl.commandToClient('commandName', args...)` - As the server, execute a client command on a specific client
`bl.commandToAll('commandName', args...)` - As the server, execute a client command on all clients
`bl.addServerCmd('commandName', function(client, args...) ... end)` - Register a /command on the server
`bl.addClientCmd('commandName', function(args...) ... end)` - Register a client command on the client
`bl.commandToServer('commandName', args...)` - As a client, execute a server command
`bl.commandToClient(client, 'commandName', args...)` - As the server, execute a client command on a specific client
`bl.commandToAll('commandName', args...)` - As the server, execute a client command on all clients
### Packages/Hooks
`bl.hook('packageName', 'functionName', 'before'/'after', function(args) yourCode end)` - Hook a Torque function with a Lua function.
`args` is an array containing the arguments provided to the function. If the hook is `before`, these can be modified before being passed to the parent function.
If `args._return` is set to anything other than nil by a `before` hook, the parent function will not be called, and the function will simply return that value. Also in this case, any `after` hook will not be executed.
In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified.
`bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook
`bl.unhook('packageName', 'functionName')` - Remove any previously defined hooks on the function within the package
`bl.unhook('packageName')` - Remove any previously defined hooks within the package
`bl.hook('packageName', 'functionName', 'before'/'after', function(args) ... end)` - Hook a Torque function with a Lua function.
`args` is an array containing the arguments provided to the function. If the hook is `before`, these can be modified before being passed to the parent function.
If `args._return` is set to anything other than nil by a `before` hook, the parent function will not be called, and the function will simply return that value. Also in this case, any `after` hook will not be executed.
In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified.
`bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook
`bl.unhook('packageName', 'functionName')` - Remove any previously defined hooks on the function within the package
`bl.unhook('packageName')` - Remove any previously defined hooks within the package
### Modules and Dependencies
`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed.
`require('modulePath.moduleName')` - Load a Lua file or external library.
`require` replaces `.` with `/` in the path, and then searches for files in the following order:
- `./modulePath/moduleName.lua`
- `./modulePath/moduleName/init.lua`
- `modulePath/moduleName.lua` (Relative to game directory)
- `modulePath/moduleName/init.lua` (Relative to game directory)
- `modules/lualib/modulePath/moduleName.lua`
- `modules/lualib/modulePath/moduleName/init.lua`
- `modules/lualib/modulePath/moduleName.dll` - C libraries for Lua can be loaded
Like in standard Lua, modules loaded using `require` are only executed the first time `require` is called with that path (from anywhere). Subsequent calls simply return the result from the initial execution. To allow hot reloading, use `dofile`.
`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed.
`require('modulePath.moduleName')` - Load a Lua file or external library.
`require` replaces `.` with `/` in the path, and then searches for files in the following order:
- `./modulePath/moduleName.lua`
- `./modulePath/moduleName/init.lua`
- `modulePath/moduleName.lua` (Relative to game directory)
- `modulePath/moduleName/init.lua` (Relative to game directory)
- `modules/lualib/modulePath/moduleName.lua`
- `modules/lualib/modulePath/moduleName/init.lua`
- `modules/lualib/modulePath/moduleName.dll` - C libraries for Lua can be loaded
Like in standard Lua, modules loaded using `require` are only executed the first time `require` is called with that path (from anywhere). Subsequent calls simply return the result from the initial execution. To allow hot reloading, use `dofile`.
### File I/O
Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O.
Relative paths (`./`) are allowed. `..` is not allowed.
`file = io.open('./file.txt', 'r'/'w'/'a'/'rb'/'wb'/'ab')` - Open a file
`file:read(numberOfChars/'*a')` - Read an opened file (must be opened in 'r' (read) or 'rb' (read binary) mode)
`file:write(string)` - Write an opened file (must be opened in 'w' (write), 'a' (append), 'wb' or 'ab' mode)
`file:close()` - Close an opened file
Reading files from ZIPs is supported, with caveats. Null characters are not allowed, and \r\n becomes \n. Generally, text formats work, and binary formats don't.
Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O.
Relative paths (`./`) are allowed. `..` is not allowed.
`file = io.open('./file.txt', 'r'/'w'/'a'/'rb'/'wb'/'ab')` - Open a file
`file:read(numberOfChars/'*a')` - Read an opened file (must be opened in 'r' (read) or 'rb' (read binary) mode)
`file:write(string)` - Write an opened file (must be opened in 'w' (write), 'a' (append), 'wb' or 'ab' mode)
`file:close()` - Close an opened file
Reading files from ZIPs is supported, with caveats. Null characters are not allowed, and \r\n becomes \n. Generally, text formats work, and binary formats don't.
When reading from outside ZIPs, binary files are fully supported.
### Object Creation
`bl.new('className')` - Create a new Torque object
`bl.new('className', {fieldName = value, ...})` - Create a new Torque object with the given fields
`bl.new('className objectName', fields?)` - Create a new named Torque object
`bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance
`bl.datablock('datablockClassName datablockName', fields?)` - Create a new datablock
`bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance
`bl.new('className')` - Create a new Torque object
`bl.new('className', {fieldName = value, ...})` - Create a new Torque object with the given fields
`bl.new('className objectName', fields?)` - Create a new named Torque object
`bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance
`bl.datablock('datablockClassName datablockName', fields?)` - Create a new datablock
`bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance
### Classes and Types
`bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'boolean', 'object', 'string' (prevents automatic conversion), and nil (default, applies automatic conversion).
`bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all default functions.
`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.
`bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes.
`bl.class('className', 'parentClassName')` - Same as above, with inheritance
`bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean.
`bl.object(arg)` - Manually convert a Torque object reference (object ID or name) into a Lua object.
`bl.string(arg)` - Manually convert any automatically-converted Torque value back into a string. This is not as reliable as using `bl.type` to specify the type as a string beforehand.
`bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'boolean', 'object', 'string' (prevents automatic conversion), and nil (default, applies automatic conversion).
`bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all default functions.
`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.
`bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes.
`bl.class('className', 'parentClassName')` - Same as above, with inheritance
`bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean.
`bl.object(arg)` - Manually convert a Torque object reference (object ID or name) into a Lua object.
`bl.string(arg)` - Manually convert any automatically-converted Torque value back into a string. This is not as reliable as using `bl.type` to specify the type as a string beforehand.
### Vector
`vec = vector{x,y,z}` - Create a vector. Can have any number of elements
`vec1 + vec2` - Add
`vec1 - vec2` - Subtract
`vec * number` - Scale (x\*n, y\*n, z\*n)
`vec / number` - Scale (x/n, y/n, z/n)
`vec ^ number` - Exponentiate (x^n, y^n, z^n)
`vec1 * vec2` - Multiply elements piecewise (x1\*x2, y1\*y2, z1\*z2)
`vec1 / vec2` - Divide elements piecewise (x1/x2, y1/y2, z1/z2)
`vec1 ^ vec2` - Exponentiate elements piecewise (x1^x2, y1^y2, z1^z2)
`-vec` - Negate/invert
`vec1 == vec2` - Compare by value
`vec:length()` - Length
`vec:normalize()` - Preserve direction but scale so magnitude is 1
`vec:floor()` - Floor each element
`vec:ceil()` - Ceil each element
`vec:abs()` - Absolute value each element
`vec1:dot(vec2)` - Dot product
`vec1:cross(vec2)` - Cross product (Only defined for two vectors of length 3)
`vec:rotateZ(radians)` - Rotate counterclockwise about the Z (vertical) axis
`vec:rotateByAngleId(0/1/2/3)` - Rotate counterclockwise about the Z (vertical) axis in increments of 90 degrees
`vec:tsString()` - Convert to string usable by Torque. Done automatically when a vector is passed to Torque.
`vec1:distance(vec2)` - Distance between two points
`vec = vector{x,y,z}` - Create a vector. Can have any number of elements
`vec1 + vec2` - Add
`vec1 - vec2` - Subtract
`vec * number` - Scale (x\*n, y\*n, z\*n)
`vec / number` - Scale (x/n, y/n, z/n)
`vec ^ number` - Exponentiate (x^n, y^n, z^n)
`vec1 * vec2` - Multiply elements piecewise (x1\*x2, y1\*y2, z1\*z2)
`vec1 / vec2` - Divide elements piecewise (x1/x2, y1/y2, z1/z2)
`vec1 ^ vec2` - Exponentiate elements piecewise (x1^x2, y1^y2, z1^z2)
`-vec` - Negate/invert
`vec1 == vec2` - Compare by value
`vec:length()` - Length
`vec:normalize()` - Preserve direction but scale so magnitude is 1
`vec:floor()` - Floor each element
`vec:ceil()` - Ceil each element
`vec:abs()` - Absolute value each element
`vec1:dot(vec2)` - Dot product
`vec1:cross(vec2)` - Cross product (Only defined for two vectors of length 3)
`vec:rotateZ(radians)` - Rotate counterclockwise about the Z (vertical) axis
`vec:rotateByAngleId(0/1/2/3)` - Rotate counterclockwise about the Z (vertical) axis in increments of 90 degrees
`vec:tsString()` - Convert to string usable by Torque. Done automatically when a vector is passed to Torque.
`vec1:distance(vec2)` - Distance between two points
`vec2 = vec:copy()` - Clone a vector so its elements can be modified without affecting the original. Usually not needed - the builtin vector functions never modify vectors in-place.
### Matrix
WIP
### Extended Standard Lua Library
`string[index]`
`string[{start,stop}]`
`string.split(str, separator='' (splits into chars), noregex=false)`
`string.bytes(str)`
`string.trim(str, charsToTrim=' \t\r\n')`
`table.empty`
`table.map(func, ...)`
`table.map_list(func, ...)`
`table.swap(tbl)`
`table.reverse(list)`
`table.islist(list)`
`table.append(list, ...)`
`table.join(...)`
`table.contains(tbl, val)`
`table.contains_list(list, val)`
`table.copy(tbl)`
`table.copy_list(list)`
`table.sortcopy(tbl, sortFunction?)`
`table.removevalue(tbl, val)`
`table.removevalue_list(tbl, val)`
`table.tostring(tbl)`
`for char in string.chars(str) do`
`string.escape(str, escapes={['\n']='\\n', etc. (C standard)})`
`string.unescape(str, escapeChar='\\', unescapes={['\\n']='\n', etc.})`
`io.readall(filename)`
`io.writeall(filename, str)`
`math.round(num)`
`math.mod(divisor, modulus)`
`math.clamp(num, min, max)`
`str[index]`
`str[{start,stop}]`
`string.split(str, separator='' (splits into chars), noregex=false)`
`string.bytes(str)`
`string.trim(str, charsToTrim=' \t\r\n')`
`table.empty`
`table.map(func, ...)`
`table.mapk(func, ...)`
`table.map_list(func, ...)`
`table.mapi_list(func, ...)`
`table.swap(tbl)`
`table.reverse(list)`
`table.islist(list)`
`table.append(list, ...)`
`table.join(...)`
`table.contains(tbl, val)`
`table.contains_list(list, val)`
`table.copy(tbl)`
`table.copy_list(list)`
`table.sortcopy(tbl, sortFunction?)`
`table.removevalue(tbl, val)`
`table.removevalue_list(tbl, val)`
`table.tostring(tbl)`
`for char in string.chars(str) do`
`string.escape(str, escapes={['\n']='\\n', etc. (C standard)})`
`string.unescape(str, escapeChar='\\', unescapes={['\\n']='\n', etc.})`
`io.readall(filename)`
`io.writeall(filename, str)`
`math.round(num)`
`math.mod(divisor, modulus)`
`math.clamp(num, min, max)`
## Type Conversion
When a TorqueScript function is called from Lua or vice-versa, the arguments and return value must be converted between the two languages' type systems.
TorqueScript stores no type information; all values in TorqueScript are strings. So it's necessary to make some inferences when converting values between the two languages.
When a TorqueScript function is called from Lua or vice-versa, the arguments and return value must be converted between the two languages' type systems.
TorqueScript stores no type information; all values in TorqueScript are strings. So it's necessary to make some inferences when converting values between the two languages.
### From Lua to TorqueScript
- `nil` becomes the empty string ""
- `true` and `false` become "1" and "0" respectively
- Torque containers become their object ID
- A `vector` becomes a string containing three numbers separated by spaces
- A table of two vectors becomes a string containing six numbers separated by spaces
- Any `string` is passed directly as a string
- Tables cannot be passed and will throw an error
- `nil` becomes the empty string ""
- `true` and `false` become "1" and "0" respectively
- A Torque object container becomes its object ID
- A `vector` becomes a string containing three numbers separated by spaces
- A table of two `vector`s becomes a string containing six numbers separated by spaces
- (WIP) A `matrix` is converted into an axis-angle (a "transform"), a string containing seven numbers separated by spaces
- Any `string` is passed directly as a string
- Tables cannot be passed and will throw an error
### From TorqueScript to Lua
- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.
- The empty string "" becomes `nil`
- A string containing two or three numbers separated by single spaces becomes a `vector`
- A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box
- (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform" in TorqueScript parlance), and is converted into a `matrix` representing the translation and rotation.
- Any other string is passed directly as a `string`
For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`.
To convert objects by hand, use `bl.object`, `bl.boolean`, or `bl.string`.
- The empty string "" becomes `nil`
- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.
- A string containing two or three numbers separated by single spaces becomes a `vector`
- A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box
- (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform"), and is converted into a `matrix` representing the translation and rotation
- Any other string is passed directly as a `string`
For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`.
To convert things by hand, use `bl.object`, `bl.boolean`, or `bl.string`.
## I/O and Safety
All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.
BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there.
### Unsafe Mode
BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi.
Please do not publish add-ons that require unsafe mode.
### List of Object Types
`'all'` - Any object
`'player'` - Players or bots
`'item'` - Items
`'vehicle'` - Vehicles
`'projectile'` - Projectiles
`'brick'` - Bricks with raycasting enabled
`'brickalways'` - All bricks including those with raycasting disabled
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.
BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there.
### Unsafe Mode
BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi.
A more limited option is `-DBLLUA_ALLOWFFI`, which allows the use of the `ffi` library. This can still be exploited to grant all the same access as full unsafe mode.
Please do not publish add-ons that require either of these.
## Compiling
With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory:
`g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 src/bllua`
With any _32-bit_ variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory:
`g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1`
LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/
LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/

View File

@@ -1,4 +1,4 @@
// BlockLua (bllua4): Simple Lua interface for TorqueScript
// BlockLua (bllua4): Advanced Lua interface for TorqueScript
// Includes
@@ -60,23 +60,27 @@ bool init() {
// Set up Lua environment
BLL_LOAD_LUA(gL, bll_fileLuaEnv);
#ifdef BLLUA_ALLOWFFI
lua_pushboolean(gL, true);
lua_setglobal(gL, "_bllua_allowffi");
#endif
#ifndef BLLUA_UNSAFE
BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe);
#endif
// Load utilities in Lua
BLL_LOAD_LUA(gL, bll_fileLuaStd);
BLL_LOAD_LUA(gL, bll_fileLuaVector);
BLL_LOAD_LUA(gL, bll_fileLuaMatrix);
BLL_LOAD_LUA(gL, bll_fileLuaLibts);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
// 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_fileLuaMatrix);
BLL_LOAD_LUA(gL, bll_fileLuaLibts);
BlEval(bll_fileTsLibts);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
BlEval(bll_fileTsLibblSupport);
BlEval(bll_fileLoadaddons);
@@ -89,8 +93,7 @@ bool init() {
bool deinit() {
BlPrintf("BlockLua: Unloading");
BlEval("deactivatePackage(_bllua_main);");
BlEval("$_bllua_active = 0;");
BlEval("$_bllua_active=0;deactivatePackage(_bllua_main);");
bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end");
lua_close(gL);

View File

@@ -14,6 +14,7 @@ local old_require = require
local old_os = os
local old_debug = debug
local old_package = package
local old_allowffi = _bllua_allowffi
-- Remove all global variables except a whitelist
local ok_names = tmap {
@@ -39,13 +40,10 @@ end
-- Sanitize file paths to point only to allowed files within the game directory
-- List of allowed directories for reading/writing
-- modules/lualib is also allowed as read-only
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,
@@ -64,6 +62,9 @@ local disallowed_exts = tmap {
-- Return: clean file path if allowed (or nil if disallowed),
-- error string (or nil if allowed)
local function safe_path(fn, readonly)
if type(fn) ~= 'string' then
return nil, 'Filename must be a string'
end
fn = fn:gsub('\\', '/')
fn = fn:gsub('^ +', '')
fn = fn:gsub(' +$', '')
@@ -81,14 +82,15 @@ local function safe_path(fn, readonly)
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')
if not (dir and (
allowed_dirs[dir:lower()] or
(readonly and fn:find('^modules/lualib/'))))
then
return nil, 'File is in disallowed directory ' .. (dir or 'nil')
end
-- disallow blacklisted extensions or no extension
-- disallow blacklisted extensions
local ext = fn:match('%.([^/%.]+)$')
if (not ext) or (disallowed_exts[ext:lower()]) then
if ext and disallowed_exts[ext:lower()] then
return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' ..
(ext or '') .. '\''
end
@@ -120,6 +122,7 @@ local disallowed_packages = tmap {
'ffi', 'debug', 'package', 'io', 'os',
'_bllua_ts',
}
if old_allowffi then disallowed_packages['ffi'] = nil end
function _bllua_requiresecure(name)
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
name:find('^%.') or name:find('%.$') then

View File

@@ -18,6 +18,8 @@ end
-- Called when pcall fails on a ts->lua call, used to print detailed error info
function _bllua_on_error(err)
-- Convert error to string if it's not already
err = tostring(err)
err = err:match(': (.+)$') or err
local tracelines = { err }
local level = 2
@@ -25,7 +27,7 @@ function _bllua_on_error(err)
local info = debug.getinfo(level)
if not info then break end
local filename = debug.getfilename(level) or info.short_src
local funcname = info.name
local funcname = info.name or '<unknown>'
if funcname == 'dofile' then break end
table.insert(tracelines, string.format('%s:%s in function \'%s\'',
filename,
@@ -37,5 +39,9 @@ function _bllua_on_error(err)
return table.concat(tracelines, '\n')
end
-- overridden in lua-env-safe.lua (executed if not BLLUA_UNSAFE)
_bllua_io_open = io.open
_bllua_requiresecure = require
print = _bllua_ts.echo
print(' Executed bllua-env.lua')

File diff suppressed because it is too large Load Diff

View File

@@ -93,11 +93,15 @@ local allowed_zip_dirs = tflip {
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
if res then
return res
elseif err and not err:find('No such file or directory$') then
return nil, err
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
if not allowed_zip_dirs[dir:lower()] then return nil, 'Zip 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
@@ -142,9 +146,9 @@ end
---@diagnostic disable-next-line: duplicate-set-field
function io.type(f)
---@diagnostic disable-next-line: undefined-field
---@diagnostic disable-next-line: undefined-field
if type(f) == 'table' and f._is_file then
---@diagnostic disable-next-line: undefined-field
---@diagnostic disable-next-line: undefined-field
return f._is_open and 'file' or 'closed file'
else
return _bllua_io_type(f)
@@ -183,14 +187,14 @@ function require(mod)
if require_memo[mod] then return unpack(require_memo[mod]) end
local fp = mod:gsub('%.', '/')
local fns = {
'./' .. fp .. '.lua', -- local file
'./' .. fp .. '.lua', -- local file
'./' .. fp .. '/init.lua', -- local library
fp .. '.lua', -- global file
fp .. '/init.lua', -- global 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 .. '.lua') -- add-on file
table.insert(fns, addonpath .. fp .. '/init.lua') -- add-on library
end
for _, fn in ipairs(fns) do

View File

@@ -4,7 +4,7 @@
-- Table / List
-- Whether a table contains no keys
function table.empty(t)
return next(t) ~= nil
return next(t) == nil
end
-- Apply a function to each key in a table
@@ -13,8 +13,19 @@ function table.map(f, ...)
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))
for j = 1, #ts do args[j] = ts[j][k] end
u[k] = f(unpack(args))
end
return u
end
function table.mapk(f, ...)
local ts = { ... }
local u = {}
for k, _ in pairs(ts[1]) do
local args = {}
for j = 1, #ts do args[j] = ts[j][k] end
u[k] = f(k, unpack(args))
end
return u
end
@@ -30,6 +41,17 @@ function table.map_list(f, ...)
return u
end
function table.mapi_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(i, unpack(args))
end
return u
end
-- Swap keys/values
function table.swap(t)
local u = {}
@@ -193,7 +215,7 @@ valueToString = function(v, tabLevel, seen)
return tostring(v)
else
--error('table.tostring: table contains a '..t..' value, cannot serialize')
return 'nil --[[ cannot serialize ' .. tostring(v) .. ' ]]'
return 'nil --[[ ' .. tostring(v) .. ' ]]'
end
end
function table.tostring(t)