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 # Export compile_commands.json for VSCode IntelliSense
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 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(OUTPUT_DIR ${CMAKE_SOURCE_DIR}/build)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR})
set(CMAKE_ARCHIVE_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( add_compile_options(
-Wall -Wall
-Werror -Werror
@@ -27,14 +27,14 @@ include_directories(
${CMAKE_SOURCE_DIR}/inc/lua ${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( link_directories(
${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}
) )
# Safe DLL # Safe DLL
add_library(BlockLua SHARED src/bllua4.cpp) 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") set_target_properties(BlockLua PROPERTIES OUTPUT_NAME "BlockLua")
# Linker flags and libraries # Linker flags and libraries
if(MSVC) if(MSVC)

View File

@@ -1,7 +1,7 @@
@echo off @echo off
cd /d %~dp0 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%" set "PATH=C:\msys64\mingw32\bin;%PATH%"
REM Configure CMake (generate into build/) 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: - Run the script:
```powershell ```powershell
compile.bat build.bat
``` ```
What the script does: What the script does:
@@ -48,5 +48,5 @@ You should see `architecture: i386` in the output.
### Notes ### 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`). - 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 # BlockLua
Lua scripting for Blockland Lua scripting for Blockland
## How to Install ## 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 ## Quick Reference
### From TorqueScript ### From TorqueScript
`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
`luaeval("code");` - Execute Lua code `'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
`luacall("funcName", %args...);` - Call a Lua global function `luaeval("code");` - Execute Lua code
`luaexec("fileName");` - Execute a Lua file. Path rules are the same as executing .cs files. `luacall("funcName", %args...);` - Call a Lua function (supports indexing tables and object methods)
`luaget("varName");` - Read a Lua global variable `luaexec("fileName");` - Execute a Lua file. Path rules are the same as when executing .cs files, relative paths are allowed.
`luaset("varName", %value);` - Write a Lua global variable `luaget("varName");` - Read a Lua global variable (supports indexing tables)
`luaset("varName", %value);` - Write a Lua global variable (supports indexing tables)
### From Lua ### From Lua
`bl.eval('code')` - Eval TorqueScript code
`bl.funcName(args)` - Call a TorqueScript function `bl.eval('code')` - Eval TorqueScript code
`bl.varName` - Read a TorqueScript global variable `bl.funcName(args)` - Call a TorqueScript function
`bl['varName']` - Read a TorqueScript global variable (i.e. with special characters in the name, or from an array) `bl.varName` - Read a TorqueScript global variable
`bl.set('varName', value)` - Write 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 `bl['namespaceName::funcName'](args)` - Call a namespaced TorqueScript function
### Accessing Torque Objects from Lua ### Accessing Torque Objects from Lua
`bl.objectName` - Access a Torque object by name
`bl[objectID]` - Access a Torque object by ID (or name) `bl.objectName` - Access a Torque object by name
`object.fieldOrKey` - Read a field or Lua key from a Torque object `bl[objectID]` - Access a Torque object by ID (or name)
`object:set('field', value)` - Write a field on a Torque object `object.fieldOrKey` - Read a field or Lua key from a Torque object
`object.key = value` - Associate Lua data with a Torque object `object:set('field', value)` - Write a field on a Torque object
`object:method(args)` - Call a Torque object method `object.key = value` - Associate Lua data with a Torque object
`object[index]` - Access a member of a Torque set or group `object:method(args)` - Call a Torque object method
`for childIndex, child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque. `object[index]` - Access a member of a Torque set or group
`bl.isObject(object, objectID, or 'objectName')` - Check if an object exists `for child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque.
`object:exists()` - Check if an object exists `bl.isObject(object, objectID, or 'objectName')` - Check if an object exists
`object:exists()` - Check if an object exists
### Timing/Schedules ### 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 ### 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. `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.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. `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 ### 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.addServerCmd('commandName', function(client, args...) ... end)` - Register a /command on the server
`bl.commandToServer('commandName', args...)` - Execute a server command as a client `bl.addClientCmd('commandName', function(args...) ... end)` - Register a client command on the client
`bl.commandToClient('commandName', args...)` - As the server, execute a client command on a specific client `bl.commandToServer('commandName', args...)` - As a client, execute a server command
`bl.commandToAll('commandName', args...)` - As the server, execute a client command on all clients `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 ### 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. `bl.hook('packageName', 'functionName', 'before'/'after', function(args) ... end)` - Hook a Torque function with a Lua 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. `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.
In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified. 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.
`bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified.
`bl.unhook('packageName', 'functionName')` - Remove any previously defined hooks on the function within the package `bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook
`bl.unhook('packageName')` - Remove any previously defined hooks within the package `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 ### Modules and Dependencies
`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed.
`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: `require('modulePath.moduleName')` - Load a Lua file or external library.
- `./modulePath/moduleName.lua` `require` replaces `.` with `/` in the path, and then searches for files in the following order:
- `./modulePath/moduleName/init.lua`
- `modulePath/moduleName.lua` (Relative to game directory) - `./modulePath/moduleName.lua`
- `modulePath/moduleName/init.lua` (Relative to game directory) - `./modulePath/moduleName/init.lua`
- `modules/lualib/modulePath/moduleName.lua` - `modulePath/moduleName.lua` (Relative to game directory)
- `modules/lualib/modulePath/moduleName/init.lua` - `modulePath/moduleName/init.lua` (Relative to game directory)
- `modules/lualib/modulePath/moduleName.dll` - C libraries for Lua can be loaded - `modules/lualib/modulePath/moduleName.lua`
- `modules/lualib/modulePath/moduleName/init.lua`
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`. - `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 ### 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. Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O.
`file = io.open('./file.txt', 'r'/'w'/'a'/'rb'/'wb'/'ab')` - Open a file Relative paths (`./`) are allowed. `..` is not allowed.
`file:read(numberOfChars/'*a')` - Read an opened file (must be opened in 'r' (read) or 'rb' (read binary) mode) `file = io.open('./file.txt', 'r'/'w'/'a'/'rb'/'wb'/'ab')` - Open a file
`file:write(string)` - Write an opened file (must be opened in 'w' (write), 'a' (append), 'wb' or 'ab' mode) `file:read(numberOfChars/'*a')` - Read an opened file (must be opened in 'r' (read) or 'rb' (read binary) mode)
`file:close()` - Close an opened file `file:write(string)` - Write an opened file (must be opened in 'w' (write), 'a' (append), 'wb' or 'ab' mode)
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. `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. When reading from outside ZIPs, binary files are fully supported.
### Object Creation ### 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')` - Create a new Torque object
`bl.new('className objectName', fields?)` - Create a new named Torque object `bl.new('className', {fieldName = value, ...})` - Create a new Torque object with the given fields
`bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance `bl.new('className objectName', fields?)` - Create a new named Torque object
`bl.datablock('datablockClassName datablockName', fields?)` - Create a new datablock `bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance
`bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock 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 ### 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('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('className::funcName', 'type')` - Register the return type of a Torque object method. `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.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes. `bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.
`bl.class('className', 'parentClassName')` - Same as above, with inheritance `bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes.
`bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean. `bl.class('className', 'parentClassName')` - Same as above, with inheritance
`bl.object(arg)` - Manually convert a Torque object reference (object ID or name) into a Lua object. `bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean.
`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.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 ### Vector
`vec = vector{x,y,z}` - Create a vector. Can have any number of elements
`vec1 + vec2` - Add `vec = vector{x,y,z}` - Create a vector. Can have any number of elements
`vec1 - vec2` - Subtract `vec1 + vec2` - Add
`vec * number` - Scale (x\*n, y\*n, z\*n) `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) `vec / number` - Scale (x/n, y/n, z/n)
`vec1 * vec2` - Multiply elements piecewise (x1\*x2, y1\*y2, z1\*z2) `vec ^ number` - Exponentiate (x^n, y^n, z^n)
`vec1 / vec2` - Divide elements piecewise (x1/x2, y1/y2, z1/z2) `vec1 * vec2` - Multiply elements piecewise (x1\*x2, y1\*y2, z1\*z2)
`vec1 ^ vec2` - Exponentiate elements piecewise (x1^x2, y1^y2, z1^z2) `vec1 / vec2` - Divide elements piecewise (x1/x2, y1/y2, z1/z2)
`-vec` - Negate/invert `vec1 ^ vec2` - Exponentiate elements piecewise (x1^x2, y1^y2, z1^z2)
`vec1 == vec2` - Compare by value `-vec` - Negate/invert
`vec:length()` - Length `vec1 == vec2` - Compare by value
`vec:normalize()` - Preserve direction but scale so magnitude is 1 `vec:length()` - Length
`vec:floor()` - Floor each element `vec:normalize()` - Preserve direction but scale so magnitude is 1
`vec:ceil()` - Ceil each element `vec:floor()` - Floor each element
`vec:abs()` - Absolute value each element `vec:ceil()` - Ceil each element
`vec1:dot(vec2)` - Dot product `vec:abs()` - Absolute value each element
`vec1:cross(vec2)` - Cross product (Only defined for two vectors of length 3) `vec1:dot(vec2)` - Dot product
`vec:rotateZ(radians)` - Rotate counterclockwise about the Z (vertical) axis `vec1:cross(vec2)` - Cross product (Only defined for two vectors of length 3)
`vec:rotateByAngleId(0/1/2/3)` - Rotate counterclockwise about the Z (vertical) axis in increments of 90 degrees `vec:rotateZ(radians)` - Rotate counterclockwise about the Z (vertical) axis
`vec:tsString()` - Convert to string usable by Torque. Done automatically when a vector is passed to Torque. `vec:rotateByAngleId(0/1/2/3)` - Rotate counterclockwise about the Z (vertical) axis in increments of 90 degrees
`vec1:distance(vec2)` - Distance between two points `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. `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 ### Matrix
WIP WIP
### Extended Standard Lua Library ### Extended Standard Lua Library
`string[index]`
`string[{start,stop}]` `str[index]`
`string.split(str, separator='' (splits into chars), noregex=false)` `str[{start,stop}]`
`string.bytes(str)` `string.split(str, separator='' (splits into chars), noregex=false)`
`string.trim(str, charsToTrim=' \t\r\n')` `string.bytes(str)`
`table.empty` `string.trim(str, charsToTrim=' \t\r\n')`
`table.map(func, ...)` `table.empty`
`table.map_list(func, ...)` `table.map(func, ...)`
`table.swap(tbl)` `table.mapk(func, ...)`
`table.reverse(list)` `table.map_list(func, ...)`
`table.islist(list)` `table.mapi_list(func, ...)`
`table.append(list, ...)` `table.swap(tbl)`
`table.join(...)` `table.reverse(list)`
`table.contains(tbl, val)` `table.islist(list)`
`table.contains_list(list, val)` `table.append(list, ...)`
`table.copy(tbl)` `table.join(...)`
`table.copy_list(list)` `table.contains(tbl, val)`
`table.sortcopy(tbl, sortFunction?)` `table.contains_list(list, val)`
`table.removevalue(tbl, val)` `table.copy(tbl)`
`table.removevalue_list(tbl, val)` `table.copy_list(list)`
`table.tostring(tbl)` `table.sortcopy(tbl, sortFunction?)`
`for char in string.chars(str) do` `table.removevalue(tbl, val)`
`string.escape(str, escapes={['\n']='\\n', etc. (C standard)})` `table.removevalue_list(tbl, val)`
`string.unescape(str, escapeChar='\\', unescapes={['\\n']='\n', etc.})` `table.tostring(tbl)`
`io.readall(filename)` `for char in string.chars(str) do`
`io.writeall(filename, str)` `string.escape(str, escapes={['\n']='\\n', etc. (C standard)})`
`math.round(num)` `string.unescape(str, escapeChar='\\', unescapes={['\\n']='\n', etc.})`
`math.mod(divisor, modulus)` `io.readall(filename)`
`math.clamp(num, min, max)` `io.writeall(filename, str)`
`math.round(num)`
`math.mod(divisor, modulus)`
`math.clamp(num, min, max)`
## Type Conversion ## 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 ### From Lua to TorqueScript
- `nil` becomes the empty string ""
- `true` and `false` become "1" and "0" respectively - `nil` becomes the empty string ""
- Torque containers become their object ID - `true` and `false` become "1" and "0" respectively
- A `vector` becomes a string containing three numbers separated by spaces - A Torque object container becomes its object ID
- A table of two vectors becomes a string containing six numbers separated by spaces - A `vector` becomes a string containing three numbers separated by spaces
- Any `string` is passed directly as a string - A table of two `vector`s becomes a string containing six numbers separated by spaces
- Tables cannot be passed and will throw an error - (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 ### 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` - The empty string "" becomes `nil`
- A string containing two or three numbers separated by single spaces becomes a `vector` - 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 six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box - A string containing two or three numbers separated by single spaces becomes a `vector`
- (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. - A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box
- Any other string is passed directly as a `string` - (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 objects by hand, use `bl.object`, `bl.boolean`, or `bl.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 ## 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 Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.
`'all'` - Any object BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there.
`'player'` - Players or bots
`'item'` - Items ### Unsafe Mode
`'vehicle'` - Vehicles
`'projectile'` - Projectiles 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.
`'brick'` - Bricks with raycasting enabled 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.
`'brickalways'` - All bricks including those with raycasting disabled Please do not publish add-ons that require either of these.
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
## Compiling ## Compiling
With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory: 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` `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 // Includes
@@ -60,23 +60,27 @@ bool init() {
// Set up Lua environment // Set up Lua environment
BLL_LOAD_LUA(gL, bll_fileLuaEnv); BLL_LOAD_LUA(gL, bll_fileLuaEnv);
#ifdef BLLUA_ALLOWFFI
lua_pushboolean(gL, true);
lua_setglobal(gL, "_bllua_allowffi");
#endif
#ifndef BLLUA_UNSAFE #ifndef BLLUA_UNSAFE
BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe); BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe);
#endif #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 // Expose Lua API to TS
BlAddFunction( BlAddFunction(
NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20); NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20);
BlEval(bll_fileTsEnv); 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); BlEval(bll_fileTsLibts);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
BlEval(bll_fileTsLibblSupport); BlEval(bll_fileTsLibblSupport);
BlEval(bll_fileLoadaddons); BlEval(bll_fileLoadaddons);
@@ -89,8 +93,7 @@ bool init() {
bool deinit() { bool deinit() {
BlPrintf("BlockLua: Unloading"); BlPrintf("BlockLua: Unloading");
BlEval("deactivatePackage(_bllua_main);"); BlEval("$_bllua_active=0;deactivatePackage(_bllua_main);");
BlEval("$_bllua_active = 0;");
bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end"); bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end");
lua_close(gL); lua_close(gL);

View File

@@ -14,6 +14,7 @@ local old_require = require
local old_os = os local old_os = os
local old_debug = debug local old_debug = debug
local old_package = package local old_package = package
local old_allowffi = _bllua_allowffi
-- Remove all global variables except a whitelist -- Remove all global variables except a whitelist
local ok_names = tmap { local ok_names = tmap {
@@ -39,13 +40,10 @@ end
-- Sanitize file paths to point only to allowed files within the game directory -- Sanitize file paths to point only to allowed files within the game directory
-- List of allowed directories for reading/writing -- List of allowed directories for reading/writing
-- modules/lualib is also allowed as read-only
local allowed_dirs = tmap { local allowed_dirs = tmap {
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' '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 -- List of disallowed file extensions - basically executable file extensions
-- Note that even without this protection, exploiting would still require somehow -- Note that even without this protection, exploiting would still require somehow
-- getting a file within the allowed directories to autorun, -- 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), -- Return: clean file path if allowed (or nil if disallowed),
-- error string (or nil if allowed) -- error string (or nil if allowed)
local function safe_path(fn, readonly) 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('^ +', '') fn = fn:gsub('^ +', '')
fn = fn:gsub(' +$', '') fn = fn:gsub(' +$', '')
@@ -81,14 +82,15 @@ local function safe_path(fn, readonly)
end end
-- allow only whitelisted dirs -- allow only whitelisted dirs
local dir = fn:match('^([^/]+)/') local dir = fn:match('^([^/]+)/')
if (not dir) or ( if not (dir and (
(not allowed_dirs[dir:lower()]) and allowed_dirs[dir:lower()] or
((not readonly) or (not allowed_dirs_readonly[dir:lower()]))) then (readonly and fn:find('^modules/lualib/'))))
return nil, 'filename is in disallowed directory ' .. (dir or 'nil') then
return nil, 'File is in disallowed directory ' .. (dir or 'nil')
end end
-- disallow blacklisted extensions or no extension -- disallow blacklisted extensions
local ext = fn:match('%.([^/%.]+)$') 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 \'' .. return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' ..
(ext or '') .. '\'' (ext or '') .. '\''
end end
@@ -120,6 +122,7 @@ local disallowed_packages = tmap {
'ffi', 'debug', 'package', 'io', 'os', 'ffi', 'debug', 'package', 'io', 'os',
'_bllua_ts', '_bllua_ts',
} }
if old_allowffi then disallowed_packages['ffi'] = nil end
function _bllua_requiresecure(name) function _bllua_requiresecure(name)
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
name:find('^%.') or name:find('%.$') then 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 -- Called when pcall fails on a ts->lua call, used to print detailed error info
function _bllua_on_error(err) function _bllua_on_error(err)
-- Convert error to string if it's not already
err = tostring(err)
err = err:match(': (.+)$') or err err = err:match(': (.+)$') or err
local tracelines = { err } local tracelines = { err }
local level = 2 local level = 2
@@ -25,7 +27,7 @@ function _bllua_on_error(err)
local info = debug.getinfo(level) local info = debug.getinfo(level)
if not info then break end if not info then break end
local filename = debug.getfilename(level) or info.short_src 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 if funcname == 'dofile' then break end
table.insert(tracelines, string.format('%s:%s in function \'%s\'', table.insert(tracelines, string.format('%s:%s in function \'%s\'',
filename, filename,
@@ -37,5 +39,9 @@ function _bllua_on_error(err)
return table.concat(tracelines, '\n') return table.concat(tracelines, '\n')
end 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 = _bllua_ts.echo
print(' Executed bllua-env.lua') 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) local function io_open_absolute(fn, mode)
-- if file exists, use original mode -- if file exists, use original mode
local res, err = _bllua_io_open(fn, 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 -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
local dir = fn:match('^[^/]+') 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' local exist = _bllua_ts.call('isFile', fn) == '1'
if not exist then return nil, err end if not exist then return nil, err end
@@ -142,9 +146,9 @@ end
---@diagnostic disable-next-line: duplicate-set-field ---@diagnostic disable-next-line: duplicate-set-field
function io.type(f) 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 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' return f._is_open and 'file' or 'closed file'
else else
return _bllua_io_type(f) return _bllua_io_type(f)
@@ -183,14 +187,14 @@ function require(mod)
if require_memo[mod] then return unpack(require_memo[mod]) end if require_memo[mod] then return unpack(require_memo[mod]) end
local fp = mod:gsub('%.', '/') local fp = mod:gsub('%.', '/')
local fns = { local fns = {
'./' .. fp .. '.lua', -- local file './' .. fp .. '.lua', -- local file
'./' .. fp .. '/init.lua', -- local library './' .. fp .. '/init.lua', -- local library
fp .. '.lua', -- global file fp .. '.lua', -- global file
fp .. '/init.lua', -- global library fp .. '/init.lua', -- global library
} }
if fp:lower():find('^add-ons/') then if fp:lower():find('^add-ons/') then
local addonpath = fp:lower():match('^add-ons/[^/]+') .. '/' 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 table.insert(fns, addonpath .. fp .. '/init.lua') -- add-on library
end end
for _, fn in ipairs(fns) do for _, fn in ipairs(fns) do

View File

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