diff --git a/Mod Sources/TSExtension/TSExtension/TSExtension.vcxproj b/Mod Sources/TSExtension/TSExtension/TSExtension.vcxproj index 6ab5808..15dfc68 100644 --- a/Mod Sources/TSExtension/TSExtension/TSExtension.vcxproj +++ b/Mod Sources/TSExtension/TSExtension/TSExtension.vcxproj @@ -15,32 +15,42 @@ + + + + + + + + + + @@ -91,6 +101,7 @@ Windows true + ws2_32.lib;Dnsapi.lib;%(AdditionalDependencies) @@ -107,6 +118,7 @@ true true true + ws2_32.lib;Dnsapi.lib;%(AdditionalDependencies) diff --git a/Mod Sources/TSExtension/TSExtension/include/DXAPI/DXAPI.h b/Mod Sources/TSExtension/TSExtension/include/DXAPI/DXAPI.h index f809a40..72391c4 100644 --- a/Mod Sources/TSExtension/TSExtension/include/DXAPI/DXAPI.h +++ b/Mod Sources/TSExtension/TSExtension/include/DXAPI/DXAPI.h @@ -30,9 +30,19 @@ #include #include #include +#include namespace DX { //! A typedef referring to some type of unresolved object in the game. typedef void* UnresolvedObject; + + const char *GetModPaths(void); + + bool IsFile(const char *filename); + + bool GetRelativePath(const char *filename, char *ret, int buffer_length); + bool GetRunningMod(char *ret, int buffer_length); + + bool SanitizeFileName(char *ret, int buffer_length); } // End NameSpace DX \ No newline at end of file diff --git a/Mod Sources/TSExtension/TSExtension/include/DXAPI/ScriptObject.h b/Mod Sources/TSExtension/TSExtension/include/DXAPI/ScriptObject.h new file mode 100644 index 0000000..9149d99 --- /dev/null +++ b/Mod Sources/TSExtension/TSExtension/include/DXAPI/ScriptObject.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace DX +{ + class ScriptObject : public SimObject + { + public: + ScriptObject(unsigned int obj); + }; +} // End NameSpace DX diff --git a/Mod Sources/TSExtension/TSExtension/include/DXConCmds.h b/Mod Sources/TSExtension/TSExtension/include/DXConCmds.h index 106d503..14685f7 100644 --- a/Mod Sources/TSExtension/TSExtension/include/DXConCmds.h +++ b/Mod Sources/TSExtension/TSExtension/include/DXConCmds.h @@ -39,6 +39,17 @@ bool conTCPObjectDisconnect(Linker::SimObject *obj, S32 argc, const char *argv[] // HTTPObject Commands ------------------------------ bool conHTTPObjectDoNothing(Linker::SimObject *obj, S32 argc, const char *argv[]); +// BinaryObject Commands ---------------------------- +bool conBinaryObjectOpenForRead(Linker::SimObject *obj, S32 argc, const char *argv[]); +const char *conBinaryObjectReadU32(Linker::SimObject *obj, S32 argc, const char *argv[]); +const char *conBinaryObjectReadF32(Linker::SimObject *obj, S32 argc, const char *argv[]); +const char *conBinaryObjectReadU8(Linker::SimObject *obj, S32 argc, const char *argv[]); +const char *conBinaryObjectGetBufferLength(Linker::SimObject *obj, S32 argc, const char *argv[]); +bool conBinaryObjectSetBufferPointer(Linker::SimObject *obj, S32 argc, const char *argv[]); +const char *conBinaryObjectGetBufferPointer(Linker::SimObject *obj, S32 argc, const char *argv[]); +bool conBinaryObjectClose(Linker::SimObject *obj, S32 argc, const char *argv[]); +bool conBinaryObjectSave(Linker::SimObject *obj, S32 argc, const char *argv[]); + // General Commands --------------------------------- const char* conSprintf(Linker::SimObject *obj, S32 argc, const char* argv[]); bool conTSExtensionUpdate(Linker::SimObject *obj, S32 argc, const char *argv[]); \ No newline at end of file diff --git a/Mod Sources/TSExtension/TSExtension/source/DXAPI/DXAPI.cpp b/Mod Sources/TSExtension/TSExtension/source/DXAPI/DXAPI.cpp index 6498e5c..e97ccd6 100644 --- a/Mod Sources/TSExtension/TSExtension/source/DXAPI/DXAPI.cpp +++ b/Mod Sources/TSExtension/TSExtension/source/DXAPI/DXAPI.cpp @@ -19,5 +19,107 @@ namespace DX { + const char *GetModPaths(void) + { + int pointer = *(int*)0x9E8690; + pointer = *(int*)(pointer + 2048); + return (const char*)pointer; + } + bool IsFile(const char *filename) + { + typedef bool (*IsFileFuncPtr)(void*, const char *filename, int); + static IsFileFuncPtr IsFileFunc = (IsFileFuncPtr)0x440DF0; + + // File Manager Object or something? + void *unknown = *(void**)0x9E8690; + int unknown_int = 0; // Not sure what this is + + int result = 0; + __asm + { + push unknown_int; + push filename; + mov ecx, unknown; + lea eax, IsFileFunc; + mov eax, [eax]; + call eax; + mov result, eax; + } + + return result != 0; + } + + bool GetRelativePath(const char *filename, char *ret, int buffer_length) + { + // Make sure T2 can see it first off, we don't want to be loading + // arbitrary files on disk + if (!IsFile(filename)) + return false; + + const char *modpaths = GetModPaths(); + + int modpaths_len = strlen(modpaths); + char *modpaths_temp = new char[modpaths_len + 1]; + memcpy(modpaths_temp, modpaths, modpaths_len + 1); + + // Now we process all the modpaths + int last_start = 0; + for (int iteration = 0; iteration < modpaths_len + 1; iteration++) + if (modpaths_temp[iteration] == ';' || iteration == modpaths_len) + { + memset(ret, 0x00, buffer_length); + modpaths_temp[iteration] = 0x00; + char *modname = &modpaths_temp[last_start]; + + sprintf_s(ret, buffer_length, "%s/%s", modname, filename); + + // Check if it exists + FILE *handle = fopen(ret, "r"); + if (handle) + { + fclose(handle); + delete[] modpaths_temp; + return true; + } + + last_start = iteration + 1; + } + + delete[] modpaths_temp; + return false; + } + + bool GetRunningMod(char *ret, int buffer_length) + { + const char *modpaths = GetModPaths(); + unsigned int start_point = (unsigned int)modpaths; + unsigned int end_point = (unsigned int)strstr(modpaths, ";"); + + // FIXME: Potential Buffer overflow + if (end_point == 0) + memcpy(ret, modpaths, strlen(modpaths)); + else + memcpy(ret, modpaths, end_point - start_point); + + // FIXME: Attackers can use setModPath() to attempt to trick my code into loading files + // outside of Tribes 2's reach + return SanitizeFileName(ret, buffer_length); + } + + bool SanitizeFileName(char *ret, int buffer_length) + { + bool was_dirty = false; + for (unsigned int iteration = 0; iteration < strlen(ret); iteration++) + if (ret[iteration] == '.' || ret[iteration] == '\\' || ret[iteration] == '/' || ret[iteration] == '~') + { + was_dirty = true; + ret[iteration] == 0x20; // In the event the occurence is at the very end + + for (unsigned int replace_iteration = iteration; replace_iteration < strlen(ret); replace_iteration++) + ret[replace_iteration] = ret[replace_iteration + 1]; + } + + return was_dirty; + } } \ No newline at end of file diff --git a/Mod Sources/TSExtension/TSExtension/source/DXAPI/ScriptObject.cpp b/Mod Sources/TSExtension/TSExtension/source/DXAPI/ScriptObject.cpp new file mode 100644 index 0000000..18bf1aa --- /dev/null +++ b/Mod Sources/TSExtension/TSExtension/source/DXAPI/ScriptObject.cpp @@ -0,0 +1,8 @@ +#include + +namespace DX +{ + ScriptObject::ScriptObject(unsigned int obj) : SimObject(obj) + { + } +} \ No newline at end of file diff --git a/Mod Sources/TSExtension/TSExtension/source/DXBinaryObjects.cpp b/Mod Sources/TSExtension/TSExtension/source/DXBinaryObjects.cpp new file mode 100644 index 0000000..957f2a3 --- /dev/null +++ b/Mod Sources/TSExtension/TSExtension/source/DXBinaryObjects.cpp @@ -0,0 +1,291 @@ +/** + */ + +#include +#include + +#include + +typedef struct +{ + char *buffer; + int buffer_length; + int buffer_pointer; + bool disk_streaming; + FILE *handle; +} DXBinaryObject; + +typedef std::map DXBinaryObjectMapping; +static DXBinaryObjectMapping DXBinaryObjectMap; + +bool conBinaryObjectOpenForRead(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + // By default we don't do disk streaming but this allows for the support + bool should_stream = false; + if (argc == 4) + { + int stream_value = atoi(argv[3]); + if (stream_value != 0) + should_stream = true; + } + + DX::ScriptObject scriptobject((unsigned int)obj); + + char filepath[256]; + if (!DX::GetRelativePath(argv[2], filepath, 256)) + { + scriptobject.CallMethod("onOpenFailed", 0); + return false; + } + + // Already open + if (DXBinaryObjectMap.count(scriptobject.identifier) != 0) + { + // TODO: Make these report the actual errors + scriptobject.CallMethod("onOpenFailed", 0); + return false; + } + + FILE *handle = fopen(filepath, "rb"); + if (!handle) + { + Con::errorf(0, "Failed to open: %s", filepath); + scriptobject.CallMethod("onOpenFailed", 0); + return false; + } + fseek(handle, 0, SEEK_END); + + // Init the Binobj + DXBinaryObject *binobj = new DXBinaryObject; + binobj->disk_streaming = should_stream; + binobj->buffer_length = ftell(handle); + binobj->buffer_pointer = binobj->buffer_length; + binobj->handle = handle; + + if (!should_stream) + binobj->buffer = new char[binobj->buffer_length]; + + // We don't need the file object anymore after we've read the buffer + // so we ditch it. + fseek(handle, 0, SEEK_SET); + + if (!should_stream) + { + fread(binobj->buffer, binobj->buffer_length, 1, handle); + fclose(handle); + } + + DXBinaryObjectMap[scriptobject.identifier] = binobj; + return true; +} + +const char *conBinaryObjectReadU32(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + DX::ScriptObject scriptobject((unsigned int)obj); + + // Not open + if (DXBinaryObjectMap.count(scriptobject.identifier) == 0) + { + scriptobject.CallMethod("onReadFailed", 0); + return "-1"; + } + + DXBinaryObject *binobj = DXBinaryObjectMap[scriptobject.identifier]; + + // Okay, so we're open, now we need to make sure we have enough bytes left in the buffer + int read_length = sizeof(unsigned int); + + if (binobj->buffer_length - (binobj->buffer_length - binobj->buffer_pointer) < read_length) + { + scriptobject.CallMethod("onReadFailed", 0); + return "-1"; + } + + unsigned int return_value = 0; + binobj->buffer_pointer -= read_length; + if (binobj->disk_streaming) + { + char out_memory[4]; + fseek(binobj->handle, binobj->buffer_pointer, SEEK_SET); + fread(out_memory, 4, 1, binobj->handle); + return_value = *(unsigned int*)out_memory; + } + else + // Get a pointer to the object in the buffer and deref it + return_value = *(unsigned int*)&binobj->buffer[binobj->buffer_pointer]; + + char result[256]; + sprintf_s<256>(result, "%u", return_value); + return result; +} + +const char *conBinaryObjectReadF32(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + DX::ScriptObject scriptobject((unsigned int)obj); + + // Not open + if (DXBinaryObjectMap.count(scriptobject.identifier) == 0) + { + scriptobject.CallMethod("onReadFailed", 0); + return "-1"; + } + + DXBinaryObject *binobj = DXBinaryObjectMap[scriptobject.identifier]; + + // Okay, so we're open, now we need to make sure we have enough bytes left in the buffer + int read_length = sizeof(float); + + if (binobj->buffer_length - (binobj->buffer_length - binobj->buffer_pointer) < read_length) + { + scriptobject.CallMethod("onReadFailed", 0); + return "-1"; + } + + float return_value = 0; + binobj->buffer_pointer -= read_length; + if (binobj->disk_streaming) + { + char out_memory[4]; + fseek(binobj->handle, binobj->buffer_pointer, SEEK_SET); + fread(out_memory, 4, 1, binobj->handle); + return_value = *(float*)out_memory; + } + else + // Get a pointer to the object in the buffer and deref it + return_value = *(float*)&binobj->buffer[binobj->buffer_pointer]; + + char result[256]; + sprintf_s<256>(result, "%f", return_value); + return result; +} + +const char *conBinaryObjectReadU8(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + DX::ScriptObject scriptobject((unsigned int)obj); + + // Not open + if (DXBinaryObjectMap.count(scriptobject.identifier) == 0) + { + scriptobject.CallMethod("onReadFailed", 0); + return "-1"; + } + + DXBinaryObject *binobj = DXBinaryObjectMap[scriptobject.identifier]; + + // Okay, so we're open, now we need to make sure we have enough bytes left in the buffer + int read_length = sizeof(unsigned char); + + if (binobj->buffer_length - (binobj->buffer_length - binobj->buffer_pointer) < read_length) + { + scriptobject.CallMethod("onReadFailed", 0); + return "-1"; + } + + unsigned char return_value = 0; + binobj->buffer_pointer -= read_length; + if (binobj->disk_streaming) + { + char out_memory[1]; + fseek(binobj->handle, binobj->buffer_pointer, SEEK_SET); + fread(out_memory, 4, 1, binobj->handle); + return_value = *(unsigned char*)out_memory; + } + else + // Get a pointer to the object in the buffer and deref it + return_value = *(unsigned char*)&binobj->buffer[binobj->buffer_pointer]; + + char result[256]; + sprintf_s<256>(result, "%u", return_value); + return result; +} + +bool conBinaryObjectSetBufferPointer(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + // We don't need a full object instance for some of these + unsigned int identifier = atoi(argv[1]); + if (DXBinaryObjectMap.count(identifier) == 0) + return false; + + DXBinaryObject *binobj = DXBinaryObjectMap[identifier]; + + int desired_value = atoi(argv[2]); + if (desired_value < 0 || desired_value > binobj->buffer_length) + return false; + + binobj->buffer_pointer = desired_value; + return true; +} + +const char *conBinaryObjectGetBufferPointer(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + unsigned int identifier = atoi(argv[1]); + if (DXBinaryObjectMap.count(identifier) == 0) + return "-1"; + + DXBinaryObject *binobj = DXBinaryObjectMap[identifier]; + + char result[256]; + sprintf_s<256>(result, "%u", binobj->buffer_pointer); + return result; +} + +const char *conBinaryObjectGetBufferLength(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + unsigned int identifier = atoi(argv[1]); + if (DXBinaryObjectMap.count(identifier) == 0) + return "-1"; + + DXBinaryObject *binobj = DXBinaryObjectMap[identifier]; + + char result[256]; + sprintf_s<256>(result, "%u", binobj->buffer_length); + return result; +} + +bool conBinaryObjectClose(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + unsigned int identifier = atoi(argv[1]); + if (DXBinaryObjectMap.count(identifier) == 0) + return false; + + DXBinaryObject *binobj = DXBinaryObjectMap[identifier]; + if (!binobj->disk_streaming) + delete binobj->buffer; + else + fclose(binobj->handle); + + delete binobj; + + DXBinaryObjectMap.erase(identifier); + return true; +} + +bool conBinaryObjectSave(Linker::SimObject *obj, S32 argc, const char *argv[]) +{ + unsigned int identifier = atoi(argv[1]); + if (DXBinaryObjectMap.count(identifier) == 0) + return false; + + char runningmod[256]; + DX::GetRunningMod(runningmod, 256); + int runningmod_length = strlen(runningmod); + + Con::errorf(0, "Got Mod: %s", runningmod); + + return true; + + int filename_length = strlen(argv[2]); + char *desired_filename = (char*)malloc(filename_length + runningmod_length + 2); + memcpy(desired_filename, argv[2], filename_length + 1); + + DX::SanitizeFileName(desired_filename, filename_length); + sprintf(desired_filename, "%s/%s", runningmod, desired_filename); + //Con::errorf(0, "Wrote to: %s", desired_filename); + + FILE *handle = fopen(desired_filename, "wb"); + DXBinaryObject *binobj = DXBinaryObjectMap[identifier]; + fwrite(binobj->buffer, binobj->buffer_length, 1, handle); + fclose(handle); + + return true; +} \ No newline at end of file diff --git a/Mod Sources/TSExtension/TSExtension/source/DXTCPObjects.cpp b/Mod Sources/TSExtension/TSExtension/source/DXTCPObjects.cpp index 36ea35e..5dde056 100644 --- a/Mod Sources/TSExtension/TSExtension/source/DXTCPObjects.cpp +++ b/Mod Sources/TSExtension/TSExtension/source/DXTCPObjects.cpp @@ -11,7 +11,7 @@ static unsigned int TSEXTENSION_RUNNINGTCPOBJECTCOUNT = 0; static DX::TCPObject *TSEXTENSION_RUNNINGTCPOBJECTS[TCPOBJECT_MAXCOUNT]; -// Since wants TS function call arguments to be of type char*, we use this +// Since TS wants function call arguments to be of type char*, we use this // helper function to painlessly pass in unsigned int arguments for things // such as the return value from WSAGetLastError(). __forceinline static char *S32ToCharPtr(unsigned int in) diff --git a/Mod Sources/TSExtension/TSExtension/source/dllmain.cpp b/Mod Sources/TSExtension/TSExtension/source/dllmain.cpp index 4e1a7fa..c4a494f 100644 --- a/Mod Sources/TSExtension/TSExtension/source/dllmain.cpp +++ b/Mod Sources/TSExtension/TSExtension/source/dllmain.cpp @@ -54,6 +54,17 @@ extern "C" Con::addMethodB("HTTPObject", "connect", &conHTTPObjectDoNothing, "Disconnects from the remote server", 6, 6); Con::addMethodB("HTTPObject", "listen", &conHTTPObjectDoNothing, "Disconnects from the remote server", 6, 6); + // BinaryObject + Con::addMethodB("BinaryObject", "openforread", &conBinaryObjectOpenForRead, "Opens the input file for reading binary data", 3, 4); + Con::addMethodB("BinaryObject", "save", &conBinaryObjectSave, "Saves the binary object data to a file", 3, 3); + Con::addMethodS("BinaryObject", "readu32", &conBinaryObjectReadU32, "Reads an unsigned int from the buffer", 2, 2); + Con::addMethodS("BinaryObject", "readf32", &conBinaryObjectReadF32, "Reads a float from the buffer", 2, 2); + Con::addMethodS("BinaryObject", "readu8", &conBinaryObjectReadU8, "Reads a unsigned char from the buffer", 2, 2); + Con::addMethodB("BinaryObject", "setbufferpointer", &conBinaryObjectSetBufferPointer, "Sets the buffer pointer", 3, 3); + Con::addMethodS("BinaryObject", "getbufferlength", &conBinaryObjectGetBufferLength, "Returns the length of the buffer", 2, 2); + Con::addMethodS("BinaryObject", "getbufferpointer", &conBinaryObjectGetBufferPointer, "Returns the buffer pointer", 2, 2); + Con::addMethodB("BinaryObject", "close", &conBinaryObjectClose, "Closes the binary object", 2, 2); + // General Con::addMethodS(NULL, "sprintf", &conSprintf,"Formats a string. See the C sprintf.", 2, 20); Con::addMethodB(NULL, "tsExtensionUpdate", &conTSExtensionUpdate,"Updates the TSExtension.", 1, 1);