From 058a4b84a6d80e5726fc1a2c65dd275ce59dacd5 Mon Sep 17 00:00:00 2001 From: bobode Date: Sun, 29 Jul 2012 22:01:55 +0000 Subject: [PATCH] --- D2BS.h | 2 +- File.cpp | 98 +++++++++++++++++++++++++ File.h | 2 + JSFile.cpp | 60 +++++++++------ JSFileTools.cpp | 190 ++++++++++++++++++++++++------------------------ JSUnit.cpp | 4 + JSUnit.h | 4 +- 7 files changed, 237 insertions(+), 123 deletions(-) diff --git a/D2BS.h b/D2BS.h index 4e8c625e..e35174f3 100644 --- a/D2BS.h +++ b/D2BS.h @@ -5,7 +5,7 @@ #define XP_WIN -#define D2BS_VERSION "1.4.1717" +#define D2BS_VERSION "1.5.1717" #include #include diff --git a/File.cpp b/File.cpp index df33376d..2742efd8 100644 --- a/File.cpp +++ b/File.cpp @@ -20,7 +20,10 @@ #include +#include + #include "File.h" +#include "D2BS.h" using namespace std; @@ -185,7 +188,102 @@ bool writeValue(FILE* fptr, JSContext* cx, jsval value, bool isBinary, bool lock return false; } +/** Safely open a file relative to the script dir. + * + * In theory this is the only function used to open files that is exposed to + * javascript. + * + * \param filename Name of the file to open relative to the script folder + * + * \param mode Mode to open in. See fopen_s. + * + * \param cx JSContext that is running. Used to throw errors. + * + * \return The file pointer. + */ +FILE* fileOpenRelScript(const char* filename, const char* mode, JSContext* cx) +{ + FILE* f; + char fullPath[_MAX_PATH+_MAX_FNAME]; + + // Get the relative path + if(getPathRelScript(filename, _MAX_PATH+_MAX_FNAME, fullPath) == NULL) + { + JS_ReportError(cx, "Invalid file name"); + return NULL; + } + + // Open the file + if(fopen_s(&f, fullPath, mode) != 0 || f == NULL) + { + JS_ReportError(cx, "Couldn't open file %s: %s", filename, _strerror(NULL)); + return NULL; + } + + return f; +} + +/** Get the full path relative to the script path. Does the validation check. + * + * \param filename Name of the file to open relative to the script folder. + * + * \param bufLen Length of the output buffer. + * + * \param fullPath Buffer where the full path will be stored. Empty string if + * location is invalid. + * + * \return fullPath on success or NULL on failure. + */ +char* getPathRelScript(const char* filename, int bufLen, char* fullPath) +{ + char fullScriptPath[_MAX_PATH+_MAX_FNAME]; + char* relPath; + int strLenScript; + DWORD scrPathLen; + + strLenScript = strlen(Vars.szScriptPath); + + // Make the filename relative to the script path + relPath = (char*)_alloca(strLenScript+strlen(filename)+2); + strcpy(relPath, Vars.szScriptPath); + relPath[strLenScript] = '\\'; + strcpy(relPath+strLenScript+1, filename); + + // Transform to the full pathname + GetFullPathName(relPath, bufLen, fullPath, NULL); + + // Get the full path of the script path, check it is the prefix of fullPath + scrPathLen = GetFullPathName(Vars.szScriptPath, sizeof(fullScriptPath), + fullScriptPath, NULL); + + // Check that fullScriptPath is the prefix of fullPath + // As GetFullPathName seems to not add a trailing \, if there is not a + // trailing \ in fullScriptPath check for it in fullPath + if(strncmp(fullPath, fullScriptPath, scrPathLen) != 0 || + (fullScriptPath[scrPathLen-1] != '\\' && + fullPath[scrPathLen] != '\\')) + { + fullPath[0] = '\0'; + return NULL; + } + + return fullPath; +} + +/** Check that the full path of the script path is the prefix of the fullpath + * of name. Also checks that there is no ..\ or ../ sequences or ":?*<>| chars. + * + * \param name The file/path to validate. + * + * \return true if path is valid, false otherwise. + */ bool isValidPath(const char* name) { + char fullPath[_MAX_PATH+_MAX_FNAME]; + + // Use getPathRelScript to validate based on full paths + if(getPathRelScript(name, _MAX_PATH+_MAX_FNAME, fullPath) == NULL) + return false; + return (!strstr(name, "..\\") && !strstr(name, "../") && (strcspn(name, "\":?*<>|") == strlen(name))); } diff --git a/File.h b/File.h index ac1db307..d6a5e8fb 100644 --- a/File.h +++ b/File.h @@ -5,6 +5,8 @@ const char* readLine(FILE* fptr, bool locking); bool writeValue(FILE* fptr, JSContext* cx, jsval value, bool isBinary, bool locking); +FILE* fileOpenRelScript(const char* filename, const char* mode, JSContext* cx); +char* getPathRelScript(const char* filename, int bufLen, char* fullPath); bool isValidPath(const char* name); #endif diff --git a/JSFile.cpp b/JSFile.cpp index 6df9373f..b0b74f36 100644 --- a/JSFile.cpp +++ b/JSFile.cpp @@ -83,7 +83,7 @@ JSAPI_PROP(file_getProperty) *vp = JSVAL_ZERO; break; case FILE_PATH: - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, fdata->path+strlen(Vars.szScriptPath)+1)); + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, fdata->path+1)); break; case FILE_POSITION: if(fdata->fptr) @@ -168,21 +168,14 @@ JSAPI_FUNC(file_open) if(!JSVAL_IS_INT(JS_ARGV(cx, vp)[1])) THROW_ERROR(cx, "Parameter 2 not a mode"); - // check for attempts to break the sandbox and for invalid file name characters + // Convert from JS params to C values char* file = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!(file && file[0] && isValidPath(file))) - THROW_ERROR(cx, "Invalid file name"); - int32 mode; if(JS_ValueToInt32(cx, JS_ARGV(cx, vp)[1], &mode) == JS_FALSE) THROW_ERROR(cx, "Could not convert parameter 2"); - - // this could be simplified to: mode > FILE_APPEND || mode < FILE_READ - // but then it takes a hit for readability - if(!(mode == FILE_READ || mode == FILE_WRITE || mode == FILE_APPEND)) - THROW_ERROR(cx, "Invalid file mode"); - - bool binary = false, autoflush = false, lockFile = false; + bool binary = false; + bool autoflush = false; + bool lockFile = false; if(argc > 2 && JSVAL_IS_BOOLEAN(JS_ARGV(cx, vp)[2])) binary = !!JSVAL_TO_BOOLEAN(JS_ARGV(cx, vp)[2]); if(argc > 3 && JSVAL_IS_BOOLEAN(JS_ARGV(cx, vp)[3])) @@ -190,18 +183,35 @@ JSAPI_FUNC(file_open) if(argc > 4 && JSVAL_IS_BOOLEAN(JS_ARGV(cx, vp)[4])) lockFile = !!JSVAL_TO_BOOLEAN(JS_ARGV(cx, vp)[4]); + // Check that the path looks basically ok, validation is handled later + if(file == NULL || + file[0] == '\0') + THROW_ERROR(cx, "Invalid file name"); + + // this could be simplified to: mode > FILE_APPEND || mode < FILE_READ + // but then it takes a hit for readability + switch(mode) + { + // Good modes + case FILE_READ: + case FILE_WRITE: + case FILE_APPEND: + break; + // Bad modes + default: + THROW_ERROR(cx, "Invalid file mode"); + } + if(binary) mode += 3; + static const char* modes[] = {"rt", "w+t", "a+t", "rb", "w+b", "a+b"}; - char path[_MAX_FNAME+_MAX_PATH]; - sprintf_s(path, sizeof(path), "%s\\%s", Vars.szScriptPath, file); - FILE* fptr = NULL; - fopen_s(&fptr, path, modes[mode]); - if(!fptr) { - JS_ReportError(cx, "Couldn't open file %s: %s", file, _strerror(NULL)); + FILE* fptr = fileOpenRelScript(file, modes[mode], cx); + + // If fileOpenRelScript failed, it already reported the error + if(fptr == NULL) return JS_FALSE; - } FileData* fdata = new FileData; if(!fdata) @@ -211,7 +221,7 @@ JSAPI_FUNC(file_open) } fdata->mode = mode; - fdata->path = _strdup(path); + fdata->path = _strdup(file); fdata->autoflush = autoflush; fdata->locked = lockFile; fdata->fptr = fptr; @@ -272,10 +282,12 @@ JSAPI_FUNC(file_reopen) if(fdata) if(!fdata->fptr) { static const char* modes[] = {"rt", "w+t", "a+t", "rb", "w+b", "a+b"}; - fdata->fptr = NULL; - fopen_s(&fdata->fptr, fdata->path, modes[fdata->mode]); - if(!fdata->fptr) - THROW_ERROR(cx, _strerror("Could not reopen file")); + fdata->fptr = fileOpenRelScript(fdata->path, modes[fdata->mode], cx); + + // If fileOpenRelScript failed, it already reported the error + if(fdata->fptr == NULL) + return JS_FALSE; + if(fdata->locked) { _lock_file(fdata->fptr); diff --git a/JSFileTools.cpp b/JSFileTools.cpp index 342e8620..ec7098b2 100644 --- a/JSFileTools.cpp +++ b/JSFileTools.cpp @@ -20,6 +20,8 @@ #include #include +#include + #include "JSFileTools.h" #include "D2BS.h" #include "File.h" @@ -33,13 +35,14 @@ JSAPI_FUNC(filetools_remove) { if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) THROW_ERROR(cx, "You must supply a file name"); - char* file = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!isValidPath(file)) + + char* file = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); + char fullpath[_MAX_PATH+_MAX_FNAME]; + + if(getPathRelScript(file, _MAX_PATH+_MAX_FNAME, fullpath) == NULL) THROW_ERROR(cx, "Invalid file name"); - char path[_MAX_PATH+_MAX_FNAME]; - sprintf_s(path, sizeof(path), "%s\\%s", Vars.szScriptPath, file); - remove(path); + remove(fullpath); JS_free(cx, file); return JS_TRUE; } @@ -48,23 +51,24 @@ JSAPI_FUNC(filetools_rename) { if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) THROW_ERROR(cx, "You must supply an original file name"); - char* orig = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!isValidPath(orig)) - THROW_ERROR(cx, "Invalid file name"); - char porig[_MAX_PATH+_MAX_FNAME]; - sprintf_s(porig, sizeof(porig), "%s\\%s", Vars.szScriptPath, orig); - if(argc < 2 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[1])) THROW_ERROR(cx, "You must supply a new file name"); - char* newName = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[1])); - if(!isValidPath(newName)) - THROW_ERROR(cx, "Invalid file name"); + + char* orig = JS_EncodeString(cx ,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); + char* newName = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[1])); + + char porig[_MAX_PATH+_MAX_FNAME]; char pnewName[_MAX_PATH+_MAX_FNAME]; - sprintf_s(pnewName, sizeof(pnewName), "%s\\%s", Vars.szScriptPath, newName); + + if(getPathRelScript(orig, _MAX_PATH+_MAX_FNAME, porig) == NULL) + THROW_ERROR(cx, "Invalid original file name"); + + if(getPathRelScript(newName, _MAX_PATH+_MAX_FNAME, pnewName) == NULL) + THROW_ERROR(cx, "Invalid new file name"); rename(porig, pnewName); JS_free(cx, orig); - JS_free(cx, newName); + JS_free(cx, newName); return JS_TRUE; } @@ -72,38 +76,29 @@ JSAPI_FUNC(filetools_copy) { if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) THROW_ERROR(cx, "You must supply an original file name"); - char* orig = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!isValidPath(orig)) - THROW_ERROR(cx, "Invalid file name"); - char porig[_MAX_PATH+_MAX_FNAME]; - sprintf_s(porig, sizeof(porig), "%s\\%s", Vars.szScriptPath, orig); - if(argc < 2 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[1])) THROW_ERROR(cx, "You must supply a new file name"); - char* newName = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[1])); - if(!isValidPath(newName)) - THROW_ERROR(cx, "Invalid file name"); - char pnewName[_MAX_PATH+_MAX_FNAME]; - sprintf_s(pnewName, sizeof(pnewName), "%s\\%s", Vars.szScriptPath, newName); + char* orig = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); + char* newName = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[1])); + char pnewName[_MAX_PATH+_MAX_FNAME]; bool overwrite = false; + if(argc > 2 && JSVAL_IS_BOOLEAN(JS_ARGV(cx, vp)[2])) overwrite = !!JSVAL_TO_BOOLEAN(JS_ARGV(cx, vp)[2]); + if(getPathRelScript(newName, _MAX_PATH+_MAX_FNAME, pnewName) == NULL) + THROW_ERROR(cx, "Invalid new file name"); + if(overwrite && _access(pnewName, 0) == 0) return JS_TRUE; - FILE* fptr1 = NULL; - fopen_s(&fptr1, porig, "r"); - FILE* fptr2 = NULL; - fopen_s(&fptr2, pnewName, "w"); + FILE* fptr1 = fileOpenRelScript(orig, "r", cx); + FILE* fptr2 = fileOpenRelScript(newName, "w", cx); - //Sanity check to make sure the file opened for reading! - if(!fptr1) - THROW_ERROR(cx, _strerror("Read file open failed")); - // Same for file opened for writing - if(!fptr2) - THROW_ERROR(cx, _strerror("Write file open failed")); + // If fileOpenRelScript failed, it already reported the error + if(fptr1 == NULL || fptr2 == NULL) + return JS_FALSE; while(!feof(fptr1)) { @@ -139,7 +134,7 @@ JSAPI_FUNC(filetools_copy) fclose(fptr2); fclose(fptr1); JS_free(cx, orig); - JS_free(cx, newName); + JS_free(cx, newName); return JS_TRUE; } @@ -147,45 +142,53 @@ JSAPI_FUNC(filetools_exists) { if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) THROW_ERROR(cx, "Invalid file name"); - char* file = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!isValidPath(file)) + char* file = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); + char fullpath[_MAX_PATH+_MAX_FNAME]; + + if(getPathRelScript(file, _MAX_PATH+_MAX_FNAME, fullpath) == NULL) THROW_ERROR(cx, "Invalid file name"); - char path[_MAX_PATH+_MAX_FNAME]; - sprintf_s(path, sizeof(path), "%s\\%s", Vars.szScriptPath, file); - JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(!(_access(path, 0) != 0 && errno == ENOENT))); + JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(!(_access(fullpath, 0) != 0 && errno == ENOENT))); return JS_TRUE; } + JSAPI_FUNC(filetools_readText) { if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) - THROW_ERROR(cx, "You must supply an original file name"); - char* orig = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!isValidPath(orig)) - THROW_ERROR(cx, "Invalid file name"); - char porig[_MAX_PATH+_MAX_FNAME]; - sprintf_s(porig, sizeof(porig), "%s\\%s", Vars.szScriptPath, orig); + THROW_ERROR(cx, "You must supply a file name"); + char* fname = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); + FILE* fptr = fileOpenRelScript(fname, "r", cx); - if((_access(porig, 0) != 0 && errno == ENOENT)) - THROW_ERROR(cx, "File not found"); + // If fileOpenRelScript failed, it already reported the error + if(fptr == NULL) + return JS_FALSE; - FILE* fptr = NULL; uint size, readCount; - fopen_s(&fptr, porig, "r"); + char* contents; + + // Determine file size fseek(fptr, 0, SEEK_END); size = ftell(fptr); rewind(fptr); - char* contents = new char[size+1]; - memset(contents, 0, size+1); + + // Allocate and read the string. Need to set last char to \0 since fread + // doesn't. + contents = new char[size+1]; readCount = fread(contents, sizeof(char), size, fptr); - if( readCount != size && ferror(fptr)) + assert(readCount <= size); // Avoid SEGFAULT + contents[readCount] = 0; + fclose(fptr); + + // Check to see if we had an error + if(ferror(fptr)) { delete[] contents; THROW_ERROR(cx, _strerror("Read failed")); } - fclose(fptr); + + // Convert to JSVAL cleanup and return JS_BeginRequest(cx); JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(JS_NewStringCopyN(cx, contents, strlen(contents)))); JS_EndRequest(cx); @@ -195,57 +198,50 @@ JSAPI_FUNC(filetools_readText) JSAPI_FUNC(filetools_writeText) { - if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) - THROW_ERROR(cx, "You must supply an original file name"); - - char* orig = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!isValidPath(orig)) - THROW_ERROR(cx, "Invalid file name"); - - char porig[_MAX_PATH+_MAX_FNAME]; - sprintf_s(porig, sizeof(porig), "%s\\%s", Vars.szScriptPath, orig); - - StringReplace(porig, '/', '\\', strlen(porig)); - - PathRemoveFileSpec(porig); - if(!PathFileExists(porig)) - THROW_ERROR(cx, "File path directory invalid"); - - sprintf_s(porig, sizeof(porig), "%s\\%s", Vars.szScriptPath, orig); - - bool result = true; - FILE* fptr = NULL; - fopen_s(&fptr, porig, "w"); - - for(uintN i = 1; i < argc; i++) - if(!writeValue(fptr, cx, JS_ARGV(cx, vp)[i], false, true)) - fflush(fptr); - fclose(fptr); - JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(result)); - JS_free(cx, orig); - return JS_TRUE; + if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) + THROW_ERROR(cx, "You must supply a file name"); + + char* fname = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); + FILE* fptr = fileOpenRelScript(fname, "w", cx); + bool result = true; + + // If fileOpenRelScript failed, it already reported the error + if(fptr == NULL) + return JS_FALSE; + + for(uintN i = 1; i < argc; i++) + if(!writeValue(fptr, cx, JS_ARGV(cx, vp)[i], false, true)) + result = false; + + fflush(fptr); + fclose(fptr); + + JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(result)); + JS_free(cx, fname); + return JS_TRUE; } JSAPI_FUNC(filetools_appendText) { if(argc < 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) - THROW_ERROR(cx, "You must supply an original file name"); - char* orig = JS_EncodeString(cx,JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); - if(!isValidPath(orig)) - THROW_ERROR(cx, "Invalid file name"); - char porig[_MAX_PATH+_MAX_FNAME]; - sprintf_s(porig, sizeof(porig), "%s\\%s", Vars.szScriptPath, orig); + THROW_ERROR(cx, "You must supply a file name"); + char* fname = JS_EncodeString(cx, JSVAL_TO_STRING(JS_ARGV(cx, vp)[0])); + FILE* fptr = fileOpenRelScript(fname, "a+", cx); bool result = true; - FILE* fptr = NULL; - if(fopen_s(&fptr, porig, "a+") != 0) - THROW_ERROR(cx, _strerror("Failed to open file")); + + // If fileOpenRelScript failed, it already reported the error + if(fptr == NULL) + return JS_FALSE; + for(uintN i = 1; i < argc; i++) - if(!writeValue(fptr, cx, JS_ARGV(cx, vp)[i], false, true)) + if(!writeValue(fptr, cx,JS_ARGV(cx, vp)[i], false, true)) result = false; + fflush(fptr); fclose(fptr); - JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(result)); + + JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(result)); return JS_TRUE; } diff --git a/JSUnit.cpp b/JSUnit.cpp index e29996c7..3e6c4f0d 100644 --- a/JSUnit.cpp +++ b/JSUnit.cpp @@ -445,6 +445,10 @@ JSAPI_PROP(unit_getProperty) } } break; + case ITEM_GFX: + if(pUnit->dwType == UNIT_ITEM && pUnit->pItemData) + *vp = INT_TO_JSVAL(pUnit->pItemData->bInvGfxIdx); + break; case UNIT_ITEMCOUNT: if(pUnit->pInventory) *vp = INT_TO_JSVAL(pUnit->pInventory->dwItemCount); diff --git a/JSUnit.h b/JSUnit.h index 1fae4569..a27bbca2 100644 --- a/JSUnit.h +++ b/JSUnit.h @@ -70,7 +70,8 @@ enum unit_tinyid ITEM_SIZEY, ITEM_TYPE, MISSILE_DIR, MISSILE_VEL, ITEM_CLASS, UNIT_SPECTYPE, ITEM_DESC, ITEM_BODYLOCATION, UNIT_ITEMCOUNT, ITEM_LEVELREQ, UNIT_OWNER, UNIT_OWNERTYPE, UNIT_UNIQUEID, ITEM_LEVEL, UNIT_DIRECTION, - ITEM_SUFFIXNUM, ITEM_PREFIXNUM, ITEM_PREFIXES, ITEM_SUFFIXES, ITEM_SUFFIXNUMS, ITEM_PREFIXNUMS, OBJECT_TYPE, OBJECT_LOCKED + ITEM_SUFFIXNUM, ITEM_PREFIXNUM, ITEM_PREFIXES, ITEM_SUFFIXES, ITEM_SUFFIXNUMS, ITEM_PREFIXNUMS, ITEM_GFX, + OBJECT_TYPE, OBJECT_LOCKED }; enum me_tinyid { @@ -233,6 +234,7 @@ static JSPropertySpec unit_props[] = { {"bodylocation",ITEM_BODYLOCATION, JSPROP_PERMANENT_VAR, unit_getProperty}, {"ilvl", ITEM_LEVEL, JSPROP_PERMANENT_VAR, unit_getProperty}, {"lvlreq", ITEM_LEVELREQ, JSPROP_PERMANENT_VAR, unit_getProperty}, + {"gfx", ITEM_GFX, JSPROP_PERMANENT_VAR, unit_getProperty}, {"runwalk", ME_RUNWALK, JSPROP_PERMANENT_VAR, unit_getProperty}, {"weaponswitch",ME_WSWITCH, JSPROP_PERMANENT_VAR, unit_getProperty},