Files
fs2open.github.com/code/scripting/ade_args.cpp
T
asarium 43e540cc4c Add true Unicode support
This adds proper Unicode support by allowing the parse system to read
UTF-8 encoded files. The new mode is controlled by a mod option which
allows older mods to continue using the legacy text encoding.

That option changes the behavior of several systems in FSO. It disallows
the usage of Volition fonts since they don't support Unicode encoding
and changes a few usages of special characters to their unicode
counterparts of generic rendering solutions.

This fixes #1415.
2017-11-30 13:26:20 +01:00

408 lines
11 KiB
C++

#include "scripting/ade_args.h"
#include "scripting/ade.h"
#include "scripting/lua/LuaTable.h"
#include "scripting/lua/LuaFunction.h"
#include "scripting/lua/LuaConvert.h"
#include "mod_table/mod_table.h"
#include <utf8.h>
namespace {
using namespace scripting;
ade_table_entry& getTableEntry(size_t idx) {
return ade_manager::getInstance()->getEntry(idx);
}
}
namespace scripting {
//WMC - hack to skip X number of arguments on the stack
//Lets me use ade_get_args for global hook return values
int Ade_get_args_skip = 0;
bool Ade_get_args_lfunction = false;
//ade_get_args(state, arguments, variables)
//----------------------------------------------
//based on "Programming in Lua"
//
//Parses arguments from string to variables given
//a '|' divides required and optional arguments.
//Returns 0 if a required argument is invalid,
//or there are too few arguments actually passed
//
//NOTE: This function essentially takes objects
//from the stack in series, so it can easily be used
//to get the return values from a chunk of Lua code
//after it has been executed. See RunByteCode()
int ade_get_args(lua_State *L, const char *fmt, ...)
{
//Check that we have all the arguments that we need
//If we don't, return 0
int needed_args = (int)strlen(fmt);
int total_args = lua_gettop(L) - Ade_get_args_skip;
if(strchr(fmt, '|') != NULL) {
needed_args = (int)(strchr(fmt, '|') - fmt);
}
char funcname[128] = "\0";
#ifndef NDEBUG
lua_Debug ar;
memset(&ar, 0, sizeof(ar));
if(lua_getstack(L, 0, &ar))
{
lua_getinfo(L, "nl", &ar);
strcpy_s(funcname, "");
if(ar.name != NULL) {
strcat_s(funcname, ar.name);
}
if(ar.currentline > -1) {
char buf[33];
sprintf(buf, "%d", ar.currentline);
strcat_s(funcname, " (Line ");
strcat_s(funcname, buf);
strcat_s(funcname, ")");
}
}
#endif
if(!strlen(funcname)) {
//WMC - This was causing crashes with user-defined functions.
//WMC - Try and get at function name from upvalue
if(!Ade_get_args_lfunction && !lua_isnone(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX)))
{
if(lua_type(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX)) == LUA_TSTRING)
strcpy_s(funcname, lua_tostring(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX)));
}
//WMC - Totally unknown function
if(!strlen(funcname)) {
strcpy_s(funcname, "<UNKNOWN>");
}
}
if(total_args < needed_args) {
LuaError(L, "Not enough arguments for '%s' - need %d, had %d. If you are using objects or handles, make sure that you are using \":\" to access member functions, rather than \".\"", funcname, needed_args, total_args);
return 0;
}
//Start throught
va_list vl;
int nargs;
int counted_args = 0;
//Are we parsing optional args yet?
bool optional_args = false;
va_start(vl, fmt);
nargs = 1 + Ade_get_args_skip;
total_args += Ade_get_args_skip;
while(*fmt && nargs <= total_args)
{
switch(*fmt++)
{
case 'b':
if(lua_isboolean(L, nargs)) {
*va_arg(vl, bool*) = lua_toboolean(L, nargs) > 0 ? true : false;
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; boolean expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'd':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, double*) = (double)lua_tonumber(L, nargs);
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'f':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, float*) = (float)lua_tonumber(L, nargs);
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'i':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, int*) = (int)lua_tonumber(L, nargs);
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 's':
if(lua_isstring(L, nargs)) {
auto value = lua_tostring(L, nargs);
if (Unicode_text_mode) {
// Validate the string when we are in unicode mode to ensure that FSO doesn't just crash when a script
// passes an invalid UTF-8 sequence to the API
auto end = value + strlen(value);
auto invalid = utf8::find_invalid(value, end);
if (invalid != end) {
if (invalid == value) {
// The first character is the invalid sequence
LuaError(L, "An invalid UTF-8 encoding sequence was detected! The first invalid character was as the start of the string.");
} else {
// If the invalid sequence is inside the string then we try to give some context to make
// finding the bug easier
auto display_text_start = std::max(value, invalid - 32);
SCP_string context_text(display_text_start, invalid);
LuaError(L,
"An invalid UTF-8 encoding sequence was detected! The error was detected directly after this string \"%s\".",
context_text.c_str());
}
// Finally, assign a default value so that we can continue
value = "Invalid UTF-8 sequence detected!";
}
}
*va_arg(vl, const char **) = value;
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; string expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'x':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, fix*) = fl2f((float)lua_tonumber(L, nargs));
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'o':
{
ade_odata od = va_arg(vl, ade_odata);
if(lua_isuserdata(L, nargs))
{
//WMC - Get metatable
lua_getmetatable(L, nargs);
int mtb_ldx = lua_gettop(L);
Assert(!lua_isnil(L, -1));
//Get ID
lua_pushstring(L, "__adeid");
lua_rawget(L, mtb_ldx);
if(lua_tonumber(L, -1) != od.idx)
{
lua_pushstring(L, "__adederivid");
lua_rawget(L, mtb_ldx);
if((uint)lua_tonumber(L, -1) != od.idx)
{
LuaError(L, "%s: Argument %d is the wrong type of userdata; '%s' given, but '%s' expected", funcname, nargs, getTableEntry((uint)lua_tonumber(L, -2)).Name, getTableEntry(od.idx).GetName());
if(!optional_args) {
va_end(vl);
return 0;
}
}
lua_pop(L, 1);
}
lua_pop(L, 2);
if(od.size != ODATA_PTR_SIZE)
{
memcpy(od.buf, lua_touserdata(L, nargs), od.size);
if(od.sig != NULL) {
//WMC - char must be 1
Assert(sizeof(char) == 1);
//WMC - Yuck. Copy sig data.
//Maybe in the future I'll do a packet userdata thing.
(*od.sig) = *(ODATA_SIG_TYPE*)(*(char **)od.buf + od.size);
}
} else {
(*(void**)od.buf) = lua_touserdata(L, nargs);
}
}
else if(lua_isnil(L, nargs) && optional_args)
{
//WMC - Modder has chosen to ignore this argument
}
else
{
LuaError(L, "%s: Argument %d is an invalid type '%s'; type '%s' expected", funcname, nargs, ade_get_type_string(L, nargs), getTableEntry(od.idx).GetName());
if(!optional_args) {
va_end(vl);
return 0;
}
}
}
break;
case 't':
{
// Get a table
try {
*va_arg(vl, luacpp::LuaTable*) = luacpp::convert::popValue<luacpp::LuaTable>(L, nargs, false);
} catch (const luacpp::LuaException&) {
LuaError(L, "%s: Argument %d is an invalid type '%s'; table expected.", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
}
case 'u':
{
// Get a function
try {
*va_arg(vl, luacpp::LuaFunction*) = luacpp::convert::popValue<luacpp::LuaFunction>(L, nargs, false);
} catch (const luacpp::LuaException&) {
LuaError(L, "%s: Argument %d is an invalid type '%s'; function expected.", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
}
case '|':
nargs--; //cancel out the nargs++ at the end
counted_args--;
optional_args = true;
break;
case '*':
//WMC - Ignore one spot
break;
default:
Error(LOCATION, "%s: Bad character passed to ade_get_args; (%c)", funcname, *(fmt-1));
break;
}
nargs++;
counted_args++;
}
va_end(vl);
return counted_args;
}
//ade_set_args(state, arguments, variables)
//----------------------------------------------
//based on "Programming in Lua"
//
//Takes variables given and pushes them onto the
//Lua stack. Use it to return variables from a
//Lua scripting function.
//
//NOTE: You can also use this to push arguments
//on to the stack in series. See script_state::SetHookVar
int ade_set_args(lua_State *L, const char *fmt, ...)
{
//Start throught
va_list vl;
int nargs;
int setargs; //args actually set
va_start(vl, fmt);
nargs = 0;
setargs = 0;
while(*fmt != '\0')
{
switch(*fmt++)
{
case '*':
lua_pushnil(L);
break;
case 'b': //WMC - Bool is actually int for GCC (Why...?)
lua_pushboolean(L, va_arg(vl, int) ? 1 : 0);
break;
case 'd':
lua_pushnumber(L, va_arg(vl, double));
break;
case 'f':
lua_pushnumber(L, va_arg(vl, double));
break;
case 'i':
lua_pushnumber(L, va_arg(vl, int));
break;
case 's':
{
//WMC - Isn't working with HookVar for some strange reason
char *s = va_arg(vl, char*);
lua_pushstring(L, s);
break;
}
case 'x':
lua_pushnumber(L, f2fl(va_arg(vl, fix)));
break;
case 'o':
{
//WMC - char must be 1 byte, foo.
Assert(sizeof(char)==1);
//WMC - step by step
//Copy over objectdata
ade_odata od = (ade_odata) va_arg(vl, ade_odata);
//Create new LUA object and get handle
char *newod = (char*)lua_newuserdata(L, od.size + sizeof(ODATA_SIG_TYPE));
//Create or get object metatable
luaL_getmetatable(L, getTableEntry(od.idx).Name);
//Set the metatable for the object
lua_setmetatable(L, -2);
//Copy the actual object data to the Lua object
memcpy(newod, od.buf, od.size);
//Also copy in the unique sig
if(od.sig != NULL)
memcpy(newod + od.size, od.sig, sizeof(ODATA_SIG_TYPE));
else
{
ODATA_SIG_TYPE tempsig = ODATA_SIG_DEFAULT;
memcpy(newod + od.size, &tempsig, sizeof(ODATA_SIG_TYPE));
}
break;
}
case 't':
{
// Set a table
luacpp::LuaTable* table = va_arg(vl, luacpp::LuaTable*); // This must be a pointer since C++ classes can't be passed by vararg
table->pushValue();
break;
}
case 'u':
{
// Set a function
luacpp::LuaFunction* func = va_arg(vl, luacpp::LuaFunction*); // This must be a pointer since C++ classes can't be passed by vararg
func->pushValue();
break;
}
//WMC - Don't forget to update lua_set_arg
default:
Error(LOCATION, "Bad character passed to ade_set_args; (%c)", *(fmt-1));
setargs--;
}
nargs++;
setargs++;
}
va_end(vl);
return setargs;
}
}