From f4a7149cb25389b3d3e0b4ee1bf4bfefd7b02c29 Mon Sep 17 00:00:00 2001 From: Anna Chingaeva Date: Wed, 8 Apr 2015 20:16:37 +0400 Subject: [PATCH] LF Normalization --- .gitattributes | 17 + CompareUPK.cpp | 354 +-- DeserializeAll.cpp | 188 +- ExtractNameLists.cpp | 106 +- FindObjectByOffset.cpp | 112 +- FindObjectEntry.cpp | 118 +- ModParser.cpp | 778 +++--- ModParser.h | 138 +- ModScript.cpp | 4504 +++++++++++++++++----------------- ModScript.h | 304 +-- MoveExpandFunction.cpp | 162 +- PatchUPK.cpp | 168 +- UFlags.h | 420 ++-- UObject.cpp | 1826 +++++++------- UObject.h | 958 ++++---- UObjectFactory.cpp | 484 ++-- UObjectFactory.h | 34 +- UPKInfo.h | 476 ++-- UPKUtils.cpp | 1756 ++++++------- UPKUtils.h | 126 +- doc/PatchUPK_Mod_Example.txt | 282 +-- doc/PatchUPK_Readme.txt | 970 ++++---- doc/UPKUtils_Readme.txt | 338 +-- 23 files changed, 7318 insertions(+), 7301 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/CompareUPK.cpp b/CompareUPK.cpp index 64e2d6c..57ff18e 100644 --- a/CompareUPK.cpp +++ b/CompareUPK.cpp @@ -1,177 +1,177 @@ -#include -#include - -#include "UPKInfo.h" -#include - -using namespace std; - -int main(int argN, char* argV[]) -{ - cout << "CompareUPK" << endl; - - if (argN != 3) - { - cerr << "Usage: CompareUPK OldPackage.upk NewPackage.upk" << endl; - return 1; - } - - ifstream OldFile(argV[1], ios::binary); - if (!OldFile.is_open()) - { - cerr << "Can't open " << argV[1] << endl; - return 1; - } - ifstream NewFile(argV[2], ios::binary); - if (!NewFile.is_open()) - { - cerr << "Can't open " << argV[2] << endl; - return 1; - } - - UPKInfo OldPackageInfo(OldFile), NewPackageInfo(NewFile); - - UPKReadErrors OldErr = OldPackageInfo.GetError(), NewErr = NewPackageInfo.GetError(); - - if (OldErr != UPKReadErrors::NoErrors) - { - cerr << "Error reading package: " << argV[1] << std::endl - << FormatReadErrors(OldErr); - return 1; - } - if (NewErr != UPKReadErrors::NoErrors) - { - cerr << "Error reading package: " << argV[2] << std::endl - << FormatReadErrors(NewErr); - return 1; - } - - if (OldPackageInfo.GetSummary().Version != NewPackageInfo.GetSummary().Version) - { - cout << "Old Version: " << OldPackageInfo.GetSummary().Version - << "\tNew Version: " << NewPackageInfo.GetSummary().Version << std::endl; - } - if (OldPackageInfo.GetSummary().NameCount != NewPackageInfo.GetSummary().NameCount) - { - cout << "Old NameCount: " << OldPackageInfo.GetSummary().NameCount - << "\tNew NameCount: " << NewPackageInfo.GetSummary().NameCount - << "\tNew-Old = " << ((int)NewPackageInfo.GetSummary().NameCount - (int)OldPackageInfo.GetSummary().NameCount) << std::endl; - } - if (OldPackageInfo.GetSummary().ExportCount != NewPackageInfo.GetSummary().ExportCount) - { - cout << "Old ExportCount: " << OldPackageInfo.GetSummary().ExportCount - << "\tNew ExportCount: " << NewPackageInfo.GetSummary().ExportCount - << "\tNew-Old = " << ((int)NewPackageInfo.GetSummary().ExportCount - (int)OldPackageInfo.GetSummary().ExportCount) << std::endl; - } - if (OldPackageInfo.GetSummary().ImportCount != NewPackageInfo.GetSummary().ImportCount) - { - cout << "Old ImportCount: " << OldPackageInfo.GetSummary().ImportCount - << "\tNew ImportCount: " << NewPackageInfo.GetSummary().ImportCount - << "\tNew-Old = " << ((int)NewPackageInfo.GetSummary().ImportCount - (int)OldPackageInfo.GetSummary().ImportCount) << std::endl; - } - - cout << "Analyzing names:\n"; - - int numDeletedNames = 0, numNewNames = 0; - - for (unsigned i = 0; i < OldPackageInfo.GetSummary().NameCount; ++i) - { - int foundIdx = NewPackageInfo.FindName(OldPackageInfo.GetNameEntry(i).Name); - if (foundIdx < 0) - { - cout << "Deleted name: " << OldPackageInfo.GetNameEntry(i).Name - << " (index = " << i << ")\n"; - ++numDeletedNames; - } - } - - cout << "Number of deleted names = " << numDeletedNames << std::endl; - - for (unsigned i = 0; i < NewPackageInfo.GetSummary().NameCount; ++i) - { - int foundIdx = OldPackageInfo.FindName(NewPackageInfo.GetNameEntry(i).Name); - if (foundIdx < 0) - { - cout << "New name: " << NewPackageInfo.GetNameEntry(i).Name - << " (index = " << i << ")\n"; - ++numNewNames; - } - } - - cout << "Number of new names = " << numNewNames << std::endl; - - cout << "Analyzing imports:\n"; - - int numDeletedImports = 0, numNewImports = 0; - - for (unsigned i = 1; i <= OldPackageInfo.GetSummary().ImportCount; ++i) - { - UObjectReference foundIdx = NewPackageInfo.FindObject(OldPackageInfo.GetImportEntry(i).FullName, false); - if (foundIdx == 0) - { - cout << "Deleted import: " << OldPackageInfo.GetImportEntry(i).FullName - << " (index = " << i << ")\n"; - ++numDeletedImports; - } - } - - cout << "Number of deleted imports = " << numDeletedImports << std::endl; - - for (unsigned i = 1; i <= NewPackageInfo.GetSummary().ImportCount; ++i) - { - UObjectReference foundIdx = OldPackageInfo.FindObject(NewPackageInfo.GetImportEntry(i).FullName, false); - if (foundIdx == 0) - { - cout << "New import: " << NewPackageInfo.GetImportEntry(i).FullName - << " (index = " << i << ")\n"; - ++numNewImports; - } - } - - cout << "Number of new imports = " << numNewImports << std::endl; - - cout << "Analyzing exports:\n"; - - int numDeletedExports = 0, numNewExports = 0, numResizedExports = 0; - - for (unsigned i = 1; i <= OldPackageInfo.GetSummary().ExportCount; ++i) - { - UObjectReference foundIdx = NewPackageInfo.FindObject(OldPackageInfo.GetExportEntry(i).FullName); - if (foundIdx == 0) - { - cout << "Deleted export: " << OldPackageInfo.GetExportEntry(i).FullName - << " (index = " << i << ")\n"; - ++numDeletedExports; - } - } - - cout << "Number of deleted exports = " << numDeletedExports << std::endl; - - for (unsigned i = 1; i <= NewPackageInfo.GetSummary().ExportCount; ++i) - { - UObjectReference foundIdx = OldPackageInfo.FindObject(NewPackageInfo.GetExportEntry(i).FullName); - if (foundIdx == 0) - { - cout << "New export: " << NewPackageInfo.GetExportEntry(i).FullName - << " (index = " << i << ")\n"; - ++numNewExports; - } - else - { - if (NewPackageInfo.GetExportEntry(i).SerialSize != OldPackageInfo.GetExportEntry(foundIdx).SerialSize) - { - cout << "SerialSize changed: " << NewPackageInfo.GetExportEntry(i).FullName - << " (index = " << i << ")\n" - << "\tOld SerialSize: " << OldPackageInfo.GetExportEntry(foundIdx).SerialSize - << "\tNew SerialSize: " << NewPackageInfo.GetExportEntry(i).SerialSize - << "\tNew-Old = " << (NewPackageInfo.GetExportEntry(i).SerialSize - OldPackageInfo.GetExportEntry(foundIdx).SerialSize) << std::endl; - ++numResizedExports; - } - } - } - - cout << "Number of new exports = " << numNewExports << std::endl; - cout << "Number of resized exports = " << numResizedExports << std::endl; - - return 0; -} +#include +#include + +#include "UPKInfo.h" +#include + +using namespace std; + +int main(int argN, char* argV[]) +{ + cout << "CompareUPK" << endl; + + if (argN != 3) + { + cerr << "Usage: CompareUPK OldPackage.upk NewPackage.upk" << endl; + return 1; + } + + ifstream OldFile(argV[1], ios::binary); + if (!OldFile.is_open()) + { + cerr << "Can't open " << argV[1] << endl; + return 1; + } + ifstream NewFile(argV[2], ios::binary); + if (!NewFile.is_open()) + { + cerr << "Can't open " << argV[2] << endl; + return 1; + } + + UPKInfo OldPackageInfo(OldFile), NewPackageInfo(NewFile); + + UPKReadErrors OldErr = OldPackageInfo.GetError(), NewErr = NewPackageInfo.GetError(); + + if (OldErr != UPKReadErrors::NoErrors) + { + cerr << "Error reading package: " << argV[1] << std::endl + << FormatReadErrors(OldErr); + return 1; + } + if (NewErr != UPKReadErrors::NoErrors) + { + cerr << "Error reading package: " << argV[2] << std::endl + << FormatReadErrors(NewErr); + return 1; + } + + if (OldPackageInfo.GetSummary().Version != NewPackageInfo.GetSummary().Version) + { + cout << "Old Version: " << OldPackageInfo.GetSummary().Version + << "\tNew Version: " << NewPackageInfo.GetSummary().Version << std::endl; + } + if (OldPackageInfo.GetSummary().NameCount != NewPackageInfo.GetSummary().NameCount) + { + cout << "Old NameCount: " << OldPackageInfo.GetSummary().NameCount + << "\tNew NameCount: " << NewPackageInfo.GetSummary().NameCount + << "\tNew-Old = " << ((int)NewPackageInfo.GetSummary().NameCount - (int)OldPackageInfo.GetSummary().NameCount) << std::endl; + } + if (OldPackageInfo.GetSummary().ExportCount != NewPackageInfo.GetSummary().ExportCount) + { + cout << "Old ExportCount: " << OldPackageInfo.GetSummary().ExportCount + << "\tNew ExportCount: " << NewPackageInfo.GetSummary().ExportCount + << "\tNew-Old = " << ((int)NewPackageInfo.GetSummary().ExportCount - (int)OldPackageInfo.GetSummary().ExportCount) << std::endl; + } + if (OldPackageInfo.GetSummary().ImportCount != NewPackageInfo.GetSummary().ImportCount) + { + cout << "Old ImportCount: " << OldPackageInfo.GetSummary().ImportCount + << "\tNew ImportCount: " << NewPackageInfo.GetSummary().ImportCount + << "\tNew-Old = " << ((int)NewPackageInfo.GetSummary().ImportCount - (int)OldPackageInfo.GetSummary().ImportCount) << std::endl; + } + + cout << "Analyzing names:\n"; + + int numDeletedNames = 0, numNewNames = 0; + + for (unsigned i = 0; i < OldPackageInfo.GetSummary().NameCount; ++i) + { + int foundIdx = NewPackageInfo.FindName(OldPackageInfo.GetNameEntry(i).Name); + if (foundIdx < 0) + { + cout << "Deleted name: " << OldPackageInfo.GetNameEntry(i).Name + << " (index = " << i << ")\n"; + ++numDeletedNames; + } + } + + cout << "Number of deleted names = " << numDeletedNames << std::endl; + + for (unsigned i = 0; i < NewPackageInfo.GetSummary().NameCount; ++i) + { + int foundIdx = OldPackageInfo.FindName(NewPackageInfo.GetNameEntry(i).Name); + if (foundIdx < 0) + { + cout << "New name: " << NewPackageInfo.GetNameEntry(i).Name + << " (index = " << i << ")\n"; + ++numNewNames; + } + } + + cout << "Number of new names = " << numNewNames << std::endl; + + cout << "Analyzing imports:\n"; + + int numDeletedImports = 0, numNewImports = 0; + + for (unsigned i = 1; i <= OldPackageInfo.GetSummary().ImportCount; ++i) + { + UObjectReference foundIdx = NewPackageInfo.FindObject(OldPackageInfo.GetImportEntry(i).FullName, false); + if (foundIdx == 0) + { + cout << "Deleted import: " << OldPackageInfo.GetImportEntry(i).FullName + << " (index = " << i << ")\n"; + ++numDeletedImports; + } + } + + cout << "Number of deleted imports = " << numDeletedImports << std::endl; + + for (unsigned i = 1; i <= NewPackageInfo.GetSummary().ImportCount; ++i) + { + UObjectReference foundIdx = OldPackageInfo.FindObject(NewPackageInfo.GetImportEntry(i).FullName, false); + if (foundIdx == 0) + { + cout << "New import: " << NewPackageInfo.GetImportEntry(i).FullName + << " (index = " << i << ")\n"; + ++numNewImports; + } + } + + cout << "Number of new imports = " << numNewImports << std::endl; + + cout << "Analyzing exports:\n"; + + int numDeletedExports = 0, numNewExports = 0, numResizedExports = 0; + + for (unsigned i = 1; i <= OldPackageInfo.GetSummary().ExportCount; ++i) + { + UObjectReference foundIdx = NewPackageInfo.FindObject(OldPackageInfo.GetExportEntry(i).FullName); + if (foundIdx == 0) + { + cout << "Deleted export: " << OldPackageInfo.GetExportEntry(i).FullName + << " (index = " << i << ")\n"; + ++numDeletedExports; + } + } + + cout << "Number of deleted exports = " << numDeletedExports << std::endl; + + for (unsigned i = 1; i <= NewPackageInfo.GetSummary().ExportCount; ++i) + { + UObjectReference foundIdx = OldPackageInfo.FindObject(NewPackageInfo.GetExportEntry(i).FullName); + if (foundIdx == 0) + { + cout << "New export: " << NewPackageInfo.GetExportEntry(i).FullName + << " (index = " << i << ")\n"; + ++numNewExports; + } + else + { + if (NewPackageInfo.GetExportEntry(i).SerialSize != OldPackageInfo.GetExportEntry(foundIdx).SerialSize) + { + cout << "SerialSize changed: " << NewPackageInfo.GetExportEntry(i).FullName + << " (index = " << i << ")\n" + << "\tOld SerialSize: " << OldPackageInfo.GetExportEntry(foundIdx).SerialSize + << "\tNew SerialSize: " << NewPackageInfo.GetExportEntry(i).SerialSize + << "\tNew-Old = " << (NewPackageInfo.GetExportEntry(i).SerialSize - OldPackageInfo.GetExportEntry(foundIdx).SerialSize) << std::endl; + ++numResizedExports; + } + } + } + + cout << "Number of new exports = " << numNewExports << std::endl; + cout << "Number of resized exports = " << numResizedExports << std::endl; + + return 0; +} diff --git a/DeserializeAll.cpp b/DeserializeAll.cpp index 2bccc79..437295d 100644 --- a/DeserializeAll.cpp +++ b/DeserializeAll.cpp @@ -1,94 +1,94 @@ -#include -#include - -#include -#include -#include - -#include "UPKUtils.h" - -using namespace std; - -string CreatePath(string fullName, string dirName) -{ - string str = fullName; - vector names; - unsigned pos = str.find('.'); - if (pos == string::npos) - return (dirName + "\\" + str + ".txt"); - while (pos != string::npos) - { - names.push_back(str.substr(0, pos)); - str = str.substr(pos + 1); - pos = str.find('.'); - } - string ret = str; - str = dirName; - for (unsigned i = 0; i < names.size(); ++i) - { - str += "\\" + names[i]; - if (!wxDirExists(str)) - wxMkdir(str); - } - return (str + "\\" + ret + ".txt"); -} - -int main(int argN, char* argV[]) -{ - cout << "DeserializeAll" << endl; - - if (argN < 2 || argN > 3) - { - cerr << "Usage: DeserializeAll UnpackedResourceFile.upk [NameMask]" << endl; - return 1; - } - - UPKUtils package(argV[1]); - - UPKReadErrors err = package.GetError(); - - if (err != UPKReadErrors::NoErrors) - { - cerr << "Error reading package:\n" << FormatReadErrors(err); - if (package.IsCompressed()) - cerr << "Compression flags:\n" << FormatCompressionFlags(package.GetCompressionFlags()); - return 1; - } - - string NameMask = ""; - if (argN == 3) - NameMask = argV[2]; - - wxString dirName; - wxFileName::SplitPath(argV[1], nullptr, nullptr, &dirName, nullptr); - - if (!wxDirExists(dirName)) - wxMkdir(dirName); - - string listPath = dirName.ToStdString() + ".txt"; - ofstream out(listPath); - out << package.FormatSummary() << std::endl - << package.FormatNames(false) << std::endl - << package.FormatImports(false) << std::endl - << package.FormatExports(false); - out.close(); - - vector ExportTable = package.GetExportTable(); - - for (unsigned i = 1; i < ExportTable.size(); ++i) - { - cout << ExportTable[i].FullName < +#include + +#include +#include +#include + +#include "UPKUtils.h" + +using namespace std; + +string CreatePath(string fullName, string dirName) +{ + string str = fullName; + vector names; + unsigned pos = str.find('.'); + if (pos == string::npos) + return (dirName + "\\" + str + ".txt"); + while (pos != string::npos) + { + names.push_back(str.substr(0, pos)); + str = str.substr(pos + 1); + pos = str.find('.'); + } + string ret = str; + str = dirName; + for (unsigned i = 0; i < names.size(); ++i) + { + str += "\\" + names[i]; + if (!wxDirExists(str)) + wxMkdir(str); + } + return (str + "\\" + ret + ".txt"); +} + +int main(int argN, char* argV[]) +{ + cout << "DeserializeAll" << endl; + + if (argN < 2 || argN > 3) + { + cerr << "Usage: DeserializeAll UnpackedResourceFile.upk [NameMask]" << endl; + return 1; + } + + UPKUtils package(argV[1]); + + UPKReadErrors err = package.GetError(); + + if (err != UPKReadErrors::NoErrors) + { + cerr << "Error reading package:\n" << FormatReadErrors(err); + if (package.IsCompressed()) + cerr << "Compression flags:\n" << FormatCompressionFlags(package.GetCompressionFlags()); + return 1; + } + + string NameMask = ""; + if (argN == 3) + NameMask = argV[2]; + + wxString dirName; + wxFileName::SplitPath(argV[1], nullptr, nullptr, &dirName, nullptr); + + if (!wxDirExists(dirName)) + wxMkdir(dirName); + + string listPath = dirName.ToStdString() + ".txt"; + ofstream out(listPath); + out << package.FormatSummary() << std::endl + << package.FormatNames(false) << std::endl + << package.FormatImports(false) << std::endl + << package.FormatExports(false); + out.close(); + + vector ExportTable = package.GetExportTable(); + + for (unsigned i = 1; i < ExportTable.size(); ++i) + { + cout << ExportTable[i].FullName < -#include - -#include "UPKInfo.h" -#include - -using namespace std; - -int main(int argN, char* argV[]) -{ - cout << "ExtractNameLists" << endl; - - if (argN < 2 || argN > 3) - { - cerr << "Usage: ExtractNameLists UnpackedResourceFile.upk [/v]" << endl; - return 1; - } - - ifstream package(argV[1], ios::binary); - if (!package.is_open()) - { - cerr << "Can't open " << argV[1] << endl; - return 1; - } - - bool verbose = false; - - if (argN == 3 && string(argV[2]) == "/v") - { - verbose = true; - } - - UPKInfo PackageInfo(package); - - UPKReadErrors err = PackageInfo.GetError(); - - if (err != UPKReadErrors::NoErrors) - { - cerr << "Error reading package:\n" << FormatReadErrors(err); - if (PackageInfo.IsCompressed()) - { - cout << PackageInfo.FormatSummary(); - } - return 1; - } - - cout << PackageInfo.FormatSummary() << std::endl - << PackageInfo.FormatNames(verbose) << std::endl - << PackageInfo.FormatImports(verbose) << std::endl - << PackageInfo.FormatExports(verbose); - - return 0; -} +#include +#include + +#include "UPKInfo.h" +#include + +using namespace std; + +int main(int argN, char* argV[]) +{ + cout << "ExtractNameLists" << endl; + + if (argN < 2 || argN > 3) + { + cerr << "Usage: ExtractNameLists UnpackedResourceFile.upk [/v]" << endl; + return 1; + } + + ifstream package(argV[1], ios::binary); + if (!package.is_open()) + { + cerr << "Can't open " << argV[1] << endl; + return 1; + } + + bool verbose = false; + + if (argN == 3 && string(argV[2]) == "/v") + { + verbose = true; + } + + UPKInfo PackageInfo(package); + + UPKReadErrors err = PackageInfo.GetError(); + + if (err != UPKReadErrors::NoErrors) + { + cerr << "Error reading package:\n" << FormatReadErrors(err); + if (PackageInfo.IsCompressed()) + { + cout << PackageInfo.FormatSummary(); + } + return 1; + } + + cout << PackageInfo.FormatSummary() << std::endl + << PackageInfo.FormatNames(verbose) << std::endl + << PackageInfo.FormatImports(verbose) << std::endl + << PackageInfo.FormatExports(verbose); + + return 0; +} diff --git a/FindObjectByOffset.cpp b/FindObjectByOffset.cpp index bf383a3..dc783fe 100644 --- a/FindObjectByOffset.cpp +++ b/FindObjectByOffset.cpp @@ -1,56 +1,56 @@ -#include - -#include "UPKInfo.h" -#include -#include - -using namespace std; - -int main(int argN, char* argV[]) -{ - cout << "FindObjectByOffset" << endl; - - if (argN != 3) - { - cerr << "Usage: FindObjectByOffset UnpackedResourceFile.upk offset" << endl; - return 1; - } - - ifstream package(argV[1], ios::binary); - if (!package.is_open()) - { - cerr << "Can't open " << argV[1] << endl; - return 1; - } - - size_t Offset = 0; - string str(argV[2]); - istringstream ss(str); - if (str.find("0x") != string::npos) - ss >> hex >> Offset; - else - ss >> dec >> Offset; - - cout << "Offset = " << FormatHEX((uint32_t)Offset) << " (" << Offset << ")\n"; - - UPKInfo PackageInfo(package); - UPKReadErrors err = PackageInfo.GetError(); - if (err != UPKReadErrors::NoErrors) - { - cerr << "Error reading package:\n" << FormatReadErrors(err); - if (PackageInfo.IsCompressed()) - { - cout << PackageInfo.FormatSummary(); - } - return 1; - } - - UObjectReference ObjRef = PackageInfo.FindObjectByOffset(Offset); - if (ObjRef <= 0) - { - cerr << "Can't find object by specified offset!\n"; - } - cout << "Found object: " << PackageInfo.GetExportEntry(ObjRef).FullName << std::endl; - - return 0; -} +#include + +#include "UPKInfo.h" +#include +#include + +using namespace std; + +int main(int argN, char* argV[]) +{ + cout << "FindObjectByOffset" << endl; + + if (argN != 3) + { + cerr << "Usage: FindObjectByOffset UnpackedResourceFile.upk offset" << endl; + return 1; + } + + ifstream package(argV[1], ios::binary); + if (!package.is_open()) + { + cerr << "Can't open " << argV[1] << endl; + return 1; + } + + size_t Offset = 0; + string str(argV[2]); + istringstream ss(str); + if (str.find("0x") != string::npos) + ss >> hex >> Offset; + else + ss >> dec >> Offset; + + cout << "Offset = " << FormatHEX((uint32_t)Offset) << " (" << Offset << ")\n"; + + UPKInfo PackageInfo(package); + UPKReadErrors err = PackageInfo.GetError(); + if (err != UPKReadErrors::NoErrors) + { + cerr << "Error reading package:\n" << FormatReadErrors(err); + if (PackageInfo.IsCompressed()) + { + cout << PackageInfo.FormatSummary(); + } + return 1; + } + + UObjectReference ObjRef = PackageInfo.FindObjectByOffset(Offset); + if (ObjRef <= 0) + { + cerr << "Can't find object by specified offset!\n"; + } + cout << "Found object: " << PackageInfo.GetExportEntry(ObjRef).FullName << std::endl; + + return 0; +} diff --git a/FindObjectEntry.cpp b/FindObjectEntry.cpp index d64105c..eac769d 100644 --- a/FindObjectEntry.cpp +++ b/FindObjectEntry.cpp @@ -1,59 +1,59 @@ -#include - -#include "UPKUtils.h" - -using namespace std; - -int main(int argN, char* argV[]) -{ - cout << "FindObjectEntry" << endl; - - if (argN < 3 || argN > 4) - { - cerr << "Usage: FindObjectEntry UnpackedResourceFile.upk ObjectName [/d]" << endl; - return 1; - } - - UPKUtils package(argV[1]); - - UPKReadErrors err = package.GetError(); - - if (err != UPKReadErrors::NoErrors) - { - cerr << "Error reading package:\n" << FormatReadErrors(err); - if (package.IsCompressed()) - cerr << "Compression flags:\n" << FormatCompressionFlags(package.GetCompressionFlags()); - return 1; - } - - string NameToFind = argV[2]; - - cout << "Name to find: " << NameToFind << endl; - - UObjectReference ObjRef = package.FindObject(NameToFind, false); - - if (ObjRef == 0) - { - cerr << "Can't find object entry by name " << NameToFind << endl; - return 1; - } - if (ObjRef > 0 && argN == 4 && string(argV[3]) == "/d") - { - package.SaveExportData((uint32_t)ObjRef); - } - if (ObjRef > 0) - { - cout << "Found Export Object:\n" << package.FormatExport(ObjRef, true); - } - else - { - cout << "Found Import Object:\n" << package.FormatImport(-ObjRef, true); - } - - if (ObjRef > 0) - { - cout << "Attempting deserialization:\n" << package.Deserialize(ObjRef, true); - } - - return 0; -} +#include + +#include "UPKUtils.h" + +using namespace std; + +int main(int argN, char* argV[]) +{ + cout << "FindObjectEntry" << endl; + + if (argN < 3 || argN > 4) + { + cerr << "Usage: FindObjectEntry UnpackedResourceFile.upk ObjectName [/d]" << endl; + return 1; + } + + UPKUtils package(argV[1]); + + UPKReadErrors err = package.GetError(); + + if (err != UPKReadErrors::NoErrors) + { + cerr << "Error reading package:\n" << FormatReadErrors(err); + if (package.IsCompressed()) + cerr << "Compression flags:\n" << FormatCompressionFlags(package.GetCompressionFlags()); + return 1; + } + + string NameToFind = argV[2]; + + cout << "Name to find: " << NameToFind << endl; + + UObjectReference ObjRef = package.FindObject(NameToFind, false); + + if (ObjRef == 0) + { + cerr << "Can't find object entry by name " << NameToFind << endl; + return 1; + } + if (ObjRef > 0 && argN == 4 && string(argV[3]) == "/d") + { + package.SaveExportData((uint32_t)ObjRef); + } + if (ObjRef > 0) + { + cout << "Found Export Object:\n" << package.FormatExport(ObjRef, true); + } + else + { + cout << "Found Import Object:\n" << package.FormatImport(-ObjRef, true); + } + + if (ObjRef > 0) + { + cout << "Attempting deserialization:\n" << package.Deserialize(ObjRef, true); + } + + return 0; +} diff --git a/ModParser.cpp b/ModParser.cpp index 5576d48..e773dd7 100644 --- a/ModParser.cpp +++ b/ModParser.cpp @@ -1,389 +1,389 @@ -#include "ModParser.h" - -#include -#include -#include -#include - -std::string Trim(std::string str) -{ - std::string ret = str, wspc(" \t\f\v\n\r"); - size_t pos = ret.find_first_not_of(wspc); - if (pos != std::string::npos) - { - ret = ret.substr(pos); - } - else - { - return ""; - } - pos = ret.find_last_not_of(wspc); - ret = ret.substr(0, pos + 1); - return ret; -} - -size_t SplitAt(char ch, std::string in, std::string& out1, std::string& out2) -{ - size_t pos = in.find(ch); - if (pos != std::string::npos) - { - out1 = Trim(in.substr(0, pos)); - out2 = Trim(in.substr(pos + 1)); - } - else - { - out1 = Trim(in); - out2 = ""; - } - return pos; -} - -const char chLookup[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - -std::string MakeTextBlock(char *data, size_t dataSize) -{ - std::string out = ""; - for (unsigned i = 0; i < dataSize; ++i) - { - if ((i%16 == 0) && (i != 0)) - out += '\n'; - uint8_t ch = data[i]; - uint8_t up = ((ch & 0xF0) >> 4); - uint8_t lw = (ch & 0x0F); - out += chLookup[up]; - out += chLookup[lw]; - out += ' '; - } - out += '\n'; - return out; -} - -std::string GetFilename(std::string str) -{ - unsigned found = str.find_last_of("/\\"); - return str.substr(found + 1); -} - -std::string GetStringValue(const std::string& TextBuffer) -{ - std::string str = TextBuffer; - if (str.length() < 1) - return ""; - str = str.substr(0, str.find_first_of("\n")); /// get first line - str = Trim(str); /// remove leading and trailing white-spaces - if (str.find('\"') != std::string::npos) /// remove "" - { - str = str.substr(str.find_first_not_of("\"")); - str = str.substr(0, str.find_last_not_of("\"") + 1); - } - return str; -} - -std::vector GetDataChunk(const std::string& TextBuffer) -{ - std::vector data; - if (TextBuffer.length() < 1) - return data; - std::istringstream ss(TextBuffer); - while (ss.good()) - { - int byte; - ss >> std::hex >> byte; - if (!ss.fail() && !ss.bad()) - data.push_back(byte); - } - return data; -} - -int GetIntValue(const std::string& TextBuffer) -{ - int val = 0; - std::string str = ::GetStringValue(TextBuffer); - if (str.length() < 1) - return 0; - //val = std::stoi(str, nullptr, 0); - std::istringstream ss(str); - /*if (str.find("0x") != std::string::npos) - ss >> std::hex >> val; - else - ss >> std::dec >> val;*/ - ss >> val; - return val; -} - -unsigned GetUnsignedValue(const std::string& TextBuffer) -{ - unsigned val = 0; - std::string str = ::GetStringValue(TextBuffer); - if (str.length() < 1) - return 0; - std::istringstream ss(str); - if (str.find("0x") != std::string::npos) - ss >> std::hex >> val; - else - ss >> std::dec >> val; - return val; -} - -float GetFloatValue(const std::string& TextBuffer) -{ - float val = 0; - std::string str = ::GetStringValue(TextBuffer); - if (str.length() < 1) - return 0; - std::istringstream ss(str); - ss >> val; - return val; -} - -std::string ModParser::GetText() -{ - std::string str = ""; - std::string line = ""; - size_t savedPos = modFile.tellg(); - line = GetLine(); /// read key/section line - if (isKey) - { - str += line.substr(line.find("=") + 1); - while ( modFile.good() ) - { - savedPos = modFile.tellg(); - line = GetLine(); - if (FindKey(line) != -1 || FindSection(line) != -1) - { - modFile.clear(); /// to parse last line correctly - break; - } - str += std::string("\n") + line; - } - } - else if (isSection) - { - while ( modFile.good() ) - { - savedPos = modFile.tellg(); - line = GetLine(); - if (FindKey(line) != -1 || FindSection(line) != -1) - { - modFile.clear(); /// to parse last line correctly - break; - } - if (str != "") str += "\n"; - str += line; - } - } - modFile.seekg(savedPos, std::ios::beg); - return str; -} - -std::string ModParser::GetTextValue() -{ - return Value; -} - -std::vector ModParser::GetDataChunk() -{ - return ::GetDataChunk(Value); -} - -std::string ModParser::GetStringValue() -{ - return ::GetStringValue(Value); -} - -int ModParser::GetIntValue() -{ - return ::GetIntValue(Value); -} - -float ModParser::GetFloatValue() -{ - return ::GetFloatValue(Value); -} - -std::string ModParser::GetLine() -{ - std::string line = ""; - int ch = 0; - while(ch != 0x0D && ch != 0x0A && modFile.good()) - { - ch = modFile.get(); - if (modFile.fail() || modFile.bad()) - break; - if (CStyleComments && ch == '/' && modFile.peek() == '/') - { - while (ch != 0x0D && ch != 0x0A && modFile.good()) - ch = modFile.get(); - } - else if (CStyleComments && ch == '/' && modFile.peek() == '*') - { - while (modFile.good()) - { - ch = modFile.get(); - if (ch == '*' && modFile.peek() == '/') - { - ch = modFile.get(); - break; - } - } - } - else if (ch == commentLine) - { - while (ch != 0x0D && ch != 0x0A && modFile.good()) - ch = modFile.get(); - } - else if (ch == commentBegin) - { - while (ch != commentEnd && modFile.good()) - ch = modFile.get(); - } - else if (ch != 0x0D && ch != 0x0A && modFile.good()) - { - line += ch; - } - } - if (modFile.peek() == 0x0A || modFile.peek() == 0x0D) - modFile.get(); - return line; -} - -int ModParser::FindNext() -{ - if (!modFile.good()) - return -1; - Name = ""; - Value = ""; - Index = -1; - int idx = -1, keyIdx = -1, sectionIdx = -1; - std::string line = ""; - size_t savedPos = modFile.tellg(); - while ( keyIdx == -1 && sectionIdx == -1 && modFile.good() ) - { - savedPos = modFile.tellg(); - line = GetLine(); - keyIdx = FindKey(line); - sectionIdx = FindSection(line); - } - modFile.clear(); /// to get last line value correctly - modFile.seekg(savedPos, std::ios::beg); - isKey = (keyIdx != -1); - isSection = (sectionIdx != -1); - if (keyIdx != -1) - { - idx = keyIdx; - Name = keyNames[idx]; - } - if (sectionIdx != -1) - { - idx = sectionIdx; - Name = sectionNames[idx]; - } - if (idx != -1) - { - Value = GetText(); - } - Index = idx; - return idx; -} - -int ModParser::FindKey(std::string str) -{ - size_t pos = str.find("="); - if (pos == std::string::npos) - return -1; - std::string name = Trim(str.substr(0, pos)); - /*std::string name = str.substr(0, pos); - name = name.substr(name.find_first_not_of(" ")); - name = name.substr(0, name.find_last_not_of(" ") + 1);*/ - int idx = FindKeyNameIdx(name); - return idx; -} - -int ModParser::FindSection(std::string str) -{ - if (str.find("[") == std::string::npos || str.find("]") == std::string::npos) - return -1; - std::string name = Trim(str); - /*std::string name = str; - name = name.substr(name.find_first_not_of(" ")); - name = name.substr(0, name.find_last_not_of(" ") + 1);*/ - int idx = FindSectionNameIdx(name); - return idx; -} - -int ModParser::FindKeyNameIdx(std::string name) -{ - int idx = -1; - for (unsigned int i = 0; i < keyNames.size(); ++i) - if (keyNames[i] == name) - idx = i; - return idx; -} - -int ModParser::FindSectionNameIdx(std::string name) -{ - int idx = -1; - for (unsigned int i = 0; i < sectionNames.size(); ++i) - if (sectionNames[i] == name) - idx = i; - return idx; -} - -bool ModParser::OpenModFile(const char* name) -{ - if (modFile.is_open()) - { - modFile.close(); - modFile.clear(); - } - modFile.open(name, std::ios::binary); - if (!modFile.good()) - return false; - /// check if file is text - while (modFile.good()) - { - char ch = modFile.get(); - if (modFile.eof()) - break; - if (ch < 1 || ch > 127) - { - modFile.close(); - return false; - } - } - modFile.clear(); - modFile.seekg(0); - isKey = false; - isSection = false; - Name = ""; - Value = ""; - Index = -1; - return true; -} - -void ModParser::AddKeyName(std::string name) -{ - keyNames.push_back(name); -} - -void ModParser::AddSectionName(std::string name) -{ - sectionNames.push_back(name); -} - -void ModParser::SetKeyNames(std::vector names) -{ - keyNames = names; -} - -void ModParser::SetSectionNames(std::vector names) -{ - sectionNames = names; -} - -void ModParser::SetCommentMarkers(char begMarker, char endMarker, char lineMarker) -{ - commentBegin = begMarker; - commentEnd = endMarker; - commentLine = lineMarker; -} +#include "ModParser.h" + +#include +#include +#include +#include + +std::string Trim(std::string str) +{ + std::string ret = str, wspc(" \t\f\v\n\r"); + size_t pos = ret.find_first_not_of(wspc); + if (pos != std::string::npos) + { + ret = ret.substr(pos); + } + else + { + return ""; + } + pos = ret.find_last_not_of(wspc); + ret = ret.substr(0, pos + 1); + return ret; +} + +size_t SplitAt(char ch, std::string in, std::string& out1, std::string& out2) +{ + size_t pos = in.find(ch); + if (pos != std::string::npos) + { + out1 = Trim(in.substr(0, pos)); + out2 = Trim(in.substr(pos + 1)); + } + else + { + out1 = Trim(in); + out2 = ""; + } + return pos; +} + +const char chLookup[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +std::string MakeTextBlock(char *data, size_t dataSize) +{ + std::string out = ""; + for (unsigned i = 0; i < dataSize; ++i) + { + if ((i%16 == 0) && (i != 0)) + out += '\n'; + uint8_t ch = data[i]; + uint8_t up = ((ch & 0xF0) >> 4); + uint8_t lw = (ch & 0x0F); + out += chLookup[up]; + out += chLookup[lw]; + out += ' '; + } + out += '\n'; + return out; +} + +std::string GetFilename(std::string str) +{ + unsigned found = str.find_last_of("/\\"); + return str.substr(found + 1); +} + +std::string GetStringValue(const std::string& TextBuffer) +{ + std::string str = TextBuffer; + if (str.length() < 1) + return ""; + str = str.substr(0, str.find_first_of("\n")); /// get first line + str = Trim(str); /// remove leading and trailing white-spaces + if (str.find('\"') != std::string::npos) /// remove "" + { + str = str.substr(str.find_first_not_of("\"")); + str = str.substr(0, str.find_last_not_of("\"") + 1); + } + return str; +} + +std::vector GetDataChunk(const std::string& TextBuffer) +{ + std::vector data; + if (TextBuffer.length() < 1) + return data; + std::istringstream ss(TextBuffer); + while (ss.good()) + { + int byte; + ss >> std::hex >> byte; + if (!ss.fail() && !ss.bad()) + data.push_back(byte); + } + return data; +} + +int GetIntValue(const std::string& TextBuffer) +{ + int val = 0; + std::string str = ::GetStringValue(TextBuffer); + if (str.length() < 1) + return 0; + //val = std::stoi(str, nullptr, 0); + std::istringstream ss(str); + /*if (str.find("0x") != std::string::npos) + ss >> std::hex >> val; + else + ss >> std::dec >> val;*/ + ss >> val; + return val; +} + +unsigned GetUnsignedValue(const std::string& TextBuffer) +{ + unsigned val = 0; + std::string str = ::GetStringValue(TextBuffer); + if (str.length() < 1) + return 0; + std::istringstream ss(str); + if (str.find("0x") != std::string::npos) + ss >> std::hex >> val; + else + ss >> std::dec >> val; + return val; +} + +float GetFloatValue(const std::string& TextBuffer) +{ + float val = 0; + std::string str = ::GetStringValue(TextBuffer); + if (str.length() < 1) + return 0; + std::istringstream ss(str); + ss >> val; + return val; +} + +std::string ModParser::GetText() +{ + std::string str = ""; + std::string line = ""; + size_t savedPos = modFile.tellg(); + line = GetLine(); /// read key/section line + if (isKey) + { + str += line.substr(line.find("=") + 1); + while ( modFile.good() ) + { + savedPos = modFile.tellg(); + line = GetLine(); + if (FindKey(line) != -1 || FindSection(line) != -1) + { + modFile.clear(); /// to parse last line correctly + break; + } + str += std::string("\n") + line; + } + } + else if (isSection) + { + while ( modFile.good() ) + { + savedPos = modFile.tellg(); + line = GetLine(); + if (FindKey(line) != -1 || FindSection(line) != -1) + { + modFile.clear(); /// to parse last line correctly + break; + } + if (str != "") str += "\n"; + str += line; + } + } + modFile.seekg(savedPos, std::ios::beg); + return str; +} + +std::string ModParser::GetTextValue() +{ + return Value; +} + +std::vector ModParser::GetDataChunk() +{ + return ::GetDataChunk(Value); +} + +std::string ModParser::GetStringValue() +{ + return ::GetStringValue(Value); +} + +int ModParser::GetIntValue() +{ + return ::GetIntValue(Value); +} + +float ModParser::GetFloatValue() +{ + return ::GetFloatValue(Value); +} + +std::string ModParser::GetLine() +{ + std::string line = ""; + int ch = 0; + while(ch != 0x0D && ch != 0x0A && modFile.good()) + { + ch = modFile.get(); + if (modFile.fail() || modFile.bad()) + break; + if (CStyleComments && ch == '/' && modFile.peek() == '/') + { + while (ch != 0x0D && ch != 0x0A && modFile.good()) + ch = modFile.get(); + } + else if (CStyleComments && ch == '/' && modFile.peek() == '*') + { + while (modFile.good()) + { + ch = modFile.get(); + if (ch == '*' && modFile.peek() == '/') + { + ch = modFile.get(); + break; + } + } + } + else if (ch == commentLine) + { + while (ch != 0x0D && ch != 0x0A && modFile.good()) + ch = modFile.get(); + } + else if (ch == commentBegin) + { + while (ch != commentEnd && modFile.good()) + ch = modFile.get(); + } + else if (ch != 0x0D && ch != 0x0A && modFile.good()) + { + line += ch; + } + } + if (modFile.peek() == 0x0A || modFile.peek() == 0x0D) + modFile.get(); + return line; +} + +int ModParser::FindNext() +{ + if (!modFile.good()) + return -1; + Name = ""; + Value = ""; + Index = -1; + int idx = -1, keyIdx = -1, sectionIdx = -1; + std::string line = ""; + size_t savedPos = modFile.tellg(); + while ( keyIdx == -1 && sectionIdx == -1 && modFile.good() ) + { + savedPos = modFile.tellg(); + line = GetLine(); + keyIdx = FindKey(line); + sectionIdx = FindSection(line); + } + modFile.clear(); /// to get last line value correctly + modFile.seekg(savedPos, std::ios::beg); + isKey = (keyIdx != -1); + isSection = (sectionIdx != -1); + if (keyIdx != -1) + { + idx = keyIdx; + Name = keyNames[idx]; + } + if (sectionIdx != -1) + { + idx = sectionIdx; + Name = sectionNames[idx]; + } + if (idx != -1) + { + Value = GetText(); + } + Index = idx; + return idx; +} + +int ModParser::FindKey(std::string str) +{ + size_t pos = str.find("="); + if (pos == std::string::npos) + return -1; + std::string name = Trim(str.substr(0, pos)); + /*std::string name = str.substr(0, pos); + name = name.substr(name.find_first_not_of(" ")); + name = name.substr(0, name.find_last_not_of(" ") + 1);*/ + int idx = FindKeyNameIdx(name); + return idx; +} + +int ModParser::FindSection(std::string str) +{ + if (str.find("[") == std::string::npos || str.find("]") == std::string::npos) + return -1; + std::string name = Trim(str); + /*std::string name = str; + name = name.substr(name.find_first_not_of(" ")); + name = name.substr(0, name.find_last_not_of(" ") + 1);*/ + int idx = FindSectionNameIdx(name); + return idx; +} + +int ModParser::FindKeyNameIdx(std::string name) +{ + int idx = -1; + for (unsigned int i = 0; i < keyNames.size(); ++i) + if (keyNames[i] == name) + idx = i; + return idx; +} + +int ModParser::FindSectionNameIdx(std::string name) +{ + int idx = -1; + for (unsigned int i = 0; i < sectionNames.size(); ++i) + if (sectionNames[i] == name) + idx = i; + return idx; +} + +bool ModParser::OpenModFile(const char* name) +{ + if (modFile.is_open()) + { + modFile.close(); + modFile.clear(); + } + modFile.open(name, std::ios::binary); + if (!modFile.good()) + return false; + /// check if file is text + while (modFile.good()) + { + char ch = modFile.get(); + if (modFile.eof()) + break; + if (ch < 1 || ch > 127) + { + modFile.close(); + return false; + } + } + modFile.clear(); + modFile.seekg(0); + isKey = false; + isSection = false; + Name = ""; + Value = ""; + Index = -1; + return true; +} + +void ModParser::AddKeyName(std::string name) +{ + keyNames.push_back(name); +} + +void ModParser::AddSectionName(std::string name) +{ + sectionNames.push_back(name); +} + +void ModParser::SetKeyNames(std::vector names) +{ + keyNames = names; +} + +void ModParser::SetSectionNames(std::vector names) +{ + sectionNames = names; +} + +void ModParser::SetCommentMarkers(char begMarker, char endMarker, char lineMarker) +{ + commentBegin = begMarker; + commentEnd = endMarker; + commentLine = lineMarker; +} diff --git a/ModParser.h b/ModParser.h index 549b01d..6d07c03 100644 --- a/ModParser.h +++ b/ModParser.h @@ -1,69 +1,69 @@ -#ifndef MODPARSER_H -#define MODPARSER_H - -#include -#include -#include - -std::string Trim(std::string str); -size_t SplitAt(char ch, std::string in, std::string& out1, std::string& out2); -std::string GetFilename(std::string str); -std::string MakeTextBlock(char *data, size_t dataSize); -std::string GetStringValue(const std::string& TextBuffer); -std::vector GetDataChunk(const std::string& TextBuffer); -int GetIntValue(const std::string& TextBuffer); -unsigned GetUnsignedValue(const std::string& TextBuffer); -float GetFloatValue(const std::string& TextBuffer); - -class ModParser -{ -public: - ModParser(): commentBegin(0), commentEnd(0), commentLine(0), isKey(false), isSection(false), Name(""), Value(""), Index(-1), CStyleComments(true) {} - ~ModParser() {} - /// keys, sections and comments - void AddKeyName(std::string name); - void AddSectionName(std::string name); - void SetKeyNames(std::vector names); - void SetSectionNames(std::vector names); - void ClearKeyNames() { keyNames.clear(); } - void ClearSectionNames() { sectionNames.clear(); } - void SetCommentMarkers(char begMarker, char endMarker, char lineMarker); - /// init - bool OpenModFile(const char* name); - void UseCStyleComments(bool val) { CStyleComments = val; } - /// find next key or section, set read pointer at the beginning of the line - int FindNext(); - /// Getters - std::string GetTextValue(); - std::vector GetDataChunk(); - std::string GetStringValue(); - int GetIntValue(); - float GetFloatValue(); - /// Get key/section Name and text Value - std::string GetName() { return Name; } - std::string GetValue() { return Value; } -protected: - std::string GetLine(); - std::string GetText(); - int FindKey(std::string str); - int FindSection(std::string str); - int FindKeyNameIdx(std::string name); - int FindSectionNameIdx(std::string name); - bool IsKey() { return isKey; } - bool IsSection() { return isSection; } -private: - std::ifstream modFile; - std::vector keyNames; - std::vector sectionNames; - char commentBegin; - char commentEnd; - char commentLine; - bool isKey; - bool isSection; - std::string Name; - std::string Value; - int Index; - bool CStyleComments; -}; - -#endif // MODPARSER_H +#ifndef MODPARSER_H +#define MODPARSER_H + +#include +#include +#include + +std::string Trim(std::string str); +size_t SplitAt(char ch, std::string in, std::string& out1, std::string& out2); +std::string GetFilename(std::string str); +std::string MakeTextBlock(char *data, size_t dataSize); +std::string GetStringValue(const std::string& TextBuffer); +std::vector GetDataChunk(const std::string& TextBuffer); +int GetIntValue(const std::string& TextBuffer); +unsigned GetUnsignedValue(const std::string& TextBuffer); +float GetFloatValue(const std::string& TextBuffer); + +class ModParser +{ +public: + ModParser(): commentBegin(0), commentEnd(0), commentLine(0), isKey(false), isSection(false), Name(""), Value(""), Index(-1), CStyleComments(true) {} + ~ModParser() {} + /// keys, sections and comments + void AddKeyName(std::string name); + void AddSectionName(std::string name); + void SetKeyNames(std::vector names); + void SetSectionNames(std::vector names); + void ClearKeyNames() { keyNames.clear(); } + void ClearSectionNames() { sectionNames.clear(); } + void SetCommentMarkers(char begMarker, char endMarker, char lineMarker); + /// init + bool OpenModFile(const char* name); + void UseCStyleComments(bool val) { CStyleComments = val; } + /// find next key or section, set read pointer at the beginning of the line + int FindNext(); + /// Getters + std::string GetTextValue(); + std::vector GetDataChunk(); + std::string GetStringValue(); + int GetIntValue(); + float GetFloatValue(); + /// Get key/section Name and text Value + std::string GetName() { return Name; } + std::string GetValue() { return Value; } +protected: + std::string GetLine(); + std::string GetText(); + int FindKey(std::string str); + int FindSection(std::string str); + int FindKeyNameIdx(std::string name); + int FindSectionNameIdx(std::string name); + bool IsKey() { return isKey; } + bool IsSection() { return isSection; } +private: + std::ifstream modFile; + std::vector keyNames; + std::vector sectionNames; + char commentBegin; + char commentEnd; + char commentLine; + bool isKey; + bool isSection; + std::string Name; + std::string Value; + int Index; + bool CStyleComments; +}; + +#endif // MODPARSER_H diff --git a/ModScript.cpp b/ModScript.cpp index 863044b..ef3ee37 100644 --- a/ModScript.cpp +++ b/ModScript.cpp @@ -1,1796 +1,1796 @@ -#include "ModScript.h" - -#include +#include "ModScript.h" + +#include #include -#include -#include - -void ModScript::SetExecutors() -{ - Executors.clear(); - Parser.ClearKeyNames(); - Parser.ClearSectionNames(); - /// Optional keys - Executors.insert({"MOD_NAME", &ModScript::FormatModName}); - Parser.AddKeyName("MOD_NAME"); - Executors.insert({"AUTHOR", &ModScript::FormatAuthor}); - Parser.AddKeyName("AUTHOR"); - Executors.insert({"DESCRIPTION", &ModScript::FormatDescription}); - Parser.AddKeyName("DESCRIPTION"); - /// Patcher keys - Executors.insert({"UPDATE_REL", &ModScript::SetUpdateRelOffset}); - Parser.AddKeyName("UPDATE_REL"); - Executors.insert({"UNINSTALL", &ModScript::SetUninstallAllowed}); - Parser.AddKeyName("UNINSTALL"); - /// Package keys - Executors.insert({"UPK_FILE", &ModScript::OpenPackage}); - Parser.AddKeyName("UPK_FILE"); - Executors.insert({"GUID", &ModScript::SetGUID}); - Parser.AddKeyName("GUID"); - /// Scope keys - Executors.insert({"OFFSET", &ModScript::SetGlobalOffset}); - Parser.AddKeyName("OFFSET"); - Executors.insert({"OBJECT", &ModScript::SetObject}); - Parser.AddKeyName("OBJECT"); - Executors.insert({"NAME_ENTRY", &ModScript::SetNameEntry}); - Parser.AddKeyName("NAME_ENTRY"); - Executors.insert({"IMPORT_ENTRY", &ModScript::SetImportEntry}); - Parser.AddKeyName("IMPORT_ENTRY"); - Executors.insert({"EXPORT_ENTRY", &ModScript::SetExportEntry}); - Parser.AddKeyName("EXPORT_ENTRY"); - /// Scripting - Executors.insert({"ALIAS", &ModScript::AddAlias}); - Parser.AddKeyName("ALIAS"); - /// Relative offset - Executors.insert({"REL_OFFSET", &ModScript::SetRelOffset}); - Parser.AddKeyName("REL_OFFSET"); - Executors.insert({"FIND_HEX", &ModScript::SetDataChunkOffset}); /// is scope-aware - Parser.AddKeyName("FIND_HEX"); - Executors.insert({"FIND_CODE", &ModScript::SetCodeOffset}); /// is scope-aware - Parser.AddKeyName("FIND_CODE"); - /// resizing - Executors.insert({"RESIZE", &ModScript::ResizeExportObject}); - Parser.AddKeyName("RESIZE"); - /// Actual writing - Executors.insert({"REPLACE_HEX", &ModScript::WriteReplaceAllHEX}); - Parser.AddKeyName("REPLACE_HEX"); - Executors.insert({"REPLACE_CODE", &ModScript::WriteReplaceAllCode}); - Parser.AddKeyName("REPLACE_CODE"); - Executors.insert({"BULK_DATA", &ModScript::WriteBulkData}); - Parser.AddKeyName("BULK_DATA"); - Executors.insert({"BULK_FILE", &ModScript::WriteBulkFile}); - Parser.AddKeyName("BULK_FILE"); - Executors.insert({"MODDED_HEX", &ModScript::WriteModdedHEX}); /// key - new style - Parser.AddKeyName("MODDED_HEX"); - Executors.insert({"MODDED_CODE", &ModScript::WriteModdedCode}); - Parser.AddKeyName("MODDED_CODE"); - Executors.insert({"REPLACEMENT_CODE", &ModScript::WriteReplacementCode}); - Parser.AddKeyName("REPLACEMENT_CODE"); - Executors.insert({"INSERT_CODE", &ModScript::WriteInsertCode}); - Parser.AddKeyName("INSERT_CODE"); - Executors.insert({"MODDED_FILE", &ModScript::WriteModdedFile}); - Parser.AddKeyName("MODDED_FILE"); - Executors.insert({"RENAME", &ModScript::WriteRename}); - Parser.AddKeyName("RENAME"); - Executors.insert({"BYTE", &ModScript::WriteByteValue}); - Parser.AddKeyName("BYTE"); - Executors.insert({"FLOAT", &ModScript::WriteFloatValue}); - Parser.AddKeyName("FLOAT"); - Executors.insert({"INTEGER", &ModScript::WriteIntValue}); - Parser.AddKeyName("INTEGER"); - Executors.insert({"UNSIGNED", &ModScript::WriteUnsignedValue}); - Parser.AddKeyName("UNSIGNED"); - Executors.insert({"NAMEIDX", &ModScript::WriteNameIdx}); - Parser.AddKeyName("NAMEIDX"); - Executors.insert({"OBJIDX", &ModScript::WriteObjectIdx}); - Parser.AddKeyName("OBJIDX"); - /// uninstaller thing - Executors.insert({"EXPAND_UNDO", &ModScript::WriteUndoMoveResize}); - Parser.AddKeyName("EXPAND_UNDO"); - /// add new table entry - Executors.insert({"ADD_NAME_ENTRY", &ModScript::WriteAddNameEntry}); - Parser.AddKeyName("ADD_NAME_ENTRY"); - Executors.insert({"ADD_IMPORT_ENTRY", &ModScript::WriteAddImportEntry}); - Parser.AddKeyName("ADD_IMPORT_ENTRY"); - Executors.insert({"ADD_EXPORT_ENTRY", &ModScript::WriteAddExportEntry}); - Parser.AddKeyName("ADD_EXPORT_ENTRY"); - /// section-style patching - Executors.insert({"[FIND_HEX]", &ModScript::SetDataChunkOffset}); /// is scope-aware - Parser.AddSectionName("[FIND_HEX]"); - Executors.insert({"[/FIND_HEX]", &ModScript::Sink}); - Parser.AddSectionName("[/FIND_HEX]"); - Executors.insert({"[FIND_CODE]", &ModScript::SetCodeOffset}); /// is scope-aware - Parser.AddSectionName("[FIND_CODE]"); - Executors.insert({"[/FIND_CODE]", &ModScript::Sink}); - Parser.AddSectionName("[/FIND_CODE]"); - Executors.insert({"[MODDED_HEX]", &ModScript::WriteModdedHEX}); /// section - old style - Parser.AddSectionName("[MODDED_HEX]"); - Executors.insert({"[/MODDED_HEX]", &ModScript::Sink}); - Parser.AddSectionName("[/MODDED_HEX]"); - Executors.insert({"[MODDED_CODE]", &ModScript::WriteModdedCode}); - Parser.AddSectionName("[MODDED_CODE]"); - Executors.insert({"[/MODDED_CODE]", &ModScript::Sink}); - Parser.AddSectionName("[/MODDED_CODE]"); - Executors.insert({"[REPLACEMENT_CODE]", &ModScript::WriteReplacementCode}); - Parser.AddSectionName("[REPLACEMENT_CODE]"); - Executors.insert({"[/REPLACEMENT_CODE]", &ModScript::Sink}); - Parser.AddSectionName("[/REPLACEMENT_CODE]"); - Executors.insert({"[INSERT_CODE]", &ModScript::WriteInsertCode}); - Parser.AddSectionName("[INSERT_CODE]"); - Executors.insert({"[/INSERT_CODE]", &ModScript::Sink}); - Parser.AddSectionName("[/INSERT_CODE]"); - Executors.insert({"[ADD_NAME_ENTRY]", &ModScript::WriteAddNameEntry}); - Parser.AddSectionName("[ADD_NAME_ENTRY]"); - Executors.insert({"[/ADD_NAME_ENTRY]", &ModScript::Sink}); - Parser.AddSectionName("[/ADD_NAME_ENTRY]"); - Executors.insert({"[ADD_IMPORT_ENTRY]", &ModScript::WriteAddImportEntry}); - Parser.AddSectionName("[ADD_IMPORT_ENTRY]"); - Executors.insert({"[/ADD_IMPORT_ENTRY]", &ModScript::Sink}); - Parser.AddSectionName("[/ADD_IMPORT_ENTRY]"); - Executors.insert({"[ADD_EXPORT_ENTRY]", &ModScript::WriteAddExportEntry}); - Parser.AddSectionName("[ADD_EXPORT_ENTRY]"); - Executors.insert({"[/ADD_EXPORT_ENTRY]", &ModScript::Sink}); - Parser.AddSectionName("[/ADD_EXPORT_ENTRY]"); - /// before-after style patching - Executors.insert({"[BEFORE_HEX]", &ModScript::SetBeforeHEXOffset}); /// is scope-aware - Parser.AddSectionName("[BEFORE_HEX]"); - Executors.insert({"[/BEFORE_HEX]", &ModScript::Sink}); - Parser.AddSectionName("[/BEFORE_HEX]"); - Executors.insert({"[BEFORE_CODE]", &ModScript::SetBeforeCodeOffset}); /// is scope-aware - Parser.AddSectionName("[BEFORE_CODE]"); - Executors.insert({"[/BEFORE_CODE]", &ModScript::Sink}); - Parser.AddSectionName("[/BEFORE_CODE]"); - Executors.insert({"[AFTER_HEX]", &ModScript::WriteAfterHEX}); - Parser.AddSectionName("[AFTER_HEX]"); - Executors.insert({"[/AFTER_HEX]", &ModScript::Sink}); - Parser.AddSectionName("[/AFTER_HEX]"); - Executors.insert({"[AFTER_CODE]", &ModScript::WriteAfterCode}); - Parser.AddSectionName("[AFTER_CODE]"); - Executors.insert({"[/AFTER_CODE]", &ModScript::Sink}); - Parser.AddSectionName("[/AFTER_CODE]"); - /// deprecated keys - Executors.insert({"FUNCTION", &ModScript::SetObject}); /// legacy support - OBJECT alias - Parser.AddKeyName("FUNCTION"); /// legacy support - OBJECT alias - Executors.insert({"FUNCTION_FILE", &ModScript::WriteModdedFile}); /// legacy support - MODDED_FILE alias - Parser.AddKeyName("FUNCTION_FILE"); /// legacy support - MODDED_FILE alias - Executors.insert({"NAMELIST_NAME", &ModScript::WriteRename}); /// legacy support - RENAME alias - Parser.AddKeyName("NAMELIST_NAME"); /// legacy support - RENAME alias - /// relatively safe - Executors.insert({"EXPAND_FUNCTION", &ModScript::WriteMoveExpandLegacy}); - Parser.AddKeyName("EXPAND_FUNCTION"); -} - -/******************************************************************** -************************ public functions *************************** -*********************************************************************/ - -std::string FormatUPKScope(UPKScope scope) -{ - switch (scope) - { - case UPKScope::Package: - return "Package"; - case UPKScope::Name: - return "Name Table"; - case UPKScope::Import: - return "Import Table"; - case UPKScope::Export: - return "Export Table"; - case UPKScope::Object: - return "Object Data"; - default: - return ""; - } -} - -void ModScript::SetUPKPath(const char* pathname) -{ - UPKPath = pathname; - if (UPKPath.length() < 1) - UPKPath = "."; -} - -void ModScript::InitStreams(std::ostream& err, std::ostream& res) -{ - ErrorMessages = &err; - ExecutionResults = &res; -} - -bool ModScript::Parse(const char* filename) -{ - BackupScript.clear(); - UPKNames.clear(); - GUIDs.clear(); - if (Parser.OpenModFile(filename) == false) - { - *ErrorMessages << "Can't open " << filename << " (file does not exist, or bad, or not ASCII)!" << std::endl; - return SetBad(); - } - SetExecutors(); - Parser.SetCommentMarkers('{', '}', 0); - /// begin parsing mod file - ExecutionStack.clear(); - ResetScriptFlags(); - ResetScope(); - int idx = Parser.FindNext(); - if (idx == -1) - { - *ErrorMessages << "Bad/unknown mod file format!\n"; - return SetBad(); - } - while (idx != -1) - { - ExecutionStack.push_back({Parser.GetName(), Parser.GetValue(), Executors[Parser.GetName()]}); - idx = Parser.FindNext(); - } - return SetGood(); -} - -bool ModScript::ExecuteStack() -{ - if (IsGood() == false) - { - *ErrorMessages << "Script state is bad, can not execute!\n"; - return SetBad(); - } - if (ExecutionStack.size() < 1) - { - *ErrorMessages << "Execution stack is empty!\n"; - return SetBad(); - } - for (unsigned i = 0; i < ExecutionStack.size(); ++i) - { - bool result = (this->*ExecutionStack[i].Exec)(ExecutionStack[i].Param); - if (result == false) - { - *ErrorMessages << "Execution stopped at #" << i << " command named " - << ExecutionStack[i].Name << ".\n"; - return SetBad(); - } - } - return SetGood(); -} - -/******************************************************************** -************************** patcher keys ***************************** -*********************************************************************/ - -bool ModScript::SetUpdateRelOffset(const std::string& Param) -{ - std::string val = GetStringValue(Param); - if (val == "TRUE") - { - ScriptFlags.UpdateRelOffset = true; - } - else if (val == "FALSE") - { - ScriptFlags.UpdateRelOffset = false; - } - else - { - *ErrorMessages << "Bad key value: " << Param << std::endl; - return SetBad(); - } - *ExecutionResults << "Allow relative offsets updating after each write operation: " - << Param << std::endl; - return SetGood(); -} - -bool ModScript::SetUninstallAllowed(const std::string& Param) -{ - std::string val = GetStringValue(Param); - if (val == "TRUE") - { - ScriptFlags.IsUninstallAllowed = true; - } - else if (val == "FALSE") - { - ScriptFlags.IsUninstallAllowed = false; - } - else - { - *ErrorMessages << "Bad key value: " << Param << std::endl; - return SetBad(); - } - *ExecutionResults << "Allow uninstall data to be saved: " - << Param << std::endl; - return SetGood(); -} - -/******************************************************************** -********************** mod description keys ************************* -*********************************************************************/ - -bool ModScript::FormatModName(const std::string& Param) -{ - *ExecutionResults << "Installing mod: " - << Param << std::endl; - return SetGood(); -} - -bool ModScript::FormatAuthor(const std::string& Param) -{ - *ExecutionResults << "by " - << Param << std::endl; - return SetGood(); -} - -bool ModScript::FormatDescription(const std::string& Param) -{ - *ExecutionResults << "Mod description:\n" - << Param << std::endl; - return SetGood(); -} - -/******************************************************************** -******************* functions to load a package ********************* -*********************************************************************/ - -void ModScript::AddUPKName(std::string upkname) -{ - for (unsigned i = 0; i < UPKNames.size(); ++i) - { - if (UPKNames[i] == upkname) - return; - } - UPKNames.push_back(upkname); -} - -bool ModScript::OpenPackage(const std::string& Param) -{ +#include +#include + +void ModScript::SetExecutors() +{ + Executors.clear(); + Parser.ClearKeyNames(); + Parser.ClearSectionNames(); + /// Optional keys + Executors.insert({"MOD_NAME", &ModScript::FormatModName}); + Parser.AddKeyName("MOD_NAME"); + Executors.insert({"AUTHOR", &ModScript::FormatAuthor}); + Parser.AddKeyName("AUTHOR"); + Executors.insert({"DESCRIPTION", &ModScript::FormatDescription}); + Parser.AddKeyName("DESCRIPTION"); + /// Patcher keys + Executors.insert({"UPDATE_REL", &ModScript::SetUpdateRelOffset}); + Parser.AddKeyName("UPDATE_REL"); + Executors.insert({"UNINSTALL", &ModScript::SetUninstallAllowed}); + Parser.AddKeyName("UNINSTALL"); + /// Package keys + Executors.insert({"UPK_FILE", &ModScript::OpenPackage}); + Parser.AddKeyName("UPK_FILE"); + Executors.insert({"GUID", &ModScript::SetGUID}); + Parser.AddKeyName("GUID"); + /// Scope keys + Executors.insert({"OFFSET", &ModScript::SetGlobalOffset}); + Parser.AddKeyName("OFFSET"); + Executors.insert({"OBJECT", &ModScript::SetObject}); + Parser.AddKeyName("OBJECT"); + Executors.insert({"NAME_ENTRY", &ModScript::SetNameEntry}); + Parser.AddKeyName("NAME_ENTRY"); + Executors.insert({"IMPORT_ENTRY", &ModScript::SetImportEntry}); + Parser.AddKeyName("IMPORT_ENTRY"); + Executors.insert({"EXPORT_ENTRY", &ModScript::SetExportEntry}); + Parser.AddKeyName("EXPORT_ENTRY"); + /// Scripting + Executors.insert({"ALIAS", &ModScript::AddAlias}); + Parser.AddKeyName("ALIAS"); + /// Relative offset + Executors.insert({"REL_OFFSET", &ModScript::SetRelOffset}); + Parser.AddKeyName("REL_OFFSET"); + Executors.insert({"FIND_HEX", &ModScript::SetDataChunkOffset}); /// is scope-aware + Parser.AddKeyName("FIND_HEX"); + Executors.insert({"FIND_CODE", &ModScript::SetCodeOffset}); /// is scope-aware + Parser.AddKeyName("FIND_CODE"); + /// resizing + Executors.insert({"RESIZE", &ModScript::ResizeExportObject}); + Parser.AddKeyName("RESIZE"); + /// Actual writing + Executors.insert({"REPLACE_HEX", &ModScript::WriteReplaceAllHEX}); + Parser.AddKeyName("REPLACE_HEX"); + Executors.insert({"REPLACE_CODE", &ModScript::WriteReplaceAllCode}); + Parser.AddKeyName("REPLACE_CODE"); + Executors.insert({"BULK_DATA", &ModScript::WriteBulkData}); + Parser.AddKeyName("BULK_DATA"); + Executors.insert({"BULK_FILE", &ModScript::WriteBulkFile}); + Parser.AddKeyName("BULK_FILE"); + Executors.insert({"MODDED_HEX", &ModScript::WriteModdedHEX}); /// key - new style + Parser.AddKeyName("MODDED_HEX"); + Executors.insert({"MODDED_CODE", &ModScript::WriteModdedCode}); + Parser.AddKeyName("MODDED_CODE"); + Executors.insert({"REPLACEMENT_CODE", &ModScript::WriteReplacementCode}); + Parser.AddKeyName("REPLACEMENT_CODE"); + Executors.insert({"INSERT_CODE", &ModScript::WriteInsertCode}); + Parser.AddKeyName("INSERT_CODE"); + Executors.insert({"MODDED_FILE", &ModScript::WriteModdedFile}); + Parser.AddKeyName("MODDED_FILE"); + Executors.insert({"RENAME", &ModScript::WriteRename}); + Parser.AddKeyName("RENAME"); + Executors.insert({"BYTE", &ModScript::WriteByteValue}); + Parser.AddKeyName("BYTE"); + Executors.insert({"FLOAT", &ModScript::WriteFloatValue}); + Parser.AddKeyName("FLOAT"); + Executors.insert({"INTEGER", &ModScript::WriteIntValue}); + Parser.AddKeyName("INTEGER"); + Executors.insert({"UNSIGNED", &ModScript::WriteUnsignedValue}); + Parser.AddKeyName("UNSIGNED"); + Executors.insert({"NAMEIDX", &ModScript::WriteNameIdx}); + Parser.AddKeyName("NAMEIDX"); + Executors.insert({"OBJIDX", &ModScript::WriteObjectIdx}); + Parser.AddKeyName("OBJIDX"); + /// uninstaller thing + Executors.insert({"EXPAND_UNDO", &ModScript::WriteUndoMoveResize}); + Parser.AddKeyName("EXPAND_UNDO"); + /// add new table entry + Executors.insert({"ADD_NAME_ENTRY", &ModScript::WriteAddNameEntry}); + Parser.AddKeyName("ADD_NAME_ENTRY"); + Executors.insert({"ADD_IMPORT_ENTRY", &ModScript::WriteAddImportEntry}); + Parser.AddKeyName("ADD_IMPORT_ENTRY"); + Executors.insert({"ADD_EXPORT_ENTRY", &ModScript::WriteAddExportEntry}); + Parser.AddKeyName("ADD_EXPORT_ENTRY"); + /// section-style patching + Executors.insert({"[FIND_HEX]", &ModScript::SetDataChunkOffset}); /// is scope-aware + Parser.AddSectionName("[FIND_HEX]"); + Executors.insert({"[/FIND_HEX]", &ModScript::Sink}); + Parser.AddSectionName("[/FIND_HEX]"); + Executors.insert({"[FIND_CODE]", &ModScript::SetCodeOffset}); /// is scope-aware + Parser.AddSectionName("[FIND_CODE]"); + Executors.insert({"[/FIND_CODE]", &ModScript::Sink}); + Parser.AddSectionName("[/FIND_CODE]"); + Executors.insert({"[MODDED_HEX]", &ModScript::WriteModdedHEX}); /// section - old style + Parser.AddSectionName("[MODDED_HEX]"); + Executors.insert({"[/MODDED_HEX]", &ModScript::Sink}); + Parser.AddSectionName("[/MODDED_HEX]"); + Executors.insert({"[MODDED_CODE]", &ModScript::WriteModdedCode}); + Parser.AddSectionName("[MODDED_CODE]"); + Executors.insert({"[/MODDED_CODE]", &ModScript::Sink}); + Parser.AddSectionName("[/MODDED_CODE]"); + Executors.insert({"[REPLACEMENT_CODE]", &ModScript::WriteReplacementCode}); + Parser.AddSectionName("[REPLACEMENT_CODE]"); + Executors.insert({"[/REPLACEMENT_CODE]", &ModScript::Sink}); + Parser.AddSectionName("[/REPLACEMENT_CODE]"); + Executors.insert({"[INSERT_CODE]", &ModScript::WriteInsertCode}); + Parser.AddSectionName("[INSERT_CODE]"); + Executors.insert({"[/INSERT_CODE]", &ModScript::Sink}); + Parser.AddSectionName("[/INSERT_CODE]"); + Executors.insert({"[ADD_NAME_ENTRY]", &ModScript::WriteAddNameEntry}); + Parser.AddSectionName("[ADD_NAME_ENTRY]"); + Executors.insert({"[/ADD_NAME_ENTRY]", &ModScript::Sink}); + Parser.AddSectionName("[/ADD_NAME_ENTRY]"); + Executors.insert({"[ADD_IMPORT_ENTRY]", &ModScript::WriteAddImportEntry}); + Parser.AddSectionName("[ADD_IMPORT_ENTRY]"); + Executors.insert({"[/ADD_IMPORT_ENTRY]", &ModScript::Sink}); + Parser.AddSectionName("[/ADD_IMPORT_ENTRY]"); + Executors.insert({"[ADD_EXPORT_ENTRY]", &ModScript::WriteAddExportEntry}); + Parser.AddSectionName("[ADD_EXPORT_ENTRY]"); + Executors.insert({"[/ADD_EXPORT_ENTRY]", &ModScript::Sink}); + Parser.AddSectionName("[/ADD_EXPORT_ENTRY]"); + /// before-after style patching + Executors.insert({"[BEFORE_HEX]", &ModScript::SetBeforeHEXOffset}); /// is scope-aware + Parser.AddSectionName("[BEFORE_HEX]"); + Executors.insert({"[/BEFORE_HEX]", &ModScript::Sink}); + Parser.AddSectionName("[/BEFORE_HEX]"); + Executors.insert({"[BEFORE_CODE]", &ModScript::SetBeforeCodeOffset}); /// is scope-aware + Parser.AddSectionName("[BEFORE_CODE]"); + Executors.insert({"[/BEFORE_CODE]", &ModScript::Sink}); + Parser.AddSectionName("[/BEFORE_CODE]"); + Executors.insert({"[AFTER_HEX]", &ModScript::WriteAfterHEX}); + Parser.AddSectionName("[AFTER_HEX]"); + Executors.insert({"[/AFTER_HEX]", &ModScript::Sink}); + Parser.AddSectionName("[/AFTER_HEX]"); + Executors.insert({"[AFTER_CODE]", &ModScript::WriteAfterCode}); + Parser.AddSectionName("[AFTER_CODE]"); + Executors.insert({"[/AFTER_CODE]", &ModScript::Sink}); + Parser.AddSectionName("[/AFTER_CODE]"); + /// deprecated keys + Executors.insert({"FUNCTION", &ModScript::SetObject}); /// legacy support - OBJECT alias + Parser.AddKeyName("FUNCTION"); /// legacy support - OBJECT alias + Executors.insert({"FUNCTION_FILE", &ModScript::WriteModdedFile}); /// legacy support - MODDED_FILE alias + Parser.AddKeyName("FUNCTION_FILE"); /// legacy support - MODDED_FILE alias + Executors.insert({"NAMELIST_NAME", &ModScript::WriteRename}); /// legacy support - RENAME alias + Parser.AddKeyName("NAMELIST_NAME"); /// legacy support - RENAME alias + /// relatively safe + Executors.insert({"EXPAND_FUNCTION", &ModScript::WriteMoveExpandLegacy}); + Parser.AddKeyName("EXPAND_FUNCTION"); +} + +/******************************************************************** +************************ public functions *************************** +*********************************************************************/ + +std::string FormatUPKScope(UPKScope scope) +{ + switch (scope) + { + case UPKScope::Package: + return "Package"; + case UPKScope::Name: + return "Name Table"; + case UPKScope::Import: + return "Import Table"; + case UPKScope::Export: + return "Export Table"; + case UPKScope::Object: + return "Object Data"; + default: + return ""; + } +} + +void ModScript::SetUPKPath(const char* pathname) +{ + UPKPath = pathname; + if (UPKPath.length() < 1) + UPKPath = "."; +} + +void ModScript::InitStreams(std::ostream& err, std::ostream& res) +{ + ErrorMessages = &err; + ExecutionResults = &res; +} + +bool ModScript::Parse(const char* filename) +{ + BackupScript.clear(); + UPKNames.clear(); + GUIDs.clear(); + if (Parser.OpenModFile(filename) == false) + { + *ErrorMessages << "Can't open " << filename << " (file does not exist, or bad, or not ASCII)!" << std::endl; + return SetBad(); + } + SetExecutors(); + Parser.SetCommentMarkers('{', '}', 0); + /// begin parsing mod file + ExecutionStack.clear(); + ResetScriptFlags(); + ResetScope(); + int idx = Parser.FindNext(); + if (idx == -1) + { + *ErrorMessages << "Bad/unknown mod file format!\n"; + return SetBad(); + } + while (idx != -1) + { + ExecutionStack.push_back({Parser.GetName(), Parser.GetValue(), Executors[Parser.GetName()]}); + idx = Parser.FindNext(); + } + return SetGood(); +} + +bool ModScript::ExecuteStack() +{ + if (IsGood() == false) + { + *ErrorMessages << "Script state is bad, can not execute!\n"; + return SetBad(); + } + if (ExecutionStack.size() < 1) + { + *ErrorMessages << "Execution stack is empty!\n"; + return SetBad(); + } + for (unsigned i = 0; i < ExecutionStack.size(); ++i) + { + bool result = (this->*ExecutionStack[i].Exec)(ExecutionStack[i].Param); + if (result == false) + { + *ErrorMessages << "Execution stopped at #" << i << " command named " + << ExecutionStack[i].Name << ".\n"; + return SetBad(); + } + } + return SetGood(); +} + +/******************************************************************** +************************** patcher keys ***************************** +*********************************************************************/ + +bool ModScript::SetUpdateRelOffset(const std::string& Param) +{ + std::string val = GetStringValue(Param); + if (val == "TRUE") + { + ScriptFlags.UpdateRelOffset = true; + } + else if (val == "FALSE") + { + ScriptFlags.UpdateRelOffset = false; + } + else + { + *ErrorMessages << "Bad key value: " << Param << std::endl; + return SetBad(); + } + *ExecutionResults << "Allow relative offsets updating after each write operation: " + << Param << std::endl; + return SetGood(); +} + +bool ModScript::SetUninstallAllowed(const std::string& Param) +{ + std::string val = GetStringValue(Param); + if (val == "TRUE") + { + ScriptFlags.IsUninstallAllowed = true; + } + else if (val == "FALSE") + { + ScriptFlags.IsUninstallAllowed = false; + } + else + { + *ErrorMessages << "Bad key value: " << Param << std::endl; + return SetBad(); + } + *ExecutionResults << "Allow uninstall data to be saved: " + << Param << std::endl; + return SetGood(); +} + +/******************************************************************** +********************** mod description keys ************************* +*********************************************************************/ + +bool ModScript::FormatModName(const std::string& Param) +{ + *ExecutionResults << "Installing mod: " + << Param << std::endl; + return SetGood(); +} + +bool ModScript::FormatAuthor(const std::string& Param) +{ + *ExecutionResults << "by " + << Param << std::endl; + return SetGood(); +} + +bool ModScript::FormatDescription(const std::string& Param) +{ + *ExecutionResults << "Mod description:\n" + << Param << std::endl; + return SetGood(); +} + +/******************************************************************** +******************* functions to load a package ********************* +*********************************************************************/ + +void ModScript::AddUPKName(std::string upkname) +{ + for (unsigned i = 0; i < UPKNames.size(); ++i) + { + if (UPKNames[i] == upkname) + return; + } + UPKNames.push_back(upkname); +} + +bool ModScript::OpenPackage(const std::string& Param) +{ std::string UPKFileName = GetStringValue(Param); - std::transform(UPKFileName.begin(), UPKFileName.end(), UPKFileName.begin(), ::tolower); - *ExecutionResults << "Opening package ...\n"; - if (ScriptState.UPKName == UPKFileName) - { - *ExecutionResults << UPKFileName << " is already opened!\n"; - return SetGood(); - } - ScriptState.UPKName = UPKFileName; - std::string pathName = UPKPath + "/" + UPKFileName; - if (ScriptState.Package.Read(pathName.c_str()) == false) - { - *ErrorMessages << "Error reading package: " << pathName << std::endl; - UPKReadErrors err = ScriptState.Package.GetError(); - *ErrorMessages << FormatReadErrors(err); - if (ScriptState.Package.IsCompressed()) - *ErrorMessages << "Compression flags:\n" << FormatCompressionFlags(ScriptState.Package.GetCompressionFlags()); - return SetBad(); - } - AddUPKName(ScriptState.UPKName); - ResetScope(); - *ExecutionResults << "Package file: " << pathName; - *ExecutionResults << std::endl; - if (GUIDs.count(UPKFileName) > 0) - { - bool found = false; - std::string GUID = FormatHEX(ScriptState.Package.GetGUID()); - std::multimap::iterator it; - for (it = GUIDs.equal_range(UPKFileName).first; it != GUIDs.equal_range(UPKFileName).second; ++it) - { - if ( (*it).second == GUID) - { - found = true; - break; - } - } - if (found == false) - { - *ErrorMessages << "Package GUID " << GUID << " does not match any of GUIDs specified for package " << UPKFileName << std::endl; - return SetBad(); - } - } - if (BackupScript.count(ScriptState.UPKName) < 1) - { - BackupScript.insert({ScriptState.UPKName, std::string("")}); - } - *ExecutionResults << "Package opened successfully!\n"; - return SetGood(); -} - -bool ModScript::SetGUID(const std::string& Param) -{ - std::string PackageName, GUID; - std::string str = GetStringValue(Param); - size_t pos = SplitAt(':', str, GUID, PackageName); - if (pos == std::string::npos) - { - *ErrorMessages << "Bad GUID key format!\n"; - return SetBad(); - } - std::transform(PackageName.begin(), PackageName.end(), PackageName.begin(), ::tolower); - GUIDs.insert({PackageName, GUID}); - *ExecutionResults << "Added allowed GUID:\n"; - *ExecutionResults << "Package: " << PackageName << " GUID: " << GUID << std::endl; - return SetGood(); -} - -/******************************************************************** -**************** functions to set patching scope ******************** -*********************************************************************/ - -bool ModScript::SetGlobalOffset(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - ResetScope(); - ScriptState.Scope = UPKScope::Package; - ScriptState.Offset = GetUnsignedValue(Param); - ScriptState.MaxOffset = ScriptState.Package.GetFileSize() - 1; - *ExecutionResults << "Global offset: " << FormatHEX((uint32_t)ScriptState.Offset) - << " (" << ScriptState.Offset << ")" << std::endl; - if (ScriptState.Package.CheckValidFileOffset(ScriptState.Offset) == false) - { - *ErrorMessages << "Invalid package offset!\n"; - return SetBad(); - } - return SetGood(); -} - -bool ModScript::CheckBehavior() -{ - if (ScriptState.Behavior == "") - { - ScriptState.Behavior = "KEEP"; - return SetGood(); - } - else if (ScriptState.Behavior == "KEEP") - { - return SetGood(); - } - else if (ScriptState.Behavior == "MOVE") - { - return SetGood(); - } - else if (ScriptState.Behavior == "AUTO") - { - return SetGood(); - } - else if (ScriptState.Behavior == "INPL") - { - return SetGood(); - } - return SetBad(); -} - -bool ModScript::SetObject(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - ResetScope(); - ScriptState.Scope = UPKScope::Object; - std::string str = GetStringValue(Param), ObjName; - SplitAt(':', str, ObjName, ScriptState.Behavior); - if (!CheckBehavior()) - { - *ErrorMessages << "Bad behavior modifier: " << ScriptState.Behavior << std::endl; - return SetBad(); - } - *ExecutionResults << "Searching for object named " << ObjName << " ...\n"; - UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName); - if (ObjRef == 0) - { - *ErrorMessages << "Can't find object named " << ObjName << std::endl; - return SetBad(); - } - else if (ObjRef < 0) - { - *ErrorMessages << "Import objects have no serial data: " << ObjName << std::endl; - return SetBad(); - } - *ExecutionResults << "Object found!\n"; - ScriptState.ObjIdx = (uint32_t)ObjRef; - ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; - ScriptState.RelOffset = 0; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; - /* - *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) - << "\nObject: " << ObjName - << " (" << ScriptState.Behavior << ")" - << "\nIndex: " << ScriptState.ObjIdx - << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) - << " (" << ScriptState.Offset << ")" << std::endl; - */ - return SetGood(); -} - -bool ModScript::SetNameEntry(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - ResetScope(); - ScriptState.Scope = UPKScope::Name; - std::string ObjName = GetStringValue(Param); - *ExecutionResults << "Searching for name " << ObjName << " ...\n"; - int idx = ScriptState.Package.FindName(ObjName); - if (idx < 0) - { - *ErrorMessages << "Can't find NameTable name " << ObjName << std::endl; - return SetBad(); - } - *ExecutionResults << "Name found!\n"; - ScriptState.ObjIdx = idx; - ScriptState.Offset = ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).EntryOffset; - ScriptState.RelOffset = 0; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).EntrySize - 1; - /* - *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) - << "\nName entry: " << ObjName - << "\nIndex: " << ScriptState.ObjIdx - << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) - << " (" << ScriptState.Offset << ")" << std::endl; - */ - return SetGood(); -} - -bool ModScript::SetImportEntry(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - ResetScope(); - ScriptState.Scope = UPKScope::Import; - std::string ObjName = GetStringValue(Param); - *ExecutionResults << "Searching for import table entry " << ObjName << " ...\n"; - UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName, false); - if (ObjRef == 0) - { - *ErrorMessages << "Can't find ImportTable entry named " << ObjName << std::endl; - return SetBad(); - } - else if (ObjRef > 0) - { - *ErrorMessages << ObjName << " is export object, not import object!" << std::endl; - return SetBad(); - } - *ExecutionResults << "Import table entry found!\n"; - ScriptState.ObjIdx = (uint32_t)(-ObjRef); - ScriptState.Offset = ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).EntryOffset; - ScriptState.RelOffset = 0; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).EntrySize - 1; - /* - *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) - << "\nImport entry: " << ObjName - << "\nIndex: " << ScriptState.ObjIdx - << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) - << " (" << ScriptState.Offset << ")" << std::endl; - */ - return SetGood(); -} - -bool ModScript::SetExportEntry(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - ResetScope(); - ScriptState.Scope = UPKScope::Export; - std::string ObjName = GetStringValue(Param); - *ExecutionResults << "Searching for export table entry " << ObjName << " ...\n"; - UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName); - if (ObjRef == 0) - { - *ErrorMessages << "Can't find ExportTable entry named " << ObjName << std::endl; - return SetBad(); - } - *ExecutionResults << "Export table entry found!\n"; - ScriptState.ObjIdx = (uint32_t)ObjRef; - ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).EntryOffset; - ScriptState.RelOffset = 0; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).EntrySize - 1; - /* - *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) - << "\nExport entry: " << ObjName - << "\nIndex: " << ScriptState.ObjIdx - << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) - << " (" << ScriptState.Offset << ")" << std::endl; - */ - return SetGood(); -} - -bool ModScript::SetRelOffset(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - ScriptState.RelOffset = GetUnsignedValue(Param); - *ExecutionResults << "Relative offset: " << FormatHEX((uint32_t)ScriptState.RelOffset) - << " (" << ScriptState.RelOffset << ")" << std::endl; - if (!IsInsideScope()) - { - *ErrorMessages << "Invalid relative offset!\n"; - return SetBad(); - } - return SetGood(); -} - -/******************************************************************** -**************** helpers to write modded hex/code ******************* -*********************************************************************/ - -void ModScript::ResetMaxOffset() -{ - switch (ScriptState.Scope) - { - case UPKScope::Object: - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; - return; - case UPKScope::Name: - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).EntrySize - 1; - return; - case UPKScope::Import: - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).EntrySize - 1; - return; - case UPKScope::Export: - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).EntrySize - 1; - return; - default: - ScriptState.MaxOffset = ScriptState.Package.GetFileSize() - 1; - return; - } -} - -bool ModScript::IsInsideScope(size_t DataSize) -{ - return (ScriptState.Offset + ScriptState.RelOffset + DataSize - 1) <= ScriptState.MaxOffset; -} - -size_t ModScript::GetDiff(size_t DataSize) -{ - if (!IsInsideScope(DataSize)) - return ((ScriptState.Offset + ScriptState.RelOffset + DataSize - 1) - ScriptState.MaxOffset); - else - return 0; -} - -bool ModScript::CheckMoveResize(size_t DataSize, bool FitScope) -{ - size_t ScopeSize = ScriptState.MaxOffset - ScriptState.Offset - ScriptState.RelOffset + 1; - if (ScriptState.Scope == UPKScope::Object) - { - bool NeedMoveResize = false; - size_t ObjSize = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize; - if (FitScope && ScopeSize != DataSize) - { - if (ScriptState.Behavior != "KEEP") - { - NeedMoveResize = true; - ObjSize += DataSize - ScopeSize; - } - else - { - *ErrorMessages << "Data chunk does not fit current scope!\n"; - return SetBad(); - } - } - else if (!IsInsideScope(DataSize) && ScriptState.Behavior != "KEEP") - { - NeedMoveResize = true; - ObjSize += GetDiff(DataSize); - } - else if (ScriptState.Behavior == "MOVE") - { - NeedMoveResize = true; - } - if (NeedMoveResize) - { - return DoResize(ObjSize); - } - } - else if (FitScope && ScopeSize != DataSize) - { - *ErrorMessages << "Data chunk does not fit current scope!\n"; - return SetBad(); - } - if (!IsInsideScope(DataSize)) - { - *ErrorMessages << "Data chunk too large for current scope!\n"; - return SetBad(); - } - /// resetting max offset and before flag - ResetMaxOffset(); - /// reset before hex/code status - ScriptState.BeforeUsed = false; - ScriptState.BeforeMemSize = 0; - return SetGood(); -} - -bool ModScript::DoResize(int ObjSize) -{ - if (ScriptState.Behavior == "INPL") - { - return ResizeInPlace(ObjSize); - } - else - { - return MoveResizeAtRelOffset(ObjSize); - } -} - -bool ModScript::MoveResizeAtRelOffset(int ObjSize) -{ - *ExecutionResults << "Moving/resizing object.\nNew object size: " << ObjSize << std::endl; - if (ScriptState.Package.MoveResizeObject(ScriptState.ObjIdx, ObjSize, ScriptState.RelOffset) == false) - { - *ErrorMessages << "Error moving/resizing object!\n"; - return SetBad(); - } - *ExecutionResults << "Object moved/resized successfully.\n"; - ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; - /// backup info - if (ScriptFlags.IsUninstallAllowed) - { - std::ostringstream ss; - ss << "EXPAND_UNDO=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; - ss << BackupScript[ScriptState.UPKName]; - BackupScript[ScriptState.UPKName] = ss.str(); - } - return SetGood(); -} - -bool ModScript::ResizeInPlace(int ObjSize) -{ - size_t oldSize = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize; - std::vector oldData = ScriptState.Package.GetExportData(ScriptState.ObjIdx); - *ExecutionResults << "Resizing object in place.\nNew object size: " << ObjSize << std::endl; - if (ScriptState.Package.ResizeInPlace(ScriptState.ObjIdx, ObjSize, ScriptState.RelOffset) == false) - { - *ErrorMessages << "Error resizing object in place!\n"; - return SetBad(); - } - *ExecutionResults << "Object resized in place successfully.\n"; - ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; - /// backup info - if (ScriptFlags.IsUninstallAllowed) - { - std::ostringstream ss; - ss << "OBJECT=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << ":INPL" << "\n\n"; - ss << "RESIZE=" << oldSize << "\n\n"; - ss << "[MODDED_HEX]\n" << MakeTextBlock(oldData.data(), oldData.size()) << "\n\n"; - ss << BackupScript[ScriptState.UPKName]; - BackupScript[ScriptState.UPKName] = ss.str(); - } - return SetGood(); -} - -bool ModScript::WriteBinaryData(const std::vector& DataChunk) -{ - std::vector BackupData; - *ExecutionResults << "Writing data chunk of size " << FormatHEX((uint32_t)DataChunk.size()) - << " (" << DataChunk.size() << ") at" - << "\nScope: " << FormatUPKScope(ScriptState.Scope) - << "\nOffset (absolute): " << FormatHEX((uint32_t)ScriptState.Offset) - << " (" << ScriptState.Offset << ")" - << "\nOffset (scope-relative): " << FormatHEX((uint32_t)ScriptState.RelOffset) - << " (" << ScriptState.RelOffset << ")\n"; - if (!ScriptState.Package.WriteData(ScriptState.Offset + ScriptState.RelOffset, DataChunk, &BackupData)) - { - *ErrorMessages << "Write error!\n"; - return SetBad(); - } - *ExecutionResults << "Write successful!" << std::endl; - /// backup info - if (ScriptFlags.IsUninstallAllowed) - { - std::ostringstream ss; + std::transform(UPKFileName.begin(), UPKFileName.end(), UPKFileName.begin(), ::tolower); + *ExecutionResults << "Opening package ...\n"; + if (ScriptState.UPKName == UPKFileName) + { + *ExecutionResults << UPKFileName << " is already opened!\n"; + return SetGood(); + } + ScriptState.UPKName = UPKFileName; + std::string pathName = UPKPath + "/" + UPKFileName; + if (ScriptState.Package.Read(pathName.c_str()) == false) + { + *ErrorMessages << "Error reading package: " << pathName << std::endl; + UPKReadErrors err = ScriptState.Package.GetError(); + *ErrorMessages << FormatReadErrors(err); + if (ScriptState.Package.IsCompressed()) + *ErrorMessages << "Compression flags:\n" << FormatCompressionFlags(ScriptState.Package.GetCompressionFlags()); + return SetBad(); + } + AddUPKName(ScriptState.UPKName); + ResetScope(); + *ExecutionResults << "Package file: " << pathName; + *ExecutionResults << std::endl; + if (GUIDs.count(UPKFileName) > 0) + { + bool found = false; + std::string GUID = FormatHEX(ScriptState.Package.GetGUID()); + std::multimap::iterator it; + for (it = GUIDs.equal_range(UPKFileName).first; it != GUIDs.equal_range(UPKFileName).second; ++it) + { + if ( (*it).second == GUID) + { + found = true; + break; + } + } + if (found == false) + { + *ErrorMessages << "Package GUID " << GUID << " does not match any of GUIDs specified for package " << UPKFileName << std::endl; + return SetBad(); + } + } + if (BackupScript.count(ScriptState.UPKName) < 1) + { + BackupScript.insert({ScriptState.UPKName, std::string("")}); + } + *ExecutionResults << "Package opened successfully!\n"; + return SetGood(); +} + +bool ModScript::SetGUID(const std::string& Param) +{ + std::string PackageName, GUID; + std::string str = GetStringValue(Param); + size_t pos = SplitAt(':', str, GUID, PackageName); + if (pos == std::string::npos) + { + *ErrorMessages << "Bad GUID key format!\n"; + return SetBad(); + } + std::transform(PackageName.begin(), PackageName.end(), PackageName.begin(), ::tolower); + GUIDs.insert({PackageName, GUID}); + *ExecutionResults << "Added allowed GUID:\n"; + *ExecutionResults << "Package: " << PackageName << " GUID: " << GUID << std::endl; + return SetGood(); +} + +/******************************************************************** +**************** functions to set patching scope ******************** +*********************************************************************/ + +bool ModScript::SetGlobalOffset(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + ResetScope(); + ScriptState.Scope = UPKScope::Package; + ScriptState.Offset = GetUnsignedValue(Param); + ScriptState.MaxOffset = ScriptState.Package.GetFileSize() - 1; + *ExecutionResults << "Global offset: " << FormatHEX((uint32_t)ScriptState.Offset) + << " (" << ScriptState.Offset << ")" << std::endl; + if (ScriptState.Package.CheckValidFileOffset(ScriptState.Offset) == false) + { + *ErrorMessages << "Invalid package offset!\n"; + return SetBad(); + } + return SetGood(); +} + +bool ModScript::CheckBehavior() +{ + if (ScriptState.Behavior == "") + { + ScriptState.Behavior = "KEEP"; + return SetGood(); + } + else if (ScriptState.Behavior == "KEEP") + { + return SetGood(); + } + else if (ScriptState.Behavior == "MOVE") + { + return SetGood(); + } + else if (ScriptState.Behavior == "AUTO") + { + return SetGood(); + } + else if (ScriptState.Behavior == "INPL") + { + return SetGood(); + } + return SetBad(); +} + +bool ModScript::SetObject(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + ResetScope(); + ScriptState.Scope = UPKScope::Object; + std::string str = GetStringValue(Param), ObjName; + SplitAt(':', str, ObjName, ScriptState.Behavior); + if (!CheckBehavior()) + { + *ErrorMessages << "Bad behavior modifier: " << ScriptState.Behavior << std::endl; + return SetBad(); + } + *ExecutionResults << "Searching for object named " << ObjName << " ...\n"; + UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName); + if (ObjRef == 0) + { + *ErrorMessages << "Can't find object named " << ObjName << std::endl; + return SetBad(); + } + else if (ObjRef < 0) + { + *ErrorMessages << "Import objects have no serial data: " << ObjName << std::endl; + return SetBad(); + } + *ExecutionResults << "Object found!\n"; + ScriptState.ObjIdx = (uint32_t)ObjRef; + ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; + ScriptState.RelOffset = 0; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; + /* + *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) + << "\nObject: " << ObjName + << " (" << ScriptState.Behavior << ")" + << "\nIndex: " << ScriptState.ObjIdx + << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) + << " (" << ScriptState.Offset << ")" << std::endl; + */ + return SetGood(); +} + +bool ModScript::SetNameEntry(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + ResetScope(); + ScriptState.Scope = UPKScope::Name; + std::string ObjName = GetStringValue(Param); + *ExecutionResults << "Searching for name " << ObjName << " ...\n"; + int idx = ScriptState.Package.FindName(ObjName); + if (idx < 0) + { + *ErrorMessages << "Can't find NameTable name " << ObjName << std::endl; + return SetBad(); + } + *ExecutionResults << "Name found!\n"; + ScriptState.ObjIdx = idx; + ScriptState.Offset = ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).EntryOffset; + ScriptState.RelOffset = 0; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).EntrySize - 1; + /* + *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) + << "\nName entry: " << ObjName + << "\nIndex: " << ScriptState.ObjIdx + << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) + << " (" << ScriptState.Offset << ")" << std::endl; + */ + return SetGood(); +} + +bool ModScript::SetImportEntry(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + ResetScope(); + ScriptState.Scope = UPKScope::Import; + std::string ObjName = GetStringValue(Param); + *ExecutionResults << "Searching for import table entry " << ObjName << " ...\n"; + UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName, false); + if (ObjRef == 0) + { + *ErrorMessages << "Can't find ImportTable entry named " << ObjName << std::endl; + return SetBad(); + } + else if (ObjRef > 0) + { + *ErrorMessages << ObjName << " is export object, not import object!" << std::endl; + return SetBad(); + } + *ExecutionResults << "Import table entry found!\n"; + ScriptState.ObjIdx = (uint32_t)(-ObjRef); + ScriptState.Offset = ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).EntryOffset; + ScriptState.RelOffset = 0; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).EntrySize - 1; + /* + *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) + << "\nImport entry: " << ObjName + << "\nIndex: " << ScriptState.ObjIdx + << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) + << " (" << ScriptState.Offset << ")" << std::endl; + */ + return SetGood(); +} + +bool ModScript::SetExportEntry(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + ResetScope(); + ScriptState.Scope = UPKScope::Export; + std::string ObjName = GetStringValue(Param); + *ExecutionResults << "Searching for export table entry " << ObjName << " ...\n"; + UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName); + if (ObjRef == 0) + { + *ErrorMessages << "Can't find ExportTable entry named " << ObjName << std::endl; + return SetBad(); + } + *ExecutionResults << "Export table entry found!\n"; + ScriptState.ObjIdx = (uint32_t)ObjRef; + ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).EntryOffset; + ScriptState.RelOffset = 0; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).EntrySize - 1; + /* + *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) + << "\nExport entry: " << ObjName + << "\nIndex: " << ScriptState.ObjIdx + << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) + << " (" << ScriptState.Offset << ")" << std::endl; + */ + return SetGood(); +} + +bool ModScript::SetRelOffset(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + ScriptState.RelOffset = GetUnsignedValue(Param); + *ExecutionResults << "Relative offset: " << FormatHEX((uint32_t)ScriptState.RelOffset) + << " (" << ScriptState.RelOffset << ")" << std::endl; + if (!IsInsideScope()) + { + *ErrorMessages << "Invalid relative offset!\n"; + return SetBad(); + } + return SetGood(); +} + +/******************************************************************** +**************** helpers to write modded hex/code ******************* +*********************************************************************/ + +void ModScript::ResetMaxOffset() +{ + switch (ScriptState.Scope) + { + case UPKScope::Object: + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; + return; + case UPKScope::Name: + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).EntrySize - 1; + return; + case UPKScope::Import: + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).EntrySize - 1; + return; + case UPKScope::Export: + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).EntrySize - 1; + return; + default: + ScriptState.MaxOffset = ScriptState.Package.GetFileSize() - 1; + return; + } +} + +bool ModScript::IsInsideScope(size_t DataSize) +{ + return (ScriptState.Offset + ScriptState.RelOffset + DataSize - 1) <= ScriptState.MaxOffset; +} + +size_t ModScript::GetDiff(size_t DataSize) +{ + if (!IsInsideScope(DataSize)) + return ((ScriptState.Offset + ScriptState.RelOffset + DataSize - 1) - ScriptState.MaxOffset); + else + return 0; +} + +bool ModScript::CheckMoveResize(size_t DataSize, bool FitScope) +{ + size_t ScopeSize = ScriptState.MaxOffset - ScriptState.Offset - ScriptState.RelOffset + 1; + if (ScriptState.Scope == UPKScope::Object) + { + bool NeedMoveResize = false; + size_t ObjSize = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize; + if (FitScope && ScopeSize != DataSize) + { + if (ScriptState.Behavior != "KEEP") + { + NeedMoveResize = true; + ObjSize += DataSize - ScopeSize; + } + else + { + *ErrorMessages << "Data chunk does not fit current scope!\n"; + return SetBad(); + } + } + else if (!IsInsideScope(DataSize) && ScriptState.Behavior != "KEEP") + { + NeedMoveResize = true; + ObjSize += GetDiff(DataSize); + } + else if (ScriptState.Behavior == "MOVE") + { + NeedMoveResize = true; + } + if (NeedMoveResize) + { + return DoResize(ObjSize); + } + } + else if (FitScope && ScopeSize != DataSize) + { + *ErrorMessages << "Data chunk does not fit current scope!\n"; + return SetBad(); + } + if (!IsInsideScope(DataSize)) + { + *ErrorMessages << "Data chunk too large for current scope!\n"; + return SetBad(); + } + /// resetting max offset and before flag + ResetMaxOffset(); + /// reset before hex/code status + ScriptState.BeforeUsed = false; + ScriptState.BeforeMemSize = 0; + return SetGood(); +} + +bool ModScript::DoResize(int ObjSize) +{ + if (ScriptState.Behavior == "INPL") + { + return ResizeInPlace(ObjSize); + } + else + { + return MoveResizeAtRelOffset(ObjSize); + } +} + +bool ModScript::MoveResizeAtRelOffset(int ObjSize) +{ + *ExecutionResults << "Moving/resizing object.\nNew object size: " << ObjSize << std::endl; + if (ScriptState.Package.MoveResizeObject(ScriptState.ObjIdx, ObjSize, ScriptState.RelOffset) == false) + { + *ErrorMessages << "Error moving/resizing object!\n"; + return SetBad(); + } + *ExecutionResults << "Object moved/resized successfully.\n"; + ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; + /// backup info + if (ScriptFlags.IsUninstallAllowed) + { + std::ostringstream ss; + ss << "EXPAND_UNDO=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; + ss << BackupScript[ScriptState.UPKName]; + BackupScript[ScriptState.UPKName] = ss.str(); + } + return SetGood(); +} + +bool ModScript::ResizeInPlace(int ObjSize) +{ + size_t oldSize = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize; + std::vector oldData = ScriptState.Package.GetExportData(ScriptState.ObjIdx); + *ExecutionResults << "Resizing object in place.\nNew object size: " << ObjSize << std::endl; + if (ScriptState.Package.ResizeInPlace(ScriptState.ObjIdx, ObjSize, ScriptState.RelOffset) == false) + { + *ErrorMessages << "Error resizing object in place!\n"; + return SetBad(); + } + *ExecutionResults << "Object resized in place successfully.\n"; + ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; + /// backup info + if (ScriptFlags.IsUninstallAllowed) + { + std::ostringstream ss; + ss << "OBJECT=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << ":INPL" << "\n\n"; + ss << "RESIZE=" << oldSize << "\n\n"; + ss << "[MODDED_HEX]\n" << MakeTextBlock(oldData.data(), oldData.size()) << "\n\n"; + ss << BackupScript[ScriptState.UPKName]; + BackupScript[ScriptState.UPKName] = ss.str(); + } + return SetGood(); +} + +bool ModScript::WriteBinaryData(const std::vector& DataChunk) +{ + std::vector BackupData; + *ExecutionResults << "Writing data chunk of size " << FormatHEX((uint32_t)DataChunk.size()) + << " (" << DataChunk.size() << ") at" + << "\nScope: " << FormatUPKScope(ScriptState.Scope) + << "\nOffset (absolute): " << FormatHEX((uint32_t)ScriptState.Offset) + << " (" << ScriptState.Offset << ")" + << "\nOffset (scope-relative): " << FormatHEX((uint32_t)ScriptState.RelOffset) + << " (" << ScriptState.RelOffset << ")\n"; + if (!ScriptState.Package.WriteData(ScriptState.Offset + ScriptState.RelOffset, DataChunk, &BackupData)) + { + *ErrorMessages << "Write error!\n"; + return SetBad(); + } + *ExecutionResults << "Write successful!" << std::endl; + /// backup info + if (ScriptFlags.IsUninstallAllowed) + { + std::ostringstream ss; switch (ScriptState.Scope) { - case UPKScope::Object: - ss << "OBJECT=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; - break; - case UPKScope::Import: - ss << "IMPORT_ENTRY=" << ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).FullName << "\n\n"; - break; - case UPKScope::Export: - ss << "EXPORT_ENTRY=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; - break; - case UPKScope::Name: - ss << "NAME_ENTRY=" << ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).Name << "\n\n"; - break; - default: - ss << "OFFSET=" << ScriptState.Offset << "\n\n"; - break; + case UPKScope::Object: + ss << "OBJECT=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; + break; + case UPKScope::Import: + ss << "IMPORT_ENTRY=" << ScriptState.Package.GetImportEntry(ScriptState.ObjIdx).FullName << "\n\n"; + break; + case UPKScope::Export: + ss << "EXPORT_ENTRY=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; + break; + case UPKScope::Name: + ss << "NAME_ENTRY=" << ScriptState.Package.GetNameEntry(ScriptState.ObjIdx).Name << "\n\n"; + break; + default: + ss << "OFFSET=" << ScriptState.Offset << "\n\n"; + break; + } + if (ScriptState.RelOffset > 0) + { + ss << "REL_OFFSET=" << ScriptState.RelOffset << "\n\n"; + } + ss << "[MODDED_HEX]\n" << MakeTextBlock(BackupData.data(), BackupData.size()) << "\n\n"; + ss << BackupScript[ScriptState.UPKName]; + BackupScript[ScriptState.UPKName] = ss.str(); + } + /// if rel offset updating is on + if (ScriptFlags.UpdateRelOffset) + { + /// if next byte is still inside object scope or resize/move is allowed + if (IsInsideScope(DataChunk.size() + 1) || ScriptState.Behavior != "KEEP") + { + uint32_t CurRelOffset = ScriptState.RelOffset + DataChunk.size(); + ScriptState.RelOffset = CurRelOffset; + } + } + return SetGood(); +} + +bool ModScript::WriteModdedData(const std::vector& DataChunk, bool FitScope) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + if (DataChunk.size() < 1) + { + *ErrorMessages << "Invalid/empty data!\n"; + return SetBad(); + } + if (CheckMoveResize(DataChunk.size(), FitScope) == false) + return SetBad(); + return WriteBinaryData(DataChunk); +} + +/******************************************************************** +************** functions to write modified hex/code ***************** +*********************************************************************/ + +bool ModScript::WriteUndoMoveResize(const std::string& Param) +{ + if (SetObject(Param) == false) + return SetBad(); + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "Can (undo) move/resize export data only!\n"; + return SetBad(); + } + *ExecutionResults << "Restoring object ...\n"; + if (ScriptState.Package.UndoMoveResizeObject(ScriptState.ObjIdx) == false) + { + *ErrorMessages << "Error restoring object " << ScriptState.ObjIdx << std::endl; + return SetBad(); + } + *ExecutionResults << "Move/expand undo successful!\nScope set to current object.\n"; + ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; + ScriptState.RelOffset = 0; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; + /* + *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) + << "\nObject: " << ScriptState.ObjIdx + << " (" << ScriptState.Behavior << ")" + << "\nIndex: " << ScriptState.ObjIdx + << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) + << " (" << ScriptState.Offset << ")" << std::endl; + */ + return SetGood(); +} + +bool ModScript::ResizeExportObject(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "Resizing only works for export object data!\n"; + return SetBad(); + } + if (ScriptState.Behavior == "KEEP") + { + *ErrorMessages << "Can't resize: object behavior set to KEEP!\n\n"; + return SetBad(); + } + std::string str = GetStringValue(Param); + std::string newSizeStr = str; + std::string resizeAtStr = ""; + SplitAt(':', str, newSizeStr, resizeAtStr); + int newSize = GetUnsignedValue(newSizeStr); + /// reset object scope + ScriptState.RelOffset = 0; + ResetMaxOffset(); + /// check for resizeAt + int resizeAt = -1; + if (resizeAtStr != "") + { + resizeAt = GetUnsignedValue(resizeAtStr); + if (resizeAt > newSize) + { + *ErrorMessages << "Bad RESIZE parameter: " << newSize << " : " << resizeAt << "!\n\n"; + return SetBad(); + } + ScriptState.RelOffset = resizeAt; + } + return CheckMoveResize(newSize, true); +} + +bool ModScript::WriteBulkData(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "Bulk data can only be saved to export object!\n"; + return SetBad(); + } + std::vector DataChunk = GetDataChunk(Param); + *ExecutionResults << "Bulk data size = " << DataChunk.size() << std::endl; + std::vector BulkData = ScriptState.Package.GetBulkData(ScriptState.Offset + ScriptState.RelOffset, DataChunk); + /// temporarily set ScriptState.Behavior to KEEP to avoid resizing! + std::string SavedBehavior = ScriptState.Behavior; + *ExecutionResults << "Temporarily resetting object behavior to KEEP!\n"; + ScriptState.Behavior = "KEEP"; + /// write bulk data + if (!WriteModdedData(BulkData)) + return SetBad(); + /// restore behavior + ScriptState.Behavior = SavedBehavior; + return SetGood(); +} + +bool ModScript::WriteBulkFile(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "Bulk data can only be saved to export object!\n"; + return SetBad(); + } + std::string BulkFile = GetStringValue(Param); + std::ifstream inFile(BulkFile, std::ios::binary | std::ios::ate); + if (!inFile) + { + *ErrorMessages << "Can't open bulk file: " << BulkFile << std::endl; + return SetBad(); + } + unsigned BulkSize = inFile.tellg(); + if (BulkSize < 1) + { + *ErrorMessages << "Bad bulk data in file " << BulkFile << std::endl; + return SetBad(); + } + *ExecutionResults << "Bulk data size = " << BulkSize << std::endl; + std::vector DataChunk(BulkSize); + inFile.seekg(0); + inFile.read(DataChunk.data(), DataChunk.size()); + std::vector BulkData = ScriptState.Package.GetBulkData(ScriptState.Offset + ScriptState.RelOffset, DataChunk); + /// temporarily set ScriptState.Behavior to KEEP to avoid resizing! + std::string SavedBehavior = ScriptState.Behavior; + *ExecutionResults << "Temporarily resetting object behavior to KEEP!\n"; + ScriptState.Behavior = "KEEP"; + /// write bulk data + if (!WriteModdedData(BulkData)) + return SetBad(); + /// restore behavior + ScriptState.Behavior = SavedBehavior; + return SetGood(); +} + +bool ModScript::WriteModdedHEX(const std::string& Param) +{ + std::vector DataChunk = GetDataChunk(Param); + return WriteModdedData(DataChunk); +} + +bool ModScript::WriteModdedCode(const std::string& Param) +{ + return WriteModdedHEX(ParseScript(Param)); +} + +bool ModScript::WriteReplacementCode(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "Replacement code only works for export object data!\n"; + return SetBad(); + } + size_t ScriptSize = ScriptState.Package.GetScriptSize(ScriptState.ObjIdx); + if (ScriptSize == 0) + { + *ErrorMessages << "Object has no script to replace!\n"; + return SetBad(); + } + size_t ScriptRelOffset = ScriptState.Package.GetScriptRelOffset(ScriptState.ObjIdx); + ScriptState.RelOffset = ScriptRelOffset - 8; + ScriptState.MaxOffset = ScriptState.Offset + ScriptRelOffset + ScriptSize - 1; + unsigned ScriptMemorySize = 0; + /// parse script + std::vector DataChunk = GetDataChunk(ParseScript(Param, &ScriptMemorySize)); + if (DataChunk.size() < 1) + { + *ErrorMessages << "Invalid/empty data!\n"; + return SetBad(); + } + /// sizes + data + ScriptSize = DataChunk.size(); + *ExecutionResults << "New script memory size: " << ScriptMemorySize << " (" << FormatHEX(ScriptMemorySize) << ")\n"; + *ExecutionResults << "New script serial size: " << ScriptSize << " (" << FormatHEX((uint32_t)ScriptSize) << ")\n"; + std::vector DataChunkWithSizes(8 + DataChunk.size()); + memcpy(DataChunkWithSizes.data(), reinterpret_cast(&ScriptMemorySize), 4); + memcpy(DataChunkWithSizes.data() + 4, reinterpret_cast(&ScriptSize), 4); + memcpy(DataChunkWithSizes.data() + 8, DataChunk.data(), DataChunk.size()); + return WriteModdedData(DataChunkWithSizes, true); +} + +bool ModScript::WriteInsertCode(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "Inserting code only works for export object data!\n"; + return SetBad(); + } + std::vector DataChunk = GetDataChunk(ParseScript(Param)); + /// set max offset to current offset to force resize + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.RelOffset; + return WriteModdedData(DataChunk); +} + +/******************************************************************** +******************* function to write binary file ******************* +*********************************************************************/ + +bool ModScript::WriteModdedFile(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + std::string str = GetStringValue(Param); + std::string FilePath = str; + std::string Spec = ""; + SplitAt(':', str, FilePath, Spec); + *ExecutionResults << "Reading binary data from file: " << FilePath << " ...\n"; + std::ifstream BinFile(FilePath.c_str(), std::ios::binary); + if (!BinFile) + { + *ErrorMessages << "Can't open binary file: " << FilePath << std::endl; + return SetBad(); + } + BinFile.seekg(0, std::ios::end); + size_t BinSize = BinFile.tellg(); + BinFile.seekg(0); + std::vector DataChunk(BinSize); + BinFile.read(DataChunk.data(), DataChunk.size()); + if (DataChunk.size() < 1) + { + *ErrorMessages << "Invalid/empty binary file!\n"; + return SetBad(); + } + std::string FileName = GetFilename(FilePath); + *ExecutionResults << "Attempting to set scope by file name: " << FileName << " ...\n"; + if (FileName.find(".NameEntry") != std::string::npos) + { + FileName = FileName.substr(0, FileName.find(".NameEntry")); + if (SetNameEntry(FileName) == false) + return SetBad(); + } + else if (FileName.find(".ImportEntry") != std::string::npos) + { + FileName = FileName.substr(0, FileName.find(".ImportEntry")); + if (SetImportEntry(FileName) == false) + return SetBad(); + } + else if (FileName.find(".ExportEntry") != std::string::npos) + { + FileName = FileName.substr(0, FileName.find(".ExportEntry")); + if (SetExportEntry(FileName) == false) + return SetBad(); + } + else + { + FileName = FileName.substr(0, FileName.find_last_of('.')); + UObjectReference ObjRef = ScriptState.Package.FindObject(FileName); + if (ObjRef > 0) + { + if (SetObject(FileName + ":" + Spec) == false) + return SetBad(); + } + else + { + *ExecutionResults << "File name does not make any sense!\n"; + return SetBad(); + } + } + return WriteModdedData(DataChunk, true); +} + +/******************************************************************** +********************* functions to write values ********************* +*********************************************************************/ + +bool ModScript::WriteByteValue(const std::string& Param) +{ + unsigned UVal = GetUnsignedValue(Param); + if (UVal > 0xFF) + { + *ErrorMessages << "Incorrect byte value: " << UVal << std::endl; + return SetBad(); + } + uint8_t ByteVal = (uint8_t)UVal; + std::vector dataChunk(1); + memcpy(dataChunk.data(), reinterpret_cast(&ByteVal), 1); + *ExecutionResults << "Byte value: " << (int)ByteVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; + return WriteModdedData(dataChunk); +} + +bool ModScript::WriteFloatValue(const std::string& Param) +{ + float FloatVal = GetFloatValue(Param); + std::vector dataChunk(4); + memcpy(dataChunk.data(), reinterpret_cast(&FloatVal), 4); + *ExecutionResults << "Float value: " << FloatVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; + return WriteModdedData(dataChunk); +} + +bool ModScript::WriteIntValue(const std::string& Param) +{ + int IntVal = GetIntValue(Param); + std::vector dataChunk(4); + memcpy(dataChunk.data(), reinterpret_cast(&IntVal), 4); + *ExecutionResults << "Int value: " << IntVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; + return WriteModdedData(dataChunk); +} + +bool ModScript::WriteUnsignedValue(const std::string& Param) +{ + unsigned UVal = GetUnsignedValue(Param); + std::vector dataChunk(4); + memcpy(dataChunk.data(), reinterpret_cast(&UVal), 4); + *ExecutionResults << "Unsigned value: " << UVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; + return WriteModdedData(dataChunk); +} + +bool ModScript::WriteNameIdx(const std::string& Param) +{ + std::string str = GetStringValue(Param); + std::string Name = str; + int num = 0; + size_t pos = str.rfind('_'); + if (pos != std::string::npos && isdigit(str[pos + 1])) + { + Name = str.substr(0, pos); + num = 1 + GetIntValue(str.substr(pos + 1)); + } + *ExecutionResults << "Searching for name " << str << " ...\n"; + int idx = ScriptState.Package.FindName(Name); + if (idx < 0) + { + *ErrorMessages << "Incorrect name: " << str << std::endl; + return SetBad(); + } + *ExecutionResults << "Name found!\n"; + UNameIndex NameIdx; + NameIdx.NameTableIdx = idx; + NameIdx.Numeric = num; + std::vector dataChunk(8); + memcpy(dataChunk.data(), reinterpret_cast(&NameIdx), 8); + *ExecutionResults << "UNameIndex: " << str << " -> " << FormatHEX(NameIdx) << std::endl; + return WriteModdedData(dataChunk); +} + +bool ModScript::WriteObjectIdx(const std::string& Param) +{ + std::string FullName = GetStringValue(Param); + *ExecutionResults << "Searching for object named " << FullName << " ...\n"; + UObjectReference ObjRef = ScriptState.Package.FindObject(FullName, false); + *ExecutionResults << "Object found!\n"; + if (ObjRef == 0) + { + *ErrorMessages << "Can't find object named " << FullName << std::endl; + return SetBad(); + } + std::vector dataChunk(4); + memcpy(dataChunk.data(), reinterpret_cast(&ObjRef), 4); + *ExecutionResults << "UObjectReference: " << FullName << " -> " << FormatHEX((uint32_t)ObjRef) << std::endl; + return WriteModdedData(dataChunk); +} + +/******************************************************************** +*********************** find/replace functions ********************** +*********************************************************************/ + +bool ModScript::SetDataOffset(const std::string& Param, bool isEnd, bool isBeforeData) +{ + if (isEnd && isBeforeData) + { + *ErrorMessages << "Internal error: can't use isEnd and isBeforeData together!\n"; + return SetBad(); + } + std::vector DataChunk = GetDataChunk(Param); + if (DataChunk.size() < 1) + { + *ErrorMessages << "Invalid/empty data!\n"; + return SetBad(); + } + ScriptState.BeforeUsed = isBeforeData; + *ExecutionResults << "Searching for specified data chunk ...\n"; + if (ScriptState.Scope == UPKScope::Package) + { + size_t offset = ScriptState.Package.FindDataChunk(DataChunk); + if (offset != 0) + { + if (isEnd) /// seek to the end of specified data + { + offset += DataChunk.size(); + } + ScriptState.Offset = offset; + ScriptState.RelOffset = 0; + if (isBeforeData) /// restrict scope for BEFORE/AFTER patching + { + ScriptState.MaxOffset = ScriptState.Offset + DataChunk.size() - 1; + } + else /// set scope to entire package + { + ScriptState.MaxOffset = ScriptState.Package.GetFileSize() - 1; + } + *ExecutionResults << "Data found!\nGlobal offset: " << FormatHEX((uint32_t)ScriptState.Offset) + << " (" << ScriptState.Offset << ")" << std::endl; + } + else + { + *ErrorMessages << "Can't find specified data!\n"; + return SetBad(); + } + } + else + { + size_t startOffset = ScriptState.Offset + ScriptState.RelOffset; + size_t offset = ScriptState.Package.FindDataChunk(DataChunk, startOffset, ScriptState.MaxOffset); + if (offset != 0) + { + if (isEnd) /// seek to the end of specified data + { + offset += DataChunk.size(); + } + ScriptState.RelOffset = offset - ScriptState.Offset; + if (isBeforeData) /// restrict scope for BEFORE/AFTER patching + { + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.RelOffset + DataChunk.size() - 1; + } + *ExecutionResults << "Data found!\nRelative offset: " << FormatHEX((uint32_t)ScriptState.RelOffset) + << " (" << ScriptState.RelOffset << ")" << std::endl; + } + else + { + *ErrorMessages << "Can't find specified data!\n"; + return SetBad(); + } + } + if (!IsInsideScope()) + { + *ErrorMessages << "Invalid package offset!\n"; + return SetBad(); + } + return SetGood(); +} + +bool ModScript::SetDataChunkOffset(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + std::string DataStr = Param, SpecStr; + bool isEnd = false; + SplitAt(':', Param, DataStr, SpecStr); + if (SpecStr != "") + { + if (SpecStr == "END") + { + isEnd = true; + } + else if (SpecStr != "BEG") + { + *ErrorMessages << "Bad specifier: " << SpecStr << std::endl; + return SetBad(); + } + } + return SetDataOffset(DataStr, isEnd, false); +} + +bool ModScript::SetCodeOffset(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + std::string DataStr = Param, SpecStr; + bool isEnd = false; + SplitAt(':', Param, DataStr, SpecStr); + if (SpecStr != "") + { + if (SpecStr == "END") + { + isEnd = true; + } + else if (SpecStr != "BEG") + { + *ErrorMessages << "Bad specifier: " << SpecStr << std::endl; + return SetBad(); + } + } + return SetDataOffset(ParseScript(DataStr), isEnd, false); +} + +bool ModScript::SetBeforeHEXOffset(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + return SetDataOffset(Param, false, true); +} + +bool ModScript::SetBeforeCodeOffset(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + unsigned MemSize = 0; + std::string ParsedParam = ParseScript(Param, &MemSize); + ScriptState.BeforeMemSize = MemSize; + return SetBeforeHEXOffset(ParsedParam); +} + +bool ModScript::WriteAfterHEX(const std::string& Param) +{ + return WriteAfterData(Param); +} + +bool ModScript::WriteAfterCode(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + unsigned MemSize = 0; + std::string ParsedParam = ParseScript(Param, &MemSize); + return WriteAfterData(ParsedParam, MemSize); +} + +bool ModScript::WriteReplaceAllHEX(const std::string& Param) +{ + return WriteReplaceAll(Param, false); +} + +bool ModScript::WriteReplaceAllCode(const std::string& Param) +{ + return WriteReplaceAll(Param, true); +} + +bool ModScript::WriteReplaceAll(const std::string& Param, bool isCode) +{ + std::string BeforeStr, AfterStr; + SplitAt(':', Param, BeforeStr, AfterStr); + if (BeforeStr.size() < 1 || AfterStr.size() < 1) + { + *ErrorMessages << "Invalid key parameter(s)!\n"; + return SetBad(); + } + size_t BeforeSize = GetDataChunk(BeforeStr).size(); + size_t AfterSize = GetDataChunk(AfterStr).size(); + size_t SavedRelOffset = ScriptState.RelOffset; + size_t RelOffset = SavedRelOffset; + size_t MaxRelOffset = ScriptState.MaxOffset - ScriptState.Offset; + while (RelOffset + BeforeSize <= MaxRelOffset) + { + ScriptState.RelOffset = RelOffset; + if (isCode) + { + if (!SetBeforeCodeOffset(BeforeStr)) + break; + if (!WriteAfterCode(AfterStr)) + return SetBad(); + } + else + { + if (!SetBeforeHEXOffset(BeforeStr)) + break; + if (!WriteAfterHEX(AfterStr)) + return SetBad(); + } + /// because write operation can cause object resize AfterSize is used + /// and MaxRelOffset is re-calculated + RelOffset += AfterSize; + MaxRelOffset = ScriptState.MaxOffset - ScriptState.Offset; + } + if (ScriptFlags.UpdateRelOffset != true) + { + ScriptState.RelOffset = SavedRelOffset; + } + return SetGood(); +} + +bool ModScript::WriteAfterData(const std::string& DataBlock, int MemSize) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + if (ScriptState.BeforeUsed == false) + { + *ErrorMessages << "Can't use AFTER without BEFORE!\n"; + return SetBad(); + } + std::vector DataChunk = GetDataChunk(DataBlock); + if (DataChunk.size() < 1) + { + *ErrorMessages << "Invalid/empty data!\n"; + return SetBad(); + } + /// calculate adjusted script and memory size for objects, which use scripts + std::vector SizesChunk; + size_t SizesRelOffset; + bool needAdjustSizes = GetAdjustedSizes(SizesChunk, SizesRelOffset, DataChunk.size(), MemSize); + /// writing data + if (!WriteModdedData(DataChunk, true)) + return SetBad(); + /// save rel offset + size_t SavedRelOffset = ScriptState.RelOffset; + /// write new sizes + if (needAdjustSizes) + { + ScriptState.RelOffset = SizesRelOffset; + if (!WriteModdedData(SizesChunk)) + return SetBad(); + } + /// restore rel offset + ScriptState.RelOffset = SavedRelOffset; + return SetGood(); +} + +bool ModScript::GetAdjustedSizes(std::vector& SizesChunk, size_t& SizesRelOffset, int DataSize, int NewMemSize) +{ + SizesRelOffset = 0; + SizesChunk.clear(); + /// not inside export object + if (ScriptState.Scope != UPKScope::Object) + { + return false; + } + size_t ScriptSize = ScriptState.Package.GetScriptSize(ScriptState.ObjIdx); + /// does not have script + if (ScriptSize == 0) + { + return false; + } + size_t ScriptMemSize = ScriptState.Package.GetScriptMemSize(ScriptState.ObjIdx); + size_t ScriptRelOffset = ScriptState.Package.GetScriptRelOffset(ScriptState.ObjIdx); + size_t ScopeSize = ScriptState.MaxOffset - ScriptState.Offset - ScriptState.RelOffset + 1; + /// checking if we're inside script + if ( ScriptState.RelOffset >= ScriptRelOffset && + (ScriptState.RelOffset + ScopeSize) <= (ScriptRelOffset + ScriptSize) ) + { + /// calculating new script size + size_t NewScriptSize = (int)ScriptSize + (int)DataSize - (int)ScopeSize; + /// calculating new memory size + size_t NewScriptMemSize = ScriptMemSize; + if (NewMemSize != -1) + { + NewScriptMemSize = (int)ScriptMemSize + NewMemSize - (int)ScriptState.BeforeMemSize; + } + /// no change in size + if (NewScriptSize == ScriptSize && NewScriptMemSize == ScriptMemSize) + { + return false; + } + /// save new sizes + *ExecutionResults << "New script memory size: " << NewScriptMemSize << " (" << FormatHEX((uint32_t)NewScriptMemSize) << ")\n"; + *ExecutionResults << "New script serial size: " << NewScriptSize << " (" << FormatHEX((uint32_t)NewScriptSize) << ")\n"; + SizesChunk.resize(8); + memcpy(SizesChunk.data(), reinterpret_cast(&NewScriptMemSize), 4); + memcpy(SizesChunk.data() + 4, reinterpret_cast(&NewScriptSize), 4); + SizesRelOffset = ScriptRelOffset - 8; + return true; + } + return false; +} + +/******************************************************************** +********************** move/expand legacy code ********************** +*********************************************************************/ + +bool ModScript::WriteMoveExpandLegacy(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + std::string ObjName, str = GetStringValue(Param), SizeStr; + size_t NewSize = 0; + SplitAt(':', str, ObjName, SizeStr); + if (SizeStr != "") + { + NewSize = GetUnsignedValue(SizeStr); + } + if (SetObject(ObjName) == false) + return SetBad(); + *ExecutionResults << "Moving/expanding function ...\n"; + if (ScriptState.Package.MoveExportData(ScriptState.ObjIdx, NewSize) == false) + { + *ErrorMessages << "Error expanding function!\n"; + return SetBad(); + } + *ExecutionResults << "Function moved/expanded successfully!\n"; + /// backup info + if (ScriptFlags.IsUninstallAllowed) + { + std::ostringstream ss; + ss << "EXPAND_UNDO=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; + ss << BackupScript[ScriptState.UPKName]; + BackupScript[ScriptState.UPKName] = ss.str(); + } + /// set new offset value + ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; + ScriptState.RelOffset = 0; + ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; + return SetGood(); +} + +/******************************************************************** +************************ for optional sections ********************** +*********************************************************************/ + +bool ModScript::Sink(const std::string& Param) +{ + return SetGood(); +} + +/******************************************************************** +************************* get backup script ************************* +*********************************************************************/ + +std::string ModScript::GetBackupScript() +{ + std::ostringstream ss; + for (unsigned i = 0; i < UPKNames.size(); ++i) + { + if (BackupScript[UPKNames[i]] != "") + { + ss << "UPK_FILE=" << UPKNames[i] << "\n\n" + << BackupScript[UPKNames[i]] << "\n\n"; } - if (ScriptState.RelOffset > 0) + } + return ss.str(); +} + +/******************************************************************** +*************************** table entries ************************** +*********************************************************************/ + +bool ModScript::WriteRename(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + std::string str = GetStringValue(Param), ObjName, NewName; + size_t pos = SplitAt(':', str, ObjName, NewName); + if (pos == std::string::npos) + { + *ErrorMessages << "Incorrect rename entry format: " << str << std::endl; + return SetBad(); + } + *ExecutionResults << "Renaming " << ObjName << " to " << NewName << " ...\n"; + if (NewName.length() != ObjName.length()) + { + *ErrorMessages << "New name must have the same length as old name!\n"; + return SetBad(); + } + if (SetNameEntry(ObjName) == false) + { + if (SetNameEntry(NewName) == false) { - ss << "REL_OFFSET=" << ScriptState.RelOffset << "\n\n"; + *ErrorMessages << "Can't find any of the names specified!\n"; + return SetBad(); } - ss << "[MODDED_HEX]\n" << MakeTextBlock(BackupData.data(), BackupData.size()) << "\n\n"; - ss << BackupScript[ScriptState.UPKName]; - BackupScript[ScriptState.UPKName] = ss.str(); - } - /// if rel offset updating is on - if (ScriptFlags.UpdateRelOffset) - { - /// if next byte is still inside object scope or resize/move is allowed - if (IsInsideScope(DataChunk.size() + 1) || ScriptState.Behavior != "KEEP") - { - uint32_t CurRelOffset = ScriptState.RelOffset + DataChunk.size(); - ScriptState.RelOffset = CurRelOffset; - } - } - return SetGood(); -} - -bool ModScript::WriteModdedData(const std::vector& DataChunk, bool FitScope) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - if (DataChunk.size() < 1) - { - *ErrorMessages << "Invalid/empty data!\n"; - return SetBad(); - } - if (CheckMoveResize(DataChunk.size(), FitScope) == false) - return SetBad(); - return WriteBinaryData(DataChunk); -} - -/******************************************************************** -************** functions to write modified hex/code ***************** -*********************************************************************/ - -bool ModScript::WriteUndoMoveResize(const std::string& Param) -{ - if (SetObject(Param) == false) - return SetBad(); - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "Can (undo) move/resize export data only!\n"; - return SetBad(); - } - *ExecutionResults << "Restoring object ...\n"; - if (ScriptState.Package.UndoMoveResizeObject(ScriptState.ObjIdx) == false) - { - *ErrorMessages << "Error restoring object " << ScriptState.ObjIdx << std::endl; - return SetBad(); - } - *ExecutionResults << "Move/expand undo successful!\nScope set to current object.\n"; - ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; - ScriptState.RelOffset = 0; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; - /* - *ExecutionResults << "Scope: " << FormatUPKScope(ScriptState.Scope) - << "\nObject: " << ScriptState.ObjIdx - << " (" << ScriptState.Behavior << ")" - << "\nIndex: " << ScriptState.ObjIdx - << "\nOffset (absolute): " << FormatHEX(ScriptState.Offset) - << " (" << ScriptState.Offset << ")" << std::endl; - */ - return SetGood(); -} - -bool ModScript::ResizeExportObject(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "Resizing only works for export object data!\n"; - return SetBad(); - } - if (ScriptState.Behavior == "KEEP") - { - *ErrorMessages << "Can't resize: object behavior set to KEEP!\n\n"; - return SetBad(); - } - std::string str = GetStringValue(Param); - std::string newSizeStr = str; - std::string resizeAtStr = ""; - SplitAt(':', str, newSizeStr, resizeAtStr); - int newSize = GetUnsignedValue(newSizeStr); - /// reset object scope - ScriptState.RelOffset = 0; - ResetMaxOffset(); - /// check for resizeAt - int resizeAt = -1; - if (resizeAtStr != "") - { - resizeAt = GetUnsignedValue(resizeAtStr); - if (resizeAt > newSize) - { - *ErrorMessages << "Bad RESIZE parameter: " << newSize << " : " << resizeAt << "!\n\n"; - return SetBad(); - } - ScriptState.RelOffset = resizeAt; - } - return CheckMoveResize(newSize, true); -} - -bool ModScript::WriteBulkData(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "Bulk data can only be saved to export object!\n"; - return SetBad(); - } - std::vector DataChunk = GetDataChunk(Param); - *ExecutionResults << "Bulk data size = " << DataChunk.size() << std::endl; - std::vector BulkData = ScriptState.Package.GetBulkData(ScriptState.Offset + ScriptState.RelOffset, DataChunk); - /// temporarily set ScriptState.Behavior to KEEP to avoid resizing! - std::string SavedBehavior = ScriptState.Behavior; - *ExecutionResults << "Temporarily resetting object behavior to KEEP!\n"; - ScriptState.Behavior = "KEEP"; - /// write bulk data - if (!WriteModdedData(BulkData)) - return SetBad(); - /// restore behavior - ScriptState.Behavior = SavedBehavior; - return SetGood(); -} - -bool ModScript::WriteBulkFile(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "Bulk data can only be saved to export object!\n"; - return SetBad(); - } - std::string BulkFile = GetStringValue(Param); - std::ifstream inFile(BulkFile, std::ios::binary | std::ios::ate); - if (!inFile) - { - *ErrorMessages << "Can't open bulk file: " << BulkFile << std::endl; - return SetBad(); - } - unsigned BulkSize = inFile.tellg(); - if (BulkSize < 1) - { - *ErrorMessages << "Bad bulk data in file " << BulkFile << std::endl; - return SetBad(); - } - *ExecutionResults << "Bulk data size = " << BulkSize << std::endl; - std::vector DataChunk(BulkSize); - inFile.seekg(0); - inFile.read(DataChunk.data(), DataChunk.size()); - std::vector BulkData = ScriptState.Package.GetBulkData(ScriptState.Offset + ScriptState.RelOffset, DataChunk); - /// temporarily set ScriptState.Behavior to KEEP to avoid resizing! - std::string SavedBehavior = ScriptState.Behavior; - *ExecutionResults << "Temporarily resetting object behavior to KEEP!\n"; - ScriptState.Behavior = "KEEP"; - /// write bulk data - if (!WriteModdedData(BulkData)) - return SetBad(); - /// restore behavior - ScriptState.Behavior = SavedBehavior; - return SetGood(); -} - -bool ModScript::WriteModdedHEX(const std::string& Param) -{ - std::vector DataChunk = GetDataChunk(Param); - return WriteModdedData(DataChunk); -} - -bool ModScript::WriteModdedCode(const std::string& Param) -{ - return WriteModdedHEX(ParseScript(Param)); -} - -bool ModScript::WriteReplacementCode(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "Replacement code only works for export object data!\n"; - return SetBad(); - } - size_t ScriptSize = ScriptState.Package.GetScriptSize(ScriptState.ObjIdx); - if (ScriptSize == 0) - { - *ErrorMessages << "Object has no script to replace!\n"; - return SetBad(); - } - size_t ScriptRelOffset = ScriptState.Package.GetScriptRelOffset(ScriptState.ObjIdx); - ScriptState.RelOffset = ScriptRelOffset - 8; - ScriptState.MaxOffset = ScriptState.Offset + ScriptRelOffset + ScriptSize - 1; - unsigned ScriptMemorySize = 0; - /// parse script - std::vector DataChunk = GetDataChunk(ParseScript(Param, &ScriptMemorySize)); - if (DataChunk.size() < 1) - { - *ErrorMessages << "Invalid/empty data!\n"; - return SetBad(); - } - /// sizes + data - ScriptSize = DataChunk.size(); - *ExecutionResults << "New script memory size: " << ScriptMemorySize << " (" << FormatHEX(ScriptMemorySize) << ")\n"; - *ExecutionResults << "New script serial size: " << ScriptSize << " (" << FormatHEX((uint32_t)ScriptSize) << ")\n"; - std::vector DataChunkWithSizes(8 + DataChunk.size()); - memcpy(DataChunkWithSizes.data(), reinterpret_cast(&ScriptMemorySize), 4); - memcpy(DataChunkWithSizes.data() + 4, reinterpret_cast(&ScriptSize), 4); - memcpy(DataChunkWithSizes.data() + 8, DataChunk.data(), DataChunk.size()); - return WriteModdedData(DataChunkWithSizes, true); -} - -bool ModScript::WriteInsertCode(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "Inserting code only works for export object data!\n"; - return SetBad(); - } - std::vector DataChunk = GetDataChunk(ParseScript(Param)); - /// set max offset to current offset to force resize - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.RelOffset; - return WriteModdedData(DataChunk); -} - -/******************************************************************** -******************* function to write binary file ******************* -*********************************************************************/ - -bool ModScript::WriteModdedFile(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - std::string str = GetStringValue(Param); - std::string FilePath = str; - std::string Spec = ""; - SplitAt(':', str, FilePath, Spec); - *ExecutionResults << "Reading binary data from file: " << FilePath << " ...\n"; - std::ifstream BinFile(FilePath.c_str(), std::ios::binary); - if (!BinFile) - { - *ErrorMessages << "Can't open binary file: " << FilePath << std::endl; - return SetBad(); - } - BinFile.seekg(0, std::ios::end); - size_t BinSize = BinFile.tellg(); - BinFile.seekg(0); - std::vector DataChunk(BinSize); - BinFile.read(DataChunk.data(), DataChunk.size()); - if (DataChunk.size() < 1) - { - *ErrorMessages << "Invalid/empty binary file!\n"; - return SetBad(); - } - std::string FileName = GetFilename(FilePath); - *ExecutionResults << "Attempting to set scope by file name: " << FileName << " ...\n"; - if (FileName.find(".NameEntry") != std::string::npos) - { - FileName = FileName.substr(0, FileName.find(".NameEntry")); - if (SetNameEntry(FileName) == false) - return SetBad(); - } - else if (FileName.find(".ImportEntry") != std::string::npos) - { - FileName = FileName.substr(0, FileName.find(".ImportEntry")); - if (SetImportEntry(FileName) == false) - return SetBad(); - } - else if (FileName.find(".ExportEntry") != std::string::npos) - { - FileName = FileName.substr(0, FileName.find(".ExportEntry")); - if (SetExportEntry(FileName) == false) - return SetBad(); - } - else - { - FileName = FileName.substr(0, FileName.find_last_of('.')); - UObjectReference ObjRef = ScriptState.Package.FindObject(FileName); - if (ObjRef > 0) - { - if (SetObject(FileName + ":" + Spec) == false) - return SetBad(); - } - else - { - *ExecutionResults << "File name does not make any sense!\n"; - return SetBad(); - } - } - return WriteModdedData(DataChunk, true); -} - -/******************************************************************** -********************* functions to write values ********************* -*********************************************************************/ - -bool ModScript::WriteByteValue(const std::string& Param) -{ - unsigned UVal = GetUnsignedValue(Param); - if (UVal > 0xFF) - { - *ErrorMessages << "Incorrect byte value: " << UVal << std::endl; - return SetBad(); - } - uint8_t ByteVal = (uint8_t)UVal; - std::vector dataChunk(1); - memcpy(dataChunk.data(), reinterpret_cast(&ByteVal), 1); - *ExecutionResults << "Byte value: " << (int)ByteVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; - return WriteModdedData(dataChunk); -} - -bool ModScript::WriteFloatValue(const std::string& Param) -{ - float FloatVal = GetFloatValue(Param); - std::vector dataChunk(4); - memcpy(dataChunk.data(), reinterpret_cast(&FloatVal), 4); - *ExecutionResults << "Float value: " << FloatVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; - return WriteModdedData(dataChunk); -} - -bool ModScript::WriteIntValue(const std::string& Param) -{ - int IntVal = GetIntValue(Param); - std::vector dataChunk(4); - memcpy(dataChunk.data(), reinterpret_cast(&IntVal), 4); - *ExecutionResults << "Int value: " << IntVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; - return WriteModdedData(dataChunk); -} - -bool ModScript::WriteUnsignedValue(const std::string& Param) -{ - unsigned UVal = GetUnsignedValue(Param); - std::vector dataChunk(4); - memcpy(dataChunk.data(), reinterpret_cast(&UVal), 4); - *ExecutionResults << "Unsigned value: " << UVal << " (data chunk: " << FormatHEX(dataChunk) << ")" << std::endl; - return WriteModdedData(dataChunk); -} - -bool ModScript::WriteNameIdx(const std::string& Param) -{ - std::string str = GetStringValue(Param); - std::string Name = str; - int num = 0; - size_t pos = str.rfind('_'); - if (pos != std::string::npos && isdigit(str[pos + 1])) - { - Name = str.substr(0, pos); - num = 1 + GetIntValue(str.substr(pos + 1)); - } - *ExecutionResults << "Searching for name " << str << " ...\n"; - int idx = ScriptState.Package.FindName(Name); - if (idx < 0) - { - *ErrorMessages << "Incorrect name: " << str << std::endl; - return SetBad(); - } - *ExecutionResults << "Name found!\n"; - UNameIndex NameIdx; - NameIdx.NameTableIdx = idx; - NameIdx.Numeric = num; - std::vector dataChunk(8); - memcpy(dataChunk.data(), reinterpret_cast(&NameIdx), 8); - *ExecutionResults << "UNameIndex: " << str << " -> " << FormatHEX(NameIdx) << std::endl; - return WriteModdedData(dataChunk); -} - -bool ModScript::WriteObjectIdx(const std::string& Param) -{ - std::string FullName = GetStringValue(Param); - *ExecutionResults << "Searching for object named " << FullName << " ...\n"; - UObjectReference ObjRef = ScriptState.Package.FindObject(FullName, false); - *ExecutionResults << "Object found!\n"; - if (ObjRef == 0) - { - *ErrorMessages << "Can't find object named " << FullName << std::endl; - return SetBad(); - } - std::vector dataChunk(4); - memcpy(dataChunk.data(), reinterpret_cast(&ObjRef), 4); - *ExecutionResults << "UObjectReference: " << FullName << " -> " << FormatHEX((uint32_t)ObjRef) << std::endl; - return WriteModdedData(dataChunk); -} - -/******************************************************************** -*********************** find/replace functions ********************** -*********************************************************************/ - -bool ModScript::SetDataOffset(const std::string& Param, bool isEnd, bool isBeforeData) -{ - if (isEnd && isBeforeData) - { - *ErrorMessages << "Internal error: can't use isEnd and isBeforeData together!\n"; - return SetBad(); - } - std::vector DataChunk = GetDataChunk(Param); - if (DataChunk.size() < 1) - { - *ErrorMessages << "Invalid/empty data!\n"; - return SetBad(); - } - ScriptState.BeforeUsed = isBeforeData; - *ExecutionResults << "Searching for specified data chunk ...\n"; - if (ScriptState.Scope == UPKScope::Package) - { - size_t offset = ScriptState.Package.FindDataChunk(DataChunk); - if (offset != 0) - { - if (isEnd) /// seek to the end of specified data - { - offset += DataChunk.size(); - } - ScriptState.Offset = offset; - ScriptState.RelOffset = 0; - if (isBeforeData) /// restrict scope for BEFORE/AFTER patching - { - ScriptState.MaxOffset = ScriptState.Offset + DataChunk.size() - 1; - } - else /// set scope to entire package - { - ScriptState.MaxOffset = ScriptState.Package.GetFileSize() - 1; - } - *ExecutionResults << "Data found!\nGlobal offset: " << FormatHEX((uint32_t)ScriptState.Offset) - << " (" << ScriptState.Offset << ")" << std::endl; - } - else - { - *ErrorMessages << "Can't find specified data!\n"; - return SetBad(); - } - } - else - { - size_t startOffset = ScriptState.Offset + ScriptState.RelOffset; - size_t offset = ScriptState.Package.FindDataChunk(DataChunk, startOffset, ScriptState.MaxOffset); - if (offset != 0) - { - if (isEnd) /// seek to the end of specified data - { - offset += DataChunk.size(); - } - ScriptState.RelOffset = offset - ScriptState.Offset; - if (isBeforeData) /// restrict scope for BEFORE/AFTER patching - { - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.RelOffset + DataChunk.size() - 1; - } - *ExecutionResults << "Data found!\nRelative offset: " << FormatHEX((uint32_t)ScriptState.RelOffset) - << " (" << ScriptState.RelOffset << ")" << std::endl; - } - else - { - *ErrorMessages << "Can't find specified data!\n"; - return SetBad(); - } - } - if (!IsInsideScope()) - { - *ErrorMessages << "Invalid package offset!\n"; - return SetBad(); - } - return SetGood(); -} - -bool ModScript::SetDataChunkOffset(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - std::string DataStr = Param, SpecStr; - bool isEnd = false; - SplitAt(':', Param, DataStr, SpecStr); - if (SpecStr != "") - { - if (SpecStr == "END") - { - isEnd = true; - } - else if (SpecStr != "BEG") - { - *ErrorMessages << "Bad specifier: " << SpecStr << std::endl; - return SetBad(); - } - } - return SetDataOffset(DataStr, isEnd, false); -} - -bool ModScript::SetCodeOffset(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - std::string DataStr = Param, SpecStr; - bool isEnd = false; - SplitAt(':', Param, DataStr, SpecStr); - if (SpecStr != "") - { - if (SpecStr == "END") - { - isEnd = true; - } - else if (SpecStr != "BEG") - { - *ErrorMessages << "Bad specifier: " << SpecStr << std::endl; - return SetBad(); - } - } - return SetDataOffset(ParseScript(DataStr), isEnd, false); -} - -bool ModScript::SetBeforeHEXOffset(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - return SetDataOffset(Param, false, true); -} - -bool ModScript::SetBeforeCodeOffset(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - unsigned MemSize = 0; - std::string ParsedParam = ParseScript(Param, &MemSize); - ScriptState.BeforeMemSize = MemSize; - return SetBeforeHEXOffset(ParsedParam); -} - -bool ModScript::WriteAfterHEX(const std::string& Param) -{ - return WriteAfterData(Param); -} - -bool ModScript::WriteAfterCode(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - unsigned MemSize = 0; - std::string ParsedParam = ParseScript(Param, &MemSize); - return WriteAfterData(ParsedParam, MemSize); -} - -bool ModScript::WriteReplaceAllHEX(const std::string& Param) -{ - return WriteReplaceAll(Param, false); -} - -bool ModScript::WriteReplaceAllCode(const std::string& Param) -{ - return WriteReplaceAll(Param, true); -} - -bool ModScript::WriteReplaceAll(const std::string& Param, bool isCode) -{ - std::string BeforeStr, AfterStr; - SplitAt(':', Param, BeforeStr, AfterStr); - if (BeforeStr.size() < 1 || AfterStr.size() < 1) - { - *ErrorMessages << "Invalid key parameter(s)!\n"; - return SetBad(); - } - size_t BeforeSize = GetDataChunk(BeforeStr).size(); - size_t AfterSize = GetDataChunk(AfterStr).size(); - size_t SavedRelOffset = ScriptState.RelOffset; - size_t RelOffset = SavedRelOffset; - size_t MaxRelOffset = ScriptState.MaxOffset - ScriptState.Offset; - while (RelOffset + BeforeSize <= MaxRelOffset) - { - ScriptState.RelOffset = RelOffset; - if (isCode) - { - if (!SetBeforeCodeOffset(BeforeStr)) - break; - if (!WriteAfterCode(AfterStr)) - return SetBad(); - } - else - { - if (!SetBeforeHEXOffset(BeforeStr)) - break; - if (!WriteAfterHEX(AfterStr)) - return SetBad(); - } - /// because write operation can cause object resize AfterSize is used - /// and MaxRelOffset is re-calculated - RelOffset += AfterSize; - MaxRelOffset = ScriptState.MaxOffset - ScriptState.Offset; - } - if (ScriptFlags.UpdateRelOffset != true) - { - ScriptState.RelOffset = SavedRelOffset; - } - return SetGood(); -} - -bool ModScript::WriteAfterData(const std::string& DataBlock, int MemSize) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - if (ScriptState.BeforeUsed == false) - { - *ErrorMessages << "Can't use AFTER without BEFORE!\n"; - return SetBad(); - } - std::vector DataChunk = GetDataChunk(DataBlock); - if (DataChunk.size() < 1) - { - *ErrorMessages << "Invalid/empty data!\n"; - return SetBad(); - } - /// calculate adjusted script and memory size for objects, which use scripts - std::vector SizesChunk; - size_t SizesRelOffset; - bool needAdjustSizes = GetAdjustedSizes(SizesChunk, SizesRelOffset, DataChunk.size(), MemSize); - /// writing data - if (!WriteModdedData(DataChunk, true)) - return SetBad(); - /// save rel offset - size_t SavedRelOffset = ScriptState.RelOffset; - /// write new sizes - if (needAdjustSizes) - { - ScriptState.RelOffset = SizesRelOffset; - if (!WriteModdedData(SizesChunk)) - return SetBad(); - } - /// restore rel offset - ScriptState.RelOffset = SavedRelOffset; - return SetGood(); -} - -bool ModScript::GetAdjustedSizes(std::vector& SizesChunk, size_t& SizesRelOffset, int DataSize, int NewMemSize) -{ - SizesRelOffset = 0; - SizesChunk.clear(); - /// not inside export object - if (ScriptState.Scope != UPKScope::Object) - { - return false; - } - size_t ScriptSize = ScriptState.Package.GetScriptSize(ScriptState.ObjIdx); - /// does not have script - if (ScriptSize == 0) - { - return false; - } - size_t ScriptMemSize = ScriptState.Package.GetScriptMemSize(ScriptState.ObjIdx); - size_t ScriptRelOffset = ScriptState.Package.GetScriptRelOffset(ScriptState.ObjIdx); - size_t ScopeSize = ScriptState.MaxOffset - ScriptState.Offset - ScriptState.RelOffset + 1; - /// checking if we're inside script - if ( ScriptState.RelOffset >= ScriptRelOffset && - (ScriptState.RelOffset + ScopeSize) <= (ScriptRelOffset + ScriptSize) ) - { - /// calculating new script size - size_t NewScriptSize = (int)ScriptSize + (int)DataSize - (int)ScopeSize; - /// calculating new memory size - size_t NewScriptMemSize = ScriptMemSize; - if (NewMemSize != -1) - { - NewScriptMemSize = (int)ScriptMemSize + NewMemSize - (int)ScriptState.BeforeMemSize; - } - /// no change in size - if (NewScriptSize == ScriptSize && NewScriptMemSize == ScriptMemSize) - { - return false; - } - /// save new sizes - *ExecutionResults << "New script memory size: " << NewScriptMemSize << " (" << FormatHEX((uint32_t)NewScriptMemSize) << ")\n"; - *ExecutionResults << "New script serial size: " << NewScriptSize << " (" << FormatHEX((uint32_t)NewScriptSize) << ")\n"; - SizesChunk.resize(8); - memcpy(SizesChunk.data(), reinterpret_cast(&NewScriptMemSize), 4); - memcpy(SizesChunk.data() + 4, reinterpret_cast(&NewScriptSize), 4); - SizesRelOffset = ScriptRelOffset - 8; - return true; - } - return false; -} - -/******************************************************************** -********************** move/expand legacy code ********************** -*********************************************************************/ - -bool ModScript::WriteMoveExpandLegacy(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - std::string ObjName, str = GetStringValue(Param), SizeStr; - size_t NewSize = 0; - SplitAt(':', str, ObjName, SizeStr); - if (SizeStr != "") - { - NewSize = GetUnsignedValue(SizeStr); - } - if (SetObject(ObjName) == false) - return SetBad(); - *ExecutionResults << "Moving/expanding function ...\n"; - if (ScriptState.Package.MoveExportData(ScriptState.ObjIdx, NewSize) == false) - { - *ErrorMessages << "Error expanding function!\n"; - return SetBad(); - } - *ExecutionResults << "Function moved/expanded successfully!\n"; - /// backup info - if (ScriptFlags.IsUninstallAllowed) - { - std::ostringstream ss; - ss << "EXPAND_UNDO=" << ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName << "\n\n"; - ss << BackupScript[ScriptState.UPKName]; - BackupScript[ScriptState.UPKName] = ss.str(); - } - /// set new offset value - ScriptState.Offset = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialOffset; - ScriptState.RelOffset = 0; - ScriptState.MaxOffset = ScriptState.Offset + ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).SerialSize - 1; - return SetGood(); -} - -/******************************************************************** -************************ for optional sections ********************** -*********************************************************************/ - -bool ModScript::Sink(const std::string& Param) -{ - return SetGood(); -} - -/******************************************************************** -************************* get backup script ************************* -*********************************************************************/ - -std::string ModScript::GetBackupScript() -{ - std::ostringstream ss; - for (unsigned i = 0; i < UPKNames.size(); ++i) - { - if (BackupScript[UPKNames[i]] != "") - { - ss << "UPK_FILE=" << UPKNames[i] << "\n\n" - << BackupScript[UPKNames[i]] << "\n\n"; - } - } - return ss.str(); -} - -/******************************************************************** -*************************** table entries ************************** -*********************************************************************/ - -bool ModScript::WriteRename(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - std::string str = GetStringValue(Param), ObjName, NewName; - size_t pos = SplitAt(':', str, ObjName, NewName); - if (pos == std::string::npos) - { - *ErrorMessages << "Incorrect rename entry format: " << str << std::endl; - return SetBad(); - } - *ExecutionResults << "Renaming " << ObjName << " to " << NewName << " ...\n"; - if (NewName.length() != ObjName.length()) - { - *ErrorMessages << "New name must have the same length as old name!\n"; - return SetBad(); - } - if (SetNameEntry(ObjName) == false) - { - if (SetNameEntry(NewName) == false) - { - *ErrorMessages << "Can't find any of the names specified!\n"; - return SetBad(); - } - else - { - *ExecutionResults << "Name entry already has name " << NewName << std::endl; - return SetGood(); - } - } - if (ScriptState.Package.WriteNameTableName(ScriptState.ObjIdx, NewName) == false) - { - *ErrorMessages << "Error writing new name!\n"; - return SetBad(); - } - *ExecutionResults << "Renamed successfully!\n"; - /// backup info - if (ScriptFlags.IsUninstallAllowed) - { - std::ostringstream ss; - ss << "RENAME=" << NewName << ":" << ObjName << "\n\n"; - ss << BackupScript[ScriptState.UPKName]; - BackupScript[ScriptState.UPKName] = ss.str(); - } - return SetGood(); -} - -bool ModScript::WriteAddNameEntry(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - *ExecutionResults << "Adding new name entry ...\n"; - FNameEntry Entry; - std::vector data = GetDataChunk(ParseScript(Param)); - if (!ScriptState.Package.Deserialize(Entry, data)) - { - *ErrorMessages << "Error deserializing new name entry: wrong data!\n"; - return SetBad(); - } - if (ScriptState.Package.FindName(Entry.Name) != -1) - { - *ExecutionResults << "Name " << Entry.Name << " already exists, skipping...\n"; - return SetGood(); - } - if (!ScriptState.Package.AddNameEntry(Entry)) - { - *ErrorMessages << "Error adding new name entry!\n"; - return SetBad(); - } - *ExecutionResults << "Name " << Entry.Name << " added successfully!\n"; - return SetGood(); -} - -bool ModScript::WriteAddImportEntry(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - *ExecutionResults << "Adding new import entry ...\n"; - FObjectImport Entry; - std::vector data = GetDataChunk(ParseScript(Param)); - if (!ScriptState.Package.Deserialize(Entry, data)) - { - *ErrorMessages << "Error deserializing new import entry: wrong data!\n"; - return SetBad(); - } - if (ScriptState.Package.FindObject(Entry.FullName, false) != 0) - { - *ExecutionResults << "Import object " << Entry.FullName << " already exists, skipping...\n"; - return SetGood(); - } - if (!ScriptState.Package.AddImportEntry(Entry)) - { - *ErrorMessages << "Error adding new import entry!\n"; - return SetBad(); - } - *ExecutionResults << "Import object " << Entry.FullName << " added successfully!\n"; - return SetGood(); -} - -bool ModScript::WriteAddExportEntry(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - *ExecutionResults << "Adding new export entry ...\n"; - FObjectExport Entry; - std::vector data = GetDataChunk(ParseScript(Param)); - if (!ScriptState.Package.Deserialize(Entry, data)) - { - *ErrorMessages << "Error deserializing new export entry: wrong data!\n"; - return SetBad(); - } - if (ScriptState.Package.FindObject(Entry.FullName, true) != 0) - { - *ExecutionResults << "Export object " << Entry.FullName << " already exists, skipping...\n"; - return SetGood(); - } - if (!ScriptState.Package.AddExportEntry(Entry)) - { - *ErrorMessages << "Error adding new export entry!\n"; - return SetBad(); - } - *ExecutionResults << "Export object " << Entry.FullName << " added and linked successfully!\n"; - return SetGood(); -} - -/******************************************************************** -************************ pseudo-code parsing ************************ -*********************************************************************/ - -bool ModScript::AddAlias(const std::string& Param) -{ - if (ScriptState.Package.IsLoaded() == false) - { - *ErrorMessages << "Package is not opened!\n"; - return SetBad(); - } - std::string Name, Replacement; - size_t pos = SplitAt(':', Param, Name, Replacement); - if (pos == std::string::npos) - { - *ErrorMessages << "Bad key value: " << Param << std::endl; - return SetBad(); - } - if (ScriptState.Scope == UPKScope::Object) - { - Name = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName + '.' + Name; - } - if (Alias.count(Name) != 0) - { - *ErrorMessages << "Alias is already defined: " << Name << std::endl; - return SetBad(); - } - Alias[Name] = Replacement; - *ExecutionResults << "Alias added successfully: " << Name << std::endl; - return SetGood(); -} - -std::string EatWhite(std::string str, char delim = 0) -{ - std::string ret, tail = ""; - unsigned length = str.length(); - if (delim != 0) /// use delimiter - { - size_t pos = str.find_first_of(delim); - if (pos != std::string::npos) - { - length = pos; - tail = str.substr(pos); - } - } - for (unsigned i = 0; i < length; ++i) - { - if (!isspace(str[i])) - ret += str[i]; - } - ret += tail; - return ret; -} - + else + { + *ExecutionResults << "Name entry already has name " << NewName << std::endl; + return SetGood(); + } + } + if (ScriptState.Package.WriteNameTableName(ScriptState.ObjIdx, NewName) == false) + { + *ErrorMessages << "Error writing new name!\n"; + return SetBad(); + } + *ExecutionResults << "Renamed successfully!\n"; + /// backup info + if (ScriptFlags.IsUninstallAllowed) + { + std::ostringstream ss; + ss << "RENAME=" << NewName << ":" << ObjName << "\n\n"; + ss << BackupScript[ScriptState.UPKName]; + BackupScript[ScriptState.UPKName] = ss.str(); + } + return SetGood(); +} + +bool ModScript::WriteAddNameEntry(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + *ExecutionResults << "Adding new name entry ...\n"; + FNameEntry Entry; + std::vector data = GetDataChunk(ParseScript(Param)); + if (!ScriptState.Package.Deserialize(Entry, data)) + { + *ErrorMessages << "Error deserializing new name entry: wrong data!\n"; + return SetBad(); + } + if (ScriptState.Package.FindName(Entry.Name) != -1) + { + *ExecutionResults << "Name " << Entry.Name << " already exists, skipping...\n"; + return SetGood(); + } + if (!ScriptState.Package.AddNameEntry(Entry)) + { + *ErrorMessages << "Error adding new name entry!\n"; + return SetBad(); + } + *ExecutionResults << "Name " << Entry.Name << " added successfully!\n"; + return SetGood(); +} + +bool ModScript::WriteAddImportEntry(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + *ExecutionResults << "Adding new import entry ...\n"; + FObjectImport Entry; + std::vector data = GetDataChunk(ParseScript(Param)); + if (!ScriptState.Package.Deserialize(Entry, data)) + { + *ErrorMessages << "Error deserializing new import entry: wrong data!\n"; + return SetBad(); + } + if (ScriptState.Package.FindObject(Entry.FullName, false) != 0) + { + *ExecutionResults << "Import object " << Entry.FullName << " already exists, skipping...\n"; + return SetGood(); + } + if (!ScriptState.Package.AddImportEntry(Entry)) + { + *ErrorMessages << "Error adding new import entry!\n"; + return SetBad(); + } + *ExecutionResults << "Import object " << Entry.FullName << " added successfully!\n"; + return SetGood(); +} + +bool ModScript::WriteAddExportEntry(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + *ExecutionResults << "Adding new export entry ...\n"; + FObjectExport Entry; + std::vector data = GetDataChunk(ParseScript(Param)); + if (!ScriptState.Package.Deserialize(Entry, data)) + { + *ErrorMessages << "Error deserializing new export entry: wrong data!\n"; + return SetBad(); + } + if (ScriptState.Package.FindObject(Entry.FullName, true) != 0) + { + *ExecutionResults << "Export object " << Entry.FullName << " already exists, skipping...\n"; + return SetGood(); + } + if (!ScriptState.Package.AddExportEntry(Entry)) + { + *ErrorMessages << "Error adding new export entry!\n"; + return SetBad(); + } + *ExecutionResults << "Export object " << Entry.FullName << " added and linked successfully!\n"; + return SetGood(); +} + +/******************************************************************** +************************ pseudo-code parsing ************************ +*********************************************************************/ + +bool ModScript::AddAlias(const std::string& Param) +{ + if (ScriptState.Package.IsLoaded() == false) + { + *ErrorMessages << "Package is not opened!\n"; + return SetBad(); + } + std::string Name, Replacement; + size_t pos = SplitAt(':', Param, Name, Replacement); + if (pos == std::string::npos) + { + *ErrorMessages << "Bad key value: " << Param << std::endl; + return SetBad(); + } + if (ScriptState.Scope == UPKScope::Object) + { + Name = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName + '.' + Name; + } + if (Alias.count(Name) != 0) + { + *ErrorMessages << "Alias is already defined: " << Name << std::endl; + return SetBad(); + } + Alias[Name] = Replacement; + *ExecutionResults << "Alias added successfully: " << Name << std::endl; + return SetGood(); +} + +std::string EatWhite(std::string str, char delim = 0) +{ + std::string ret, tail = ""; + unsigned length = str.length(); + if (delim != 0) /// use delimiter + { + size_t pos = str.find_first_of(delim); + if (pos != std::string::npos) + { + length = pos; + tail = str.substr(pos); + } + } + for (unsigned i = 0; i < length; ++i) + { + if (!isspace(str[i])) + ret += str[i]; + } + ret += tail; + return ret; +} + bool HasText(std::string str) { std::string testStr = EatWhite(str); @@ -1800,34 +1800,34 @@ bool HasText(std::string str) } return false; } - -std::string GetWord(std::istream& in) -{ - std::string word; - if (!in.good()) - { - return ""; - } - /// discard leading white-spaces - char ch = '\n'; - while (isspace(ch) && in.good()) - { - ch = in.get(); - } - /// if stream has ended, return empty string - if (!in.good()) - { - return ""; - } - /// if character is not white-space and stream still good - word += ch; - /// extract token - if (ch == '<') - { - bool done = false; - while (!done && in.good()) - { - ch = in.get(); + +std::string GetWord(std::istream& in) +{ + std::string word; + if (!in.good()) + { + return ""; + } + /// discard leading white-spaces + char ch = '\n'; + while (isspace(ch) && in.good()) + { + ch = in.get(); + } + /// if stream has ended, return empty string + if (!in.good()) + { + return ""; + } + /// if character is not white-space and stream still good + word += ch; + /// extract token + if (ch == '<') + { + bool done = false; + while (!done && in.good()) + { + ch = in.get(); word += ch; if (ch == '>') { @@ -1843,441 +1843,441 @@ std::string GetWord(std::istream& in) done = true; } } - } - } - return word; - } - /// extract command - if (ch == '[') - { - while (ch != ']' && in.good()) - { - ch = in.get(); - word += ch; - } - return word; - } - /// extract marker - if (ch == '(' || ch == ')') - { - return word; - } - /// extract HEX - if (isxdigit(ch)) - { - while (isxdigit(ch) && in.good()) - { - ch = in.get(); - if (isxdigit(ch)) - word += ch; - } - return word; - } - /// extract generic word - while (!isspace(ch) && in.good()) - { - ch = in.get(); - if (!isspace(ch)) - word += ch; - } - return word; -} - -std::string ExtractString(std::string str) -{ - std::string ret; - size_t pos = str.find_first_of("\""); - if (pos == std::string::npos || pos + 1 >= str.length()) - { - return ""; - } - ret = str.substr(pos + 1); - pos = ret.find_last_of("\""); - if (pos == std::string::npos || pos == 0) - { - return ""; - } - ret = ret.substr(0, pos); - return ret; -} - -std::string ModScript::ParseScript(std::string ScriptData, unsigned* ScriptMemSizeRef) -{ - std::ostringstream ScriptHEX; - std::istringstream WorkingData(ScriptData); - std::map Labels; - std::stack MarkerLabels; - unsigned ScriptMemSize = 0, MemSize = 0; - bool needSecondPass = false; - unsigned numPasses = 0; - do - { - needSecondPass = false; - if (numPasses > 10) - { - *ErrorMessages << "Infinite loop detected (numPasses > 10)!" << std::endl; - SetBad(); - return std::string(""); - } - if (numPasses != 0) /// no need to parse twice - { - WorkingData.str(ScriptHEX.str()); - WorkingData.clear(); - ScriptHEX.str(""); - ScriptHEX.clear(); - if (MarkerLabels.size() != 0) - { - *ErrorMessages << "Unresolved marker(s) found!" << std::endl; - SetBad(); - return std::string(""); - } - } - while (!WorkingData.eof()) - { - std::string NextWord; - NextWord = GetWord(WorkingData); - if (NextWord == "") - { - /// skip empty lines - } - else if (IsHEX(NextWord)) - { - ScriptHEX << NextWord << " "; - if (numPasses == 0) - ScriptMemSize += 1; - } - else if (IsToken(NextWord)) - { - ScriptHEX << TokenToHEX(NextWord, &MemSize); - if (numPasses == 0) - ScriptMemSize += MemSize; - if (!ScriptState.Good) - { - *ErrorMessages << "Bad token: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - } - else if (IsCommand(NextWord)) - { - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "You can't use code commands outside Object scope: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - std::string Command = NextWord.substr(1, NextWord.length()-2); /// remove [] - Command = EatWhite(Command); /// remove white-spaces - if (Command[0] == '@') /// label reference - { - if (Command.length() == 1) /// [@] - auto-calculate memory size - { - if (numPasses == 0) /// first pass - create label - { - std::string autoLabel = "__automemsize__" + FormatHEX(ScriptMemSize); - if (Labels.count(autoLabel) != 0) - { - *ErrorMessages << "Internal error! Duplicated auto-label: " << autoLabel << std::endl; - SetBad(); - return std::string(""); - } - Labels[autoLabel] = 0; /// init new label - MarkerLabels.push(autoLabel); /// keep track of unresolved labels - needSecondPass = true; /// request second pass - ScriptHEX << "[@" << autoLabel << "] "; /// put auto-generated label back into the stream - } - else - { - *ErrorMessages << "Internal error! Unresolved command: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - } - else /// [@label_name] - resolve named labels - { - if (Labels.count(Command.substr(1)) != 0) /// found reference - { - uint16_t LabelPos = Labels[Command.substr(1)]; - ScriptHEX << MakeTextBlock(reinterpret_cast(&LabelPos), 2); - } - else - { - ScriptHEX << NextWord << " "; - needSecondPass = true; - if (numPasses != 0 && MarkerLabels.size() == 0) - { - *ErrorMessages << "Unresolved reference: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - } - } - if (numPasses == 0) - ScriptMemSize += 2; - } - else if (Command[0] == '#') /// label mark - { - if (numPasses == 0 && Labels.count(Command.substr(1)) != 0) - { - *ErrorMessages << "Multiple labels: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - if (numPasses == 0) - Labels[Command.substr(1)] = ScriptMemSize; - } - } - else if(IsMarker(NextWord)) /// resolve memory size markers '(' and ')' - { - if (numPasses != 0) /// should not be here at the second pass - { - *ErrorMessages << "Internal error! Unresolved marker: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - if (NextWord == "(") /// start position - { - if (MarkerLabels.size() == 0 || Labels[MarkerLabels.top()] != 0) - { - *ErrorMessages << "Bad marker: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - Labels[MarkerLabels.top()] = ScriptMemSize; - } - else /// end position - { - if (MarkerLabels.size() == 0 || Labels[MarkerLabels.top()] == 0) - { - *ErrorMessages << "Bad marker: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - Labels[MarkerLabels.top()] = ScriptMemSize - Labels[MarkerLabels.top()]; - MarkerLabels.pop(); /// close the current marker - } - } - else - { - *ErrorMessages << "Bad token: " << NextWord << std::endl; - SetBad(); - return std::string(""); - } - } - ++numPasses; - } while (needSecondPass); - if (ScriptMemSizeRef != nullptr) - { - (*ScriptMemSizeRef) = ScriptMemSize; - } - return ScriptHEX.str(); -} - -bool ModScript::IsHEX(std::string word) -{ - if (word.length() != 2) - return false; - return (isxdigit(word.front()) && isxdigit(word.back())); -} - -bool ModScript::IsToken(std::string word) -{ - if (word.length() < 3) - return false; - return (word.front() == '<' && word.back() == '>'); -} - -bool ModScript::IsCommand(std::string word) -{ - if (word.length() < 3) - return false; - return (word.front() == '[' && word.back() == ']'); -} - -bool ModScript::IsMarker(std::string word) -{ - if (word.length() != 1) /// one symbol '(' or ')' - return false; - return (word.front() == '(' || word.back() == ')'); -} - -std::string ModScript::TokenToHEX(std::string Token, unsigned* MemSizeRef) -{ - std::string Code = Token.substr(1, Token.length()-2); /// remove <> - Code = EatWhite(Code, '\"'); /// remove white-spaces - unsigned MemSize = 1; - std::vector dataChunk; - if (Code[0] == '!') /// parse alias - { - std::string Name = Code.substr(1); - if (ScriptState.Scope == UPKScope::Object) - { - Name = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName + '.' + Name; - if (Alias.count(Name) == 0) - { - Name = Code.substr(1); - } - } - if (Alias.count(Name) == 0) - { - *ErrorMessages << "Alias does not exist: " << Name << std::endl; - SetBad(); - return std::string(""); - } - std::string replacement = Alias[Name]; - std::string parsed = ParseScript(replacement, &MemSize); - dataChunk = GetDataChunk(parsed); - } - else if (Code[0] == '%') - { - if (Code[1] == 'f') - { - float FloatVal = GetFloatValue(Code.substr(2)); - dataChunk.resize(4); - memcpy(dataChunk.data(), reinterpret_cast(&FloatVal), 4); - MemSize = 4; - } - else if (Code[1] == 'i') - { - int IntVal = GetIntValue(Code.substr(2)); - dataChunk.resize(4); - memcpy(dataChunk.data(), reinterpret_cast(&IntVal), 4); - MemSize = 4; - } - else if (Code[1] == 'u') - { - unsigned UnsignedVal = GetUnsignedValue(Code.substr(2)); - dataChunk.resize(4); - memcpy(dataChunk.data(), reinterpret_cast(&UnsignedVal), 4); - MemSize = 4; - } - else if (Code[1] == 's') - { - unsigned UnsignedVal = GetUnsignedValue(Code.substr(2)); - if (UnsignedVal > 0xFFFF) - { - *ErrorMessages << "Incorrect short value: " << UnsignedVal << std::endl; - SetBad(); - return std::string(""); - } - uint16_t ShortVal = (uint16_t)UnsignedVal; - dataChunk.resize(2); - memcpy(dataChunk.data(), reinterpret_cast(&ShortVal), 2); - MemSize = 2; - } - else if (Code[1] == 'b') - { - unsigned UnsignedVal = GetUnsignedValue(Code.substr(2)); - if (UnsignedVal > 0xFF) - { - *ErrorMessages << "Incorrect byte value: " << UnsignedVal << std::endl; - SetBad(); - return std::string(""); - } - uint8_t ByteVal = (uint8_t)UnsignedVal; - dataChunk.resize(1); - memcpy(dataChunk.data(), reinterpret_cast(&ByteVal), 1); - MemSize = 1; - } - else if (Code[1] == 't') - { - std::string strVal = ExtractString(Code); - dataChunk.resize(strVal.length()+1); - memcpy(dataChunk.data(), strVal.c_str(), strVal.length()+1); - MemSize = strVal.length()+1; - } - else - { - *ErrorMessages << "Bad token: " << Code << std::endl; - SetBad(); - return std::string(""); - } - } - else if (Code[0] == '@') /// member variable reference - { - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "Can't use member variable references outside Object scope: " << Code << std::endl; - SetBad(); - return std::string(""); - } - std::string ObjName = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName; - std::string ClassName = ObjName.substr(0, ObjName.find('.')); - std::string VarName = ClassName + '.' + Code.substr(1); - UObjectReference ObjRef = ScriptState.Package.FindObject(VarName, true); - if (ObjRef == 0) - { - *ErrorMessages << "Bad object name: " << VarName << std::endl; - SetBad(); - return std::string(""); - } - dataChunk.resize(4); - memcpy(dataChunk.data(), reinterpret_cast(&ObjRef), 4); - MemSize = 8; - } - else if (Code.find(".") != std::string::npos) - { - std::string ObjName = Code; - if (Code.front() == '.') /// local var reference - { - if (ScriptState.Scope != UPKScope::Object) - { - *ErrorMessages << "You can't use local references outside Object scope: " << Code << std::endl; - SetBad(); - return std::string(""); - } - else - { - ObjName = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName + Code; - } - } - else if (Code.find("Class.") == 0) /// class reference - { - ObjName = Code.substr(6); - } - UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName, false); - if (ObjRef == 0) - { - *ErrorMessages << "Bad object name: " << ObjName << std::endl; - SetBad(); - return std::string(""); - } - dataChunk.resize(4); - memcpy(dataChunk.data(), reinterpret_cast(&ObjRef), 4); - MemSize = 8; - } - else if (Code == "NullRef") /// null object - { - dataChunk.resize(4, 0); - MemSize = 8; - } - else /// Name reference - { - std::string Name = Code; - int num = 0; - size_t pos = Code.rfind('_'); - if (pos != std::string::npos && isdigit(Code[pos + 1])) - { - Name = Code.substr(0, pos); - num = 1 + GetIntValue(Code.substr(pos + 1)); - } - int idx = ScriptState.Package.FindName(Name); - if (idx < 0) - { - *ErrorMessages << "Bad name: " << Name << std::endl; - SetBad(); - return std::string(""); - } - UNameIndex NameIdx; - NameIdx.NameTableIdx = idx; - NameIdx.Numeric = num; - dataChunk.resize(8); - memcpy(dataChunk.data(), reinterpret_cast(&NameIdx), 8); - MemSize = 8; - } - if (MemSizeRef != nullptr) - { - (*MemSizeRef) = MemSize; - } - return FormatHEX(dataChunk); -} + } + } + return word; + } + /// extract command + if (ch == '[') + { + while (ch != ']' && in.good()) + { + ch = in.get(); + word += ch; + } + return word; + } + /// extract marker + if (ch == '(' || ch == ')') + { + return word; + } + /// extract HEX + if (isxdigit(ch)) + { + while (isxdigit(ch) && in.good()) + { + ch = in.get(); + if (isxdigit(ch)) + word += ch; + } + return word; + } + /// extract generic word + while (!isspace(ch) && in.good()) + { + ch = in.get(); + if (!isspace(ch)) + word += ch; + } + return word; +} + +std::string ExtractString(std::string str) +{ + std::string ret; + size_t pos = str.find_first_of("\""); + if (pos == std::string::npos || pos + 1 >= str.length()) + { + return ""; + } + ret = str.substr(pos + 1); + pos = ret.find_last_of("\""); + if (pos == std::string::npos || pos == 0) + { + return ""; + } + ret = ret.substr(0, pos); + return ret; +} + +std::string ModScript::ParseScript(std::string ScriptData, unsigned* ScriptMemSizeRef) +{ + std::ostringstream ScriptHEX; + std::istringstream WorkingData(ScriptData); + std::map Labels; + std::stack MarkerLabels; + unsigned ScriptMemSize = 0, MemSize = 0; + bool needSecondPass = false; + unsigned numPasses = 0; + do + { + needSecondPass = false; + if (numPasses > 10) + { + *ErrorMessages << "Infinite loop detected (numPasses > 10)!" << std::endl; + SetBad(); + return std::string(""); + } + if (numPasses != 0) /// no need to parse twice + { + WorkingData.str(ScriptHEX.str()); + WorkingData.clear(); + ScriptHEX.str(""); + ScriptHEX.clear(); + if (MarkerLabels.size() != 0) + { + *ErrorMessages << "Unresolved marker(s) found!" << std::endl; + SetBad(); + return std::string(""); + } + } + while (!WorkingData.eof()) + { + std::string NextWord; + NextWord = GetWord(WorkingData); + if (NextWord == "") + { + /// skip empty lines + } + else if (IsHEX(NextWord)) + { + ScriptHEX << NextWord << " "; + if (numPasses == 0) + ScriptMemSize += 1; + } + else if (IsToken(NextWord)) + { + ScriptHEX << TokenToHEX(NextWord, &MemSize); + if (numPasses == 0) + ScriptMemSize += MemSize; + if (!ScriptState.Good) + { + *ErrorMessages << "Bad token: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + } + else if (IsCommand(NextWord)) + { + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "You can't use code commands outside Object scope: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + std::string Command = NextWord.substr(1, NextWord.length()-2); /// remove [] + Command = EatWhite(Command); /// remove white-spaces + if (Command[0] == '@') /// label reference + { + if (Command.length() == 1) /// [@] - auto-calculate memory size + { + if (numPasses == 0) /// first pass - create label + { + std::string autoLabel = "__automemsize__" + FormatHEX(ScriptMemSize); + if (Labels.count(autoLabel) != 0) + { + *ErrorMessages << "Internal error! Duplicated auto-label: " << autoLabel << std::endl; + SetBad(); + return std::string(""); + } + Labels[autoLabel] = 0; /// init new label + MarkerLabels.push(autoLabel); /// keep track of unresolved labels + needSecondPass = true; /// request second pass + ScriptHEX << "[@" << autoLabel << "] "; /// put auto-generated label back into the stream + } + else + { + *ErrorMessages << "Internal error! Unresolved command: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + } + else /// [@label_name] - resolve named labels + { + if (Labels.count(Command.substr(1)) != 0) /// found reference + { + uint16_t LabelPos = Labels[Command.substr(1)]; + ScriptHEX << MakeTextBlock(reinterpret_cast(&LabelPos), 2); + } + else + { + ScriptHEX << NextWord << " "; + needSecondPass = true; + if (numPasses != 0 && MarkerLabels.size() == 0) + { + *ErrorMessages << "Unresolved reference: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + } + } + if (numPasses == 0) + ScriptMemSize += 2; + } + else if (Command[0] == '#') /// label mark + { + if (numPasses == 0 && Labels.count(Command.substr(1)) != 0) + { + *ErrorMessages << "Multiple labels: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + if (numPasses == 0) + Labels[Command.substr(1)] = ScriptMemSize; + } + } + else if(IsMarker(NextWord)) /// resolve memory size markers '(' and ')' + { + if (numPasses != 0) /// should not be here at the second pass + { + *ErrorMessages << "Internal error! Unresolved marker: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + if (NextWord == "(") /// start position + { + if (MarkerLabels.size() == 0 || Labels[MarkerLabels.top()] != 0) + { + *ErrorMessages << "Bad marker: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + Labels[MarkerLabels.top()] = ScriptMemSize; + } + else /// end position + { + if (MarkerLabels.size() == 0 || Labels[MarkerLabels.top()] == 0) + { + *ErrorMessages << "Bad marker: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + Labels[MarkerLabels.top()] = ScriptMemSize - Labels[MarkerLabels.top()]; + MarkerLabels.pop(); /// close the current marker + } + } + else + { + *ErrorMessages << "Bad token: " << NextWord << std::endl; + SetBad(); + return std::string(""); + } + } + ++numPasses; + } while (needSecondPass); + if (ScriptMemSizeRef != nullptr) + { + (*ScriptMemSizeRef) = ScriptMemSize; + } + return ScriptHEX.str(); +} + +bool ModScript::IsHEX(std::string word) +{ + if (word.length() != 2) + return false; + return (isxdigit(word.front()) && isxdigit(word.back())); +} + +bool ModScript::IsToken(std::string word) +{ + if (word.length() < 3) + return false; + return (word.front() == '<' && word.back() == '>'); +} + +bool ModScript::IsCommand(std::string word) +{ + if (word.length() < 3) + return false; + return (word.front() == '[' && word.back() == ']'); +} + +bool ModScript::IsMarker(std::string word) +{ + if (word.length() != 1) /// one symbol '(' or ')' + return false; + return (word.front() == '(' || word.back() == ')'); +} + +std::string ModScript::TokenToHEX(std::string Token, unsigned* MemSizeRef) +{ + std::string Code = Token.substr(1, Token.length()-2); /// remove <> + Code = EatWhite(Code, '\"'); /// remove white-spaces + unsigned MemSize = 1; + std::vector dataChunk; + if (Code[0] == '!') /// parse alias + { + std::string Name = Code.substr(1); + if (ScriptState.Scope == UPKScope::Object) + { + Name = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName + '.' + Name; + if (Alias.count(Name) == 0) + { + Name = Code.substr(1); + } + } + if (Alias.count(Name) == 0) + { + *ErrorMessages << "Alias does not exist: " << Name << std::endl; + SetBad(); + return std::string(""); + } + std::string replacement = Alias[Name]; + std::string parsed = ParseScript(replacement, &MemSize); + dataChunk = GetDataChunk(parsed); + } + else if (Code[0] == '%') + { + if (Code[1] == 'f') + { + float FloatVal = GetFloatValue(Code.substr(2)); + dataChunk.resize(4); + memcpy(dataChunk.data(), reinterpret_cast(&FloatVal), 4); + MemSize = 4; + } + else if (Code[1] == 'i') + { + int IntVal = GetIntValue(Code.substr(2)); + dataChunk.resize(4); + memcpy(dataChunk.data(), reinterpret_cast(&IntVal), 4); + MemSize = 4; + } + else if (Code[1] == 'u') + { + unsigned UnsignedVal = GetUnsignedValue(Code.substr(2)); + dataChunk.resize(4); + memcpy(dataChunk.data(), reinterpret_cast(&UnsignedVal), 4); + MemSize = 4; + } + else if (Code[1] == 's') + { + unsigned UnsignedVal = GetUnsignedValue(Code.substr(2)); + if (UnsignedVal > 0xFFFF) + { + *ErrorMessages << "Incorrect short value: " << UnsignedVal << std::endl; + SetBad(); + return std::string(""); + } + uint16_t ShortVal = (uint16_t)UnsignedVal; + dataChunk.resize(2); + memcpy(dataChunk.data(), reinterpret_cast(&ShortVal), 2); + MemSize = 2; + } + else if (Code[1] == 'b') + { + unsigned UnsignedVal = GetUnsignedValue(Code.substr(2)); + if (UnsignedVal > 0xFF) + { + *ErrorMessages << "Incorrect byte value: " << UnsignedVal << std::endl; + SetBad(); + return std::string(""); + } + uint8_t ByteVal = (uint8_t)UnsignedVal; + dataChunk.resize(1); + memcpy(dataChunk.data(), reinterpret_cast(&ByteVal), 1); + MemSize = 1; + } + else if (Code[1] == 't') + { + std::string strVal = ExtractString(Code); + dataChunk.resize(strVal.length()+1); + memcpy(dataChunk.data(), strVal.c_str(), strVal.length()+1); + MemSize = strVal.length()+1; + } + else + { + *ErrorMessages << "Bad token: " << Code << std::endl; + SetBad(); + return std::string(""); + } + } + else if (Code[0] == '@') /// member variable reference + { + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "Can't use member variable references outside Object scope: " << Code << std::endl; + SetBad(); + return std::string(""); + } + std::string ObjName = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName; + std::string ClassName = ObjName.substr(0, ObjName.find('.')); + std::string VarName = ClassName + '.' + Code.substr(1); + UObjectReference ObjRef = ScriptState.Package.FindObject(VarName, true); + if (ObjRef == 0) + { + *ErrorMessages << "Bad object name: " << VarName << std::endl; + SetBad(); + return std::string(""); + } + dataChunk.resize(4); + memcpy(dataChunk.data(), reinterpret_cast(&ObjRef), 4); + MemSize = 8; + } + else if (Code.find(".") != std::string::npos) + { + std::string ObjName = Code; + if (Code.front() == '.') /// local var reference + { + if (ScriptState.Scope != UPKScope::Object) + { + *ErrorMessages << "You can't use local references outside Object scope: " << Code << std::endl; + SetBad(); + return std::string(""); + } + else + { + ObjName = ScriptState.Package.GetExportEntry(ScriptState.ObjIdx).FullName + Code; + } + } + else if (Code.find("Class.") == 0) /// class reference + { + ObjName = Code.substr(6); + } + UObjectReference ObjRef = ScriptState.Package.FindObject(ObjName, false); + if (ObjRef == 0) + { + *ErrorMessages << "Bad object name: " << ObjName << std::endl; + SetBad(); + return std::string(""); + } + dataChunk.resize(4); + memcpy(dataChunk.data(), reinterpret_cast(&ObjRef), 4); + MemSize = 8; + } + else if (Code == "NullRef") /// null object + { + dataChunk.resize(4, 0); + MemSize = 8; + } + else /// Name reference + { + std::string Name = Code; + int num = 0; + size_t pos = Code.rfind('_'); + if (pos != std::string::npos && isdigit(Code[pos + 1])) + { + Name = Code.substr(0, pos); + num = 1 + GetIntValue(Code.substr(pos + 1)); + } + int idx = ScriptState.Package.FindName(Name); + if (idx < 0) + { + *ErrorMessages << "Bad name: " << Name << std::endl; + SetBad(); + return std::string(""); + } + UNameIndex NameIdx; + NameIdx.NameTableIdx = idx; + NameIdx.Numeric = num; + dataChunk.resize(8); + memcpy(dataChunk.data(), reinterpret_cast(&NameIdx), 8); + MemSize = 8; + } + if (MemSizeRef != nullptr) + { + (*MemSizeRef) = MemSize; + } + return FormatHEX(dataChunk); +} diff --git a/ModScript.h b/ModScript.h index 2f92632..34251c3 100644 --- a/ModScript.h +++ b/ModScript.h @@ -1,152 +1,152 @@ -#ifndef MODSCRIPT_H -#define MODSCRIPT_H - -#include -#include - -#include "ModParser.h" -#include "UPKUtils.h" - -enum class UPKScope -{ - Package = 0, - Name = 1, - Import = 2, - Export = 3, - Object = 4 -}; -std::string FormatUPKScope(UPKScope scope); - -class ModScript -{ -public: - ModScript(): UPKPath(".") { InitStreams(); SetBad(); } - ~ModScript() {}; - ModScript(const char* filename): UPKPath(".") { InitStreams(); Parse(filename); } - ModScript(const char* filename, const char* pathname) { InitStreams(); Parse(filename); SetUPKPath(pathname); } - /// Init stream objects - void InitStreams(std::ostream& err = std::cerr, std::ostream& res = std::cout); - /// parse mod file to build execution stack - bool Parse(const char* filename); - /// set path to upk files, referenced inside mod files - void SetUPKPath(const char* pathname); - /// execute script - bool ExecuteStack(); - /// state - std::string GetBackupScript(); - bool IsGood() { return ScriptState.Good; } -protected: - typedef bool (ModScript::*ExecFunction)(const std::string&); - struct ScriptCommand - { - std::string Name; - std::string Param; - ExecFunction Exec; - }; - ModParser Parser; - std::string UPKPath; - std::map Executors; - std::vector ExecutionStack; - std::ostream *ErrorMessages; - std::ostream *ExecutionResults; - std::map BackupScript; - std::multimap GUIDs; - std::vector UPKNames; - std::map Alias; - void SetExecutors(); /// map names to keys/sections and functions - struct - { - bool UpdateRelOffset; - bool IsUninstallAllowed; - } ScriptFlags; - void ResetScriptFlags() { ScriptFlags.UpdateRelOffset = false; ScriptFlags.IsUninstallAllowed = true; } - struct - { - std::string UPKName; - UPKUtils Package; - UPKScope Scope; - uint32_t ObjIdx; - size_t Offset; - size_t RelOffset; - size_t MaxOffset; - std::string Behavior; - bool Good; - bool BeforeUsed; - unsigned BeforeMemSize; - } ScriptState; - void ResetScope() { ScriptState.Scope = UPKScope::Package; ScriptState.ObjIdx = 0; ScriptState.Offset = 0; ScriptState.RelOffset = 0; ScriptState.MaxOffset = 0; ScriptState.Behavior = "KEEP"; ScriptState.BeforeUsed = false; ScriptState.BeforeMemSize = 0; } - bool SetBad() { return (ScriptState.Good = false); } - bool SetGood() { return (ScriptState.Good = true); } - void AddUPKName(std::string upkname); - /// methods to implement mod file commands - bool SetUpdateRelOffset(const std::string& Param); - bool SetUninstallAllowed(const std::string& Param); - bool FormatModName(const std::string& Param); - bool FormatAuthor(const std::string& Param); - bool FormatDescription(const std::string& Param); - bool OpenPackage(const std::string& Param); - bool SetGUID(const std::string& Param); - bool SetGlobalOffset(const std::string& Param); - bool SetObject(const std::string& Param); - bool SetNameEntry(const std::string& Param); - bool SetImportEntry(const std::string& Param); - bool SetExportEntry(const std::string& Param); - bool SetRelOffset(const std::string& Param); - bool ResizeExportObject(const std::string& Param); - bool WriteReplaceAllHEX(const std::string& Param); - bool WriteReplaceAllCode(const std::string& Param); - bool WriteBulkData(const std::string& Param); - bool WriteBulkFile(const std::string& Param); - bool WriteModdedHEX(const std::string& Param); - bool WriteModdedCode(const std::string& Param); - bool WriteReplacementCode(const std::string& Param); - bool WriteInsertCode(const std::string& Param); - bool WriteUndoMoveResize(const std::string& Param); - bool WriteModdedFile(const std::string& Param); - bool WriteByteValue(const std::string& Param); - bool WriteFloatValue(const std::string& Param); - bool WriteIntValue(const std::string& Param); - bool WriteUnsignedValue(const std::string& Param); - bool WriteRename(const std::string& Param); - bool SetDataChunkOffset(const std::string& Param); - bool SetCodeOffset(const std::string& Param); - bool WriteMoveExpandLegacy(const std::string& Param); - bool WriteNameIdx(const std::string& Param); - bool WriteObjectIdx(const std::string& Param); - bool AddAlias(const std::string& Param); - /// before-after - bool SetBeforeHEXOffset(const std::string& Param); - bool WriteAfterHEX(const std::string& Param); - bool SetBeforeCodeOffset(const std::string& Param); - bool WriteAfterCode(const std::string& Param); - bool WriteReplaceAll(const std::string& Param, bool isCode); - /// end-of-block indicators are just skipped, as they don't actually used - bool Sink(const std::string& Param); - /// adding new entries - bool WriteAddNameEntry(const std::string& Param); - bool WriteAddImportEntry(const std::string& Param); - bool WriteAddExportEntry(const std::string& Param); - /// helpers - bool CheckBehavior(); - bool IsInsideScope(size_t DataSize = 1); - bool SetDataOffset(const std::string& Param, bool isEnd, bool isBeforeData); - bool CheckMoveResize(size_t DataSize, bool FitScope = false); - bool DoResize(int ObjSize); - bool MoveResizeAtRelOffset(int ObjSize); - bool ResizeInPlace(int ObjSize); - bool WriteBinaryData(const std::vector& DataChunk); - bool WriteModdedData(const std::vector& DataChunk, bool FitScope = false); - bool WriteAfterData(const std::string& DataBlock, int MemSize = -1); - bool GetAdjustedSizes(std::vector& SizesChunk, size_t& SizesRelOffset, int DataSize, int NewMemSize = -1); - size_t GetDiff(size_t DataSize); - void ResetMaxOffset(); - /// parse script - std::string ParseScript(std::string ScriptData, unsigned* ScriptMemSizeRef = nullptr); - bool IsHEX(std::string word); - bool IsToken(std::string word); - bool IsCommand(std::string word); - bool IsMarker(std::string word); - std::string TokenToHEX(std::string Token, unsigned* MemSizeRef = nullptr); -}; - -#endif // MODSCRIPT_H +#ifndef MODSCRIPT_H +#define MODSCRIPT_H + +#include +#include + +#include "ModParser.h" +#include "UPKUtils.h" + +enum class UPKScope +{ + Package = 0, + Name = 1, + Import = 2, + Export = 3, + Object = 4 +}; +std::string FormatUPKScope(UPKScope scope); + +class ModScript +{ +public: + ModScript(): UPKPath(".") { InitStreams(); SetBad(); } + ~ModScript() {}; + ModScript(const char* filename): UPKPath(".") { InitStreams(); Parse(filename); } + ModScript(const char* filename, const char* pathname) { InitStreams(); Parse(filename); SetUPKPath(pathname); } + /// Init stream objects + void InitStreams(std::ostream& err = std::cerr, std::ostream& res = std::cout); + /// parse mod file to build execution stack + bool Parse(const char* filename); + /// set path to upk files, referenced inside mod files + void SetUPKPath(const char* pathname); + /// execute script + bool ExecuteStack(); + /// state + std::string GetBackupScript(); + bool IsGood() { return ScriptState.Good; } +protected: + typedef bool (ModScript::*ExecFunction)(const std::string&); + struct ScriptCommand + { + std::string Name; + std::string Param; + ExecFunction Exec; + }; + ModParser Parser; + std::string UPKPath; + std::map Executors; + std::vector ExecutionStack; + std::ostream *ErrorMessages; + std::ostream *ExecutionResults; + std::map BackupScript; + std::multimap GUIDs; + std::vector UPKNames; + std::map Alias; + void SetExecutors(); /// map names to keys/sections and functions + struct + { + bool UpdateRelOffset; + bool IsUninstallAllowed; + } ScriptFlags; + void ResetScriptFlags() { ScriptFlags.UpdateRelOffset = false; ScriptFlags.IsUninstallAllowed = true; } + struct + { + std::string UPKName; + UPKUtils Package; + UPKScope Scope; + uint32_t ObjIdx; + size_t Offset; + size_t RelOffset; + size_t MaxOffset; + std::string Behavior; + bool Good; + bool BeforeUsed; + unsigned BeforeMemSize; + } ScriptState; + void ResetScope() { ScriptState.Scope = UPKScope::Package; ScriptState.ObjIdx = 0; ScriptState.Offset = 0; ScriptState.RelOffset = 0; ScriptState.MaxOffset = 0; ScriptState.Behavior = "KEEP"; ScriptState.BeforeUsed = false; ScriptState.BeforeMemSize = 0; } + bool SetBad() { return (ScriptState.Good = false); } + bool SetGood() { return (ScriptState.Good = true); } + void AddUPKName(std::string upkname); + /// methods to implement mod file commands + bool SetUpdateRelOffset(const std::string& Param); + bool SetUninstallAllowed(const std::string& Param); + bool FormatModName(const std::string& Param); + bool FormatAuthor(const std::string& Param); + bool FormatDescription(const std::string& Param); + bool OpenPackage(const std::string& Param); + bool SetGUID(const std::string& Param); + bool SetGlobalOffset(const std::string& Param); + bool SetObject(const std::string& Param); + bool SetNameEntry(const std::string& Param); + bool SetImportEntry(const std::string& Param); + bool SetExportEntry(const std::string& Param); + bool SetRelOffset(const std::string& Param); + bool ResizeExportObject(const std::string& Param); + bool WriteReplaceAllHEX(const std::string& Param); + bool WriteReplaceAllCode(const std::string& Param); + bool WriteBulkData(const std::string& Param); + bool WriteBulkFile(const std::string& Param); + bool WriteModdedHEX(const std::string& Param); + bool WriteModdedCode(const std::string& Param); + bool WriteReplacementCode(const std::string& Param); + bool WriteInsertCode(const std::string& Param); + bool WriteUndoMoveResize(const std::string& Param); + bool WriteModdedFile(const std::string& Param); + bool WriteByteValue(const std::string& Param); + bool WriteFloatValue(const std::string& Param); + bool WriteIntValue(const std::string& Param); + bool WriteUnsignedValue(const std::string& Param); + bool WriteRename(const std::string& Param); + bool SetDataChunkOffset(const std::string& Param); + bool SetCodeOffset(const std::string& Param); + bool WriteMoveExpandLegacy(const std::string& Param); + bool WriteNameIdx(const std::string& Param); + bool WriteObjectIdx(const std::string& Param); + bool AddAlias(const std::string& Param); + /// before-after + bool SetBeforeHEXOffset(const std::string& Param); + bool WriteAfterHEX(const std::string& Param); + bool SetBeforeCodeOffset(const std::string& Param); + bool WriteAfterCode(const std::string& Param); + bool WriteReplaceAll(const std::string& Param, bool isCode); + /// end-of-block indicators are just skipped, as they don't actually used + bool Sink(const std::string& Param); + /// adding new entries + bool WriteAddNameEntry(const std::string& Param); + bool WriteAddImportEntry(const std::string& Param); + bool WriteAddExportEntry(const std::string& Param); + /// helpers + bool CheckBehavior(); + bool IsInsideScope(size_t DataSize = 1); + bool SetDataOffset(const std::string& Param, bool isEnd, bool isBeforeData); + bool CheckMoveResize(size_t DataSize, bool FitScope = false); + bool DoResize(int ObjSize); + bool MoveResizeAtRelOffset(int ObjSize); + bool ResizeInPlace(int ObjSize); + bool WriteBinaryData(const std::vector& DataChunk); + bool WriteModdedData(const std::vector& DataChunk, bool FitScope = false); + bool WriteAfterData(const std::string& DataBlock, int MemSize = -1); + bool GetAdjustedSizes(std::vector& SizesChunk, size_t& SizesRelOffset, int DataSize, int NewMemSize = -1); + size_t GetDiff(size_t DataSize); + void ResetMaxOffset(); + /// parse script + std::string ParseScript(std::string ScriptData, unsigned* ScriptMemSizeRef = nullptr); + bool IsHEX(std::string word); + bool IsToken(std::string word); + bool IsCommand(std::string word); + bool IsMarker(std::string word); + std::string TokenToHEX(std::string Token, unsigned* MemSizeRef = nullptr); +}; + +#endif // MODSCRIPT_H diff --git a/MoveExpandFunction.cpp b/MoveExpandFunction.cpp index cc06166..73056fd 100644 --- a/MoveExpandFunction.cpp +++ b/MoveExpandFunction.cpp @@ -1,81 +1,81 @@ -#include -#include -#include - -#include "UPKUtils.h" - -using namespace std; - -int main(int argN, char* argV[]) -{ - cout << "MoveExpandFunction" << endl; - - if (argN < 3 || argN > 4) - { - cerr << "Usage: MoveExpandFunction UnpackedResourceFile.upk FunctionName [NewFunctionSize or /u]" << endl; - return 1; - } - - UPKUtils package(argV[1]); - - string NameToFind = argV[2]; - - cout << "Name to find: " << NameToFind << endl; - - UObjectReference ObjRef = package.FindObject(NameToFind, true); - - if (ObjRef == 0) - { - cerr << "Can't find object entry by name " << NameToFind << endl; - return 1; - } - - FObjectExport ExportEntry = package.GetExportEntry((uint32_t)ObjRef); - - cout << "Function size: " << ExportEntry.SerialSize << endl; - cout << "Function offset: " << FormatHEX(ExportEntry.SerialOffset) << endl; - - uint32_t newFunctionSize = 0; - bool bUndoMove = false; - - if (argN == 4) - { - if (strcmp(argV[3], "/u") == 0) - { - bUndoMove = true; - } - else - { - string str(argV[3]); - - istringstream ss(str); - - if (str.find("0x") != string::npos) - ss >> hex >> newFunctionSize; - else - ss >> dec >> newFunctionSize; - - cout << "Resize function to: " << newFunctionSize << endl; - - if (newFunctionSize <= ExportEntry.SerialSize) - { - cerr << "Can't expand function: existing function size is greater than specified value!" << endl; - return 1; - } - } - } - - if (!bUndoMove) - { - package.MoveExportData((uint32_t)ObjRef, newFunctionSize); - cout << "Object moved successfully!" << endl; - } - else - { - package.UndoMoveExportData((uint32_t)ObjRef); - cout << "Object restored successfully!" << endl; - } - - return 0; -} - +#include +#include +#include + +#include "UPKUtils.h" + +using namespace std; + +int main(int argN, char* argV[]) +{ + cout << "MoveExpandFunction" << endl; + + if (argN < 3 || argN > 4) + { + cerr << "Usage: MoveExpandFunction UnpackedResourceFile.upk FunctionName [NewFunctionSize or /u]" << endl; + return 1; + } + + UPKUtils package(argV[1]); + + string NameToFind = argV[2]; + + cout << "Name to find: " << NameToFind << endl; + + UObjectReference ObjRef = package.FindObject(NameToFind, true); + + if (ObjRef == 0) + { + cerr << "Can't find object entry by name " << NameToFind << endl; + return 1; + } + + FObjectExport ExportEntry = package.GetExportEntry((uint32_t)ObjRef); + + cout << "Function size: " << ExportEntry.SerialSize << endl; + cout << "Function offset: " << FormatHEX(ExportEntry.SerialOffset) << endl; + + uint32_t newFunctionSize = 0; + bool bUndoMove = false; + + if (argN == 4) + { + if (strcmp(argV[3], "/u") == 0) + { + bUndoMove = true; + } + else + { + string str(argV[3]); + + istringstream ss(str); + + if (str.find("0x") != string::npos) + ss >> hex >> newFunctionSize; + else + ss >> dec >> newFunctionSize; + + cout << "Resize function to: " << newFunctionSize << endl; + + if (newFunctionSize <= ExportEntry.SerialSize) + { + cerr << "Can't expand function: existing function size is greater than specified value!" << endl; + return 1; + } + } + } + + if (!bUndoMove) + { + package.MoveExportData((uint32_t)ObjRef, newFunctionSize); + cout << "Object moved successfully!" << endl; + } + else + { + package.UndoMoveExportData((uint32_t)ObjRef); + cout << "Object restored successfully!" << endl; + } + + return 0; +} + diff --git a/PatchUPK.cpp b/PatchUPK.cpp index d699425..7e28cbf 100644 --- a/PatchUPK.cpp +++ b/PatchUPK.cpp @@ -1,84 +1,84 @@ -#include -#include -#include - -#include "ModScript.h" - -using namespace std; - -bool FileExists(string str) -{ - ifstream in(str); - return in.good(); -} - -string int2fstr(int val) -{ - if (val <= 0) - return string(""); - ostringstream ss; - ss << dec << val; - return ss.str(); -} - -int main(int argN, char* argV[]) -{ - cout << "PatchUPK" << endl; - - if (argN < 2 || argN > 3) - { - cerr << "Usage: PatchUPK modfile.txt [PATH_TO_UPK]" << endl; - return 1; - } - - string upkPath = ""; - - if (argN == 3) - { - upkPath = argV[2]; - if (upkPath.length() < 1) - { - cerr << "Incorrect package path!" << endl; - return 1; - } - } - - ModScript script(argV[1], upkPath.c_str()); - script.InitStreams(std::cerr, std::cout); - if (script.IsGood() == false) - { - return 1; - } - - bool ExecResult = script.ExecuteStack(); - - string backupScript = script.GetBackupScript(); - - if (string(argV[1]).find(".uninstall") == string::npos && backupScript != "") - { - unsigned i = 0; - string nextName = ""; - do - { - nextName = string(argV[1]) + string(".uninstall") + int2fstr(i) + string(".txt"); - ++i; - } while (FileExists(nextName)); - - ofstream uninstFile(nextName); - if (!uninstFile.good()) - { - cerr << "Error saving uninstall script!" << endl; - return 1; - } - uninstFile << "MOD_NAME=" << argV[1] << " uninstall script\n" - << "AUTHOR=PatchUPK\n" - << "DESCRIPTION=This is automatically generated uninstall script. Do not change anything!\n\n" - << backupScript << "\n{ backup script end }\n"; - cout << "Uninstall script saved to " << nextName << endl; - } - - if (!ExecResult) - return 1; - - return 0; -} +#include +#include +#include + +#include "ModScript.h" + +using namespace std; + +bool FileExists(string str) +{ + ifstream in(str); + return in.good(); +} + +string int2fstr(int val) +{ + if (val <= 0) + return string(""); + ostringstream ss; + ss << dec << val; + return ss.str(); +} + +int main(int argN, char* argV[]) +{ + cout << "PatchUPK" << endl; + + if (argN < 2 || argN > 3) + { + cerr << "Usage: PatchUPK modfile.txt [PATH_TO_UPK]" << endl; + return 1; + } + + string upkPath = ""; + + if (argN == 3) + { + upkPath = argV[2]; + if (upkPath.length() < 1) + { + cerr << "Incorrect package path!" << endl; + return 1; + } + } + + ModScript script(argV[1], upkPath.c_str()); + script.InitStreams(std::cerr, std::cout); + if (script.IsGood() == false) + { + return 1; + } + + bool ExecResult = script.ExecuteStack(); + + string backupScript = script.GetBackupScript(); + + if (string(argV[1]).find(".uninstall") == string::npos && backupScript != "") + { + unsigned i = 0; + string nextName = ""; + do + { + nextName = string(argV[1]) + string(".uninstall") + int2fstr(i) + string(".txt"); + ++i; + } while (FileExists(nextName)); + + ofstream uninstFile(nextName); + if (!uninstFile.good()) + { + cerr << "Error saving uninstall script!" << endl; + return 1; + } + uninstFile << "MOD_NAME=" << argV[1] << " uninstall script\n" + << "AUTHOR=PatchUPK\n" + << "DESCRIPTION=This is automatically generated uninstall script. Do not change anything!\n\n" + << backupScript << "\n{ backup script end }\n"; + cout << "Uninstall script saved to " << nextName << endl; + } + + if (!ExecResult) + return 1; + + return 0; +} diff --git a/UFlags.h b/UFlags.h index 97daaaa..844ce38 100644 --- a/UFlags.h +++ b/UFlags.h @@ -1,210 +1,210 @@ -#ifndef UFLAGS_H_INCLUDED -#define UFLAGS_H_INCLUDED - -/// flags -enum class UPackageFlags: uint32_t -{ - AllowDownload = 0x00000001, - ClientOptional = 0x00000002, - ServerSideOnly = 0x00000004, - BrokenLinks = 0x00000008, - Cooked = BrokenLinks, - Unsecure = 0x00000010, - Encrypted = 0x00000020, - Need = 0x00008000, - Map = 0x00020000, - Script = 0x00200000, - Debug = 0x00400000, - Imports = 0x00800000, - Compressed = 0x02000000, - FullyCompressed = 0x04000000, - NoExportsData = 0x20000000, - Stripped = 0x40000000, - Protected = 0x80000000 -}; - -enum class UCompressionFlags: uint32_t -{ - ZLIB = 0x00000001, - LZO = 0x00000002, - LZX = 0x00000004 -}; - -enum class UObjectFlagsL: uint32_t -{ - Transactional = 0x00000001, - Unreachable = 0x00000002, - Public = 0x00000004, - Private = 0x00000080, - Automated = 0x00000100, - Transient = 0x00004000, - Preloading = 0x00008000, - LoadForClient = 0x00010000, - LoadForServer = 0x00020000, - LoadForEdit = 0x00040000, - Standalone = 0x00080000, - NotForClient = 0x00100000, - NotForServer = 0x00200000, - NotForEdit = 0x00400000, - NeedPostLoad = 0x01000000, - HasStack = 0x02000000, - Native = 0x04000000, - Marked = 0x08000000 -}; - -enum class UObjectFlagsH: uint32_t -{ - Obsolete = 0x00000020, - Final = 0x00000080, - PerObjectLocalized = 0x00000100, - PropertiesObject = 0x00000200, - ArchetypeObject = 0x00000400, - RemappedName = 0x00000800 -}; - -enum class UExportFlags: uint32_t -{ - ForcedExport = 0x00000001 -}; - -enum class UFunctionFlags: uint32_t -{ - Final = 0x00000001, - Defined = 0x00000002, - Iterator = 0x00000004, - Latent = 0x00000008, - PreOperator = 0x00000010, - Singular = 0x00000020, - Net = 0x00000040, - NetReliable = 0x00000080, - Simulated = 0x00000100, - Exec = 0x00000200, - Native = 0x00000400, - Event = 0x00000800, - Operator = 0x00001000, - Static = 0x00002000, - NoExport = 0x00004000, - OptionalParameters = NoExport, - Const = 0x00008000, - Invariant = 0x00010000, - Public = 0x00020000, - Private = 0x00040000, - Protected = 0x00080000, - Delegate = 0x00100000, - NetServer = 0x00200000, - NetClient = 0x01000000, - DLLImport = 0x02000000 -}; - -enum class UStructFlags: uint32_t -{ - Native = 0x00000001, - Export = 0x00000002, - Long = 0x00000004, - HasComponents = Long, - Init = 0x00000008, - Transient = Init, - Atomic = 0x00000010, - Immutable = 0x00000020, - StrictConfig = 0x00000040, - ImmutableWhenCooked = 0x00000080, - AtomicWhenCooked = 0x00000100 -}; - -enum class UClassFlags: uint32_t -{ - Abstract = 0x00000001, - Compiled = 0x00000002, - Config = 0x00000004, - Transient = 0x00000008, - Parsed = 0x00000010, - Localized = 0x00000020, - SafeReplace = 0x00000040, - RuntimeStatic = 0x00000080, - NoExport = 0x00000100, - Placeable = 0x00000200, - PerObjectConfig = 0x00000400, - NativeReplication = 0x00000800, - EditInlineNew = 0x00001000, - CollapseCategories = 0x00002000, - ExportStructs = 0x00004000, - Instanced = 0x00200000, - HideDropDown = 0x00400000, - HasComponents = HideDropDown, - CacheExempt = 0x00800000, - Hidden = CacheExempt, - ParseConfig = 0x01000000, - Deprecated = ParseConfig, - HideDropDown2 = 0x02000000, - Exported = 0x04000000, - NativeOnly = 0x20000000 -}; - -enum class UStateFlags: uint32_t -{ - Editable = 0x00000001, - Auto = 0x00000002, - Simulated = 0x00000004 -}; - -enum class UPropertyFlagsL: uint32_t -{ - Editable = 0x00000001, - Const = 0x00000002, - Input = 0x00000004, - ExportObject = 0x00000008, - OptionalParm = 0x00000010, - Net = 0x00000020, - EditConstArray = 0x00000040, - EditFixedSize = EditConstArray, - Parm = 0x00000080, - OutParm = 0x00000100, - SkipParm = 0x00000200, - ReturnParm = 0x00000400, - CoerceParm = 0x00000800, - Native = 0x00001000, - Transient = 0x00002000, - Config = 0x00004000, - Localized = 0x00008000, - Travel = 0x00010000, - EditConst = 0x00020000, - GlobalConfig = 0x00040000, - Component = 0x00080000, - OnDemand = 0x00100000, - Init = OnDemand, - New = 0x00200000, - DuplicateTransient = New, - NeedCtorLink = 0x00400000, - NoExport = 0x00800000, - Cache = 0x01000000, - NoImport = Cache, - EditorData = 0x02000000, - NoClear = EditorData, - EditInline = 0x04000000, - EdFindable = 0x08000000, - EditInlineUse = 0x10000000, - Deprecated = 0x20000000, - EditInlineNotify = 0x40000000, - DataBinding = EditInlineNotify, - SerializeText = 0x80000000, - Automated = SerializeText -}; - -enum class UPropertyFlagsH: uint32_t -{ - RepNotify = 0x00000001, - Interp = 0x00000002, - NonTransactional = 0x00000004, - EditorOnly = 0x00000008, - NotForConsole = 0x00000010, - RepRetry = 0x00000020, - PrivateWrite = 0x00000040, - ProtectedWrite = 0x00000080, - Archetype = 0x00000100, - EditHide = 0x00000200, - EditTextBox = 0x00000400, - CrossLevelPassive = 0x00001000, - CrossLevelActive = 0x00002000 -}; - -#endif // UFLAGS_H_INCLUDED +#ifndef UFLAGS_H_INCLUDED +#define UFLAGS_H_INCLUDED + +/// flags +enum class UPackageFlags: uint32_t +{ + AllowDownload = 0x00000001, + ClientOptional = 0x00000002, + ServerSideOnly = 0x00000004, + BrokenLinks = 0x00000008, + Cooked = BrokenLinks, + Unsecure = 0x00000010, + Encrypted = 0x00000020, + Need = 0x00008000, + Map = 0x00020000, + Script = 0x00200000, + Debug = 0x00400000, + Imports = 0x00800000, + Compressed = 0x02000000, + FullyCompressed = 0x04000000, + NoExportsData = 0x20000000, + Stripped = 0x40000000, + Protected = 0x80000000 +}; + +enum class UCompressionFlags: uint32_t +{ + ZLIB = 0x00000001, + LZO = 0x00000002, + LZX = 0x00000004 +}; + +enum class UObjectFlagsL: uint32_t +{ + Transactional = 0x00000001, + Unreachable = 0x00000002, + Public = 0x00000004, + Private = 0x00000080, + Automated = 0x00000100, + Transient = 0x00004000, + Preloading = 0x00008000, + LoadForClient = 0x00010000, + LoadForServer = 0x00020000, + LoadForEdit = 0x00040000, + Standalone = 0x00080000, + NotForClient = 0x00100000, + NotForServer = 0x00200000, + NotForEdit = 0x00400000, + NeedPostLoad = 0x01000000, + HasStack = 0x02000000, + Native = 0x04000000, + Marked = 0x08000000 +}; + +enum class UObjectFlagsH: uint32_t +{ + Obsolete = 0x00000020, + Final = 0x00000080, + PerObjectLocalized = 0x00000100, + PropertiesObject = 0x00000200, + ArchetypeObject = 0x00000400, + RemappedName = 0x00000800 +}; + +enum class UExportFlags: uint32_t +{ + ForcedExport = 0x00000001 +}; + +enum class UFunctionFlags: uint32_t +{ + Final = 0x00000001, + Defined = 0x00000002, + Iterator = 0x00000004, + Latent = 0x00000008, + PreOperator = 0x00000010, + Singular = 0x00000020, + Net = 0x00000040, + NetReliable = 0x00000080, + Simulated = 0x00000100, + Exec = 0x00000200, + Native = 0x00000400, + Event = 0x00000800, + Operator = 0x00001000, + Static = 0x00002000, + NoExport = 0x00004000, + OptionalParameters = NoExport, + Const = 0x00008000, + Invariant = 0x00010000, + Public = 0x00020000, + Private = 0x00040000, + Protected = 0x00080000, + Delegate = 0x00100000, + NetServer = 0x00200000, + NetClient = 0x01000000, + DLLImport = 0x02000000 +}; + +enum class UStructFlags: uint32_t +{ + Native = 0x00000001, + Export = 0x00000002, + Long = 0x00000004, + HasComponents = Long, + Init = 0x00000008, + Transient = Init, + Atomic = 0x00000010, + Immutable = 0x00000020, + StrictConfig = 0x00000040, + ImmutableWhenCooked = 0x00000080, + AtomicWhenCooked = 0x00000100 +}; + +enum class UClassFlags: uint32_t +{ + Abstract = 0x00000001, + Compiled = 0x00000002, + Config = 0x00000004, + Transient = 0x00000008, + Parsed = 0x00000010, + Localized = 0x00000020, + SafeReplace = 0x00000040, + RuntimeStatic = 0x00000080, + NoExport = 0x00000100, + Placeable = 0x00000200, + PerObjectConfig = 0x00000400, + NativeReplication = 0x00000800, + EditInlineNew = 0x00001000, + CollapseCategories = 0x00002000, + ExportStructs = 0x00004000, + Instanced = 0x00200000, + HideDropDown = 0x00400000, + HasComponents = HideDropDown, + CacheExempt = 0x00800000, + Hidden = CacheExempt, + ParseConfig = 0x01000000, + Deprecated = ParseConfig, + HideDropDown2 = 0x02000000, + Exported = 0x04000000, + NativeOnly = 0x20000000 +}; + +enum class UStateFlags: uint32_t +{ + Editable = 0x00000001, + Auto = 0x00000002, + Simulated = 0x00000004 +}; + +enum class UPropertyFlagsL: uint32_t +{ + Editable = 0x00000001, + Const = 0x00000002, + Input = 0x00000004, + ExportObject = 0x00000008, + OptionalParm = 0x00000010, + Net = 0x00000020, + EditConstArray = 0x00000040, + EditFixedSize = EditConstArray, + Parm = 0x00000080, + OutParm = 0x00000100, + SkipParm = 0x00000200, + ReturnParm = 0x00000400, + CoerceParm = 0x00000800, + Native = 0x00001000, + Transient = 0x00002000, + Config = 0x00004000, + Localized = 0x00008000, + Travel = 0x00010000, + EditConst = 0x00020000, + GlobalConfig = 0x00040000, + Component = 0x00080000, + OnDemand = 0x00100000, + Init = OnDemand, + New = 0x00200000, + DuplicateTransient = New, + NeedCtorLink = 0x00400000, + NoExport = 0x00800000, + Cache = 0x01000000, + NoImport = Cache, + EditorData = 0x02000000, + NoClear = EditorData, + EditInline = 0x04000000, + EdFindable = 0x08000000, + EditInlineUse = 0x10000000, + Deprecated = 0x20000000, + EditInlineNotify = 0x40000000, + DataBinding = EditInlineNotify, + SerializeText = 0x80000000, + Automated = SerializeText +}; + +enum class UPropertyFlagsH: uint32_t +{ + RepNotify = 0x00000001, + Interp = 0x00000002, + NonTransactional = 0x00000004, + EditorOnly = 0x00000008, + NotForConsole = 0x00000010, + RepRetry = 0x00000020, + PrivateWrite = 0x00000040, + ProtectedWrite = 0x00000080, + Archetype = 0x00000100, + EditHide = 0x00000200, + EditTextBox = 0x00000400, + CrossLevelPassive = 0x00001000, + CrossLevelActive = 0x00002000 +}; + +#endif // UFLAGS_H_INCLUDED diff --git a/UObject.cpp b/UObject.cpp index 75a1c01..2051249 100644 --- a/UObject.cpp +++ b/UObject.cpp @@ -1,913 +1,913 @@ -#include "UObject.h" - -#include - -void UBulkDataMirror::SetBulkData(std::vector Data) -{ - BulkData = Data; - SavedBulkDataFlags = 0; - /// temporarily, until we know the exact meaning of this - SavedElementCount = SavedBulkDataSizeOnDisk = BulkData.size(); - SavedBulkDataOffsetInFile = 0; -} - -std::string UBulkDataMirror::Serialize() -{ - std::stringstream ss; - ss.write(reinterpret_cast(&SavedBulkDataFlags), 4); - ss.write(reinterpret_cast(&SavedElementCount), 4); - ss.write(reinterpret_cast(&SavedBulkDataSizeOnDisk), 4); - ss.write(reinterpret_cast(&SavedBulkDataOffsetInFile), 4); - if (BulkData.size() > 0) - { - ss.write(BulkData.data(), BulkData.size()); - } - return ss.str(); -} - -std::string UDefaultPropertiesList::Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe, bool quick) -{ - std::ostringstream ss; - ss << "UDefaultPropertiesList:\n"; - PropertyOffset = stream.tellg(); - DefaultProperties.clear(); - size_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; - UDefaultProperty Property; - do - { - Property = UDefaultProperty{}; - ss << Property.Deserialize(stream, info, owner, unsafe, quick); - DefaultProperties.push_back(Property); - } while (Property.GetName() != "None" && stream.good() && (size_t)stream.tellg() < maxOffset); - PropertySize = (unsigned)stream.tellg() - (unsigned)PropertyOffset; - return ss.str(); -} - -std::string UDefaultProperty::Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe, bool quick) -{ - Init(owner, unsafe, quick); - size_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; - std::ostringstream ss; - ss << "UDefaultProperty:\n"; - stream.read(reinterpret_cast(&NameIdx), sizeof(NameIdx)); - Name = info.IndexToName(NameIdx); - ss << "\tNameIdx: " << FormatHEX(NameIdx) << " -> " << info.IndexToName(NameIdx) << std::endl; - if (Name != "None") - { - stream.read(reinterpret_cast(&TypeIdx), sizeof(TypeIdx)); - ss << "\tTypeIdx: " << FormatHEX(TypeIdx) << " -> " << info.IndexToName(TypeIdx) << std::endl; - stream.read(reinterpret_cast(&PropertySize), sizeof(PropertySize)); - ss << "\tPropertySize: " << FormatHEX(PropertySize) << std::endl; - /// prevent long loop, caused by bad data - if ((int)stream.tellg() + (int)PropertySize > (int)maxOffset) - return ss.str(); - stream.read(reinterpret_cast(&ArrayIdx), sizeof(ArrayIdx)); - ss << "\tArrayIdx: " << FormatHEX(ArrayIdx) << std::endl; - Type = info.IndexToName(TypeIdx); - if (Type == "BoolProperty") - { - stream.read(reinterpret_cast(&BoolValue), sizeof(BoolValue)); - ss << "\tBoolean value: " << FormatHEX(BoolValue) << " = "; - if (BoolValue == 0) - ss << "false\n"; - else - ss << "true\n"; - } - if (Type == "StructProperty" || Type == "ByteProperty") - { - stream.read(reinterpret_cast(&InnerNameIdx), sizeof(InnerNameIdx)); - ss << "\tInnerNameIdx: " << FormatHEX(InnerNameIdx) << " -> " << info.IndexToName(InnerNameIdx) << std::endl; - if (Type == "StructProperty") - Type = info.IndexToName(InnerNameIdx); - } - if (PropertySize > 0) - { - size_t offset = stream.tellg(); - InnerValue.resize(PropertySize); - stream.read(InnerValue.data(), InnerValue.size()); - size_t offset2 = stream.tellg(); - if (QuickMode == false) - { - stream.seekg(offset); - ss << DeserializeValue(stream, info); - } - stream.seekg(offset2); - } - } - return ss.str(); -} - -std::string UDefaultProperty::DeserializeValue(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - if (Type == "IntProperty") - { - int32_t value; - stream.read(reinterpret_cast(&value), sizeof(value)); - ss << "\tInteger: " << FormatHEX((uint32_t)value) << " = " << value << std::endl; - } - else if (Type == "FloatProperty") - { - float value; - stream.read(reinterpret_cast(&value), sizeof(value)); - ss << "\tFloat: " << FormatHEX(value) << " = " << value << std::endl; - } - else if (Type == "ObjectProperty" || - Type == "InterfaceProperty" || - Type == "ComponentProperty" || - Type == "ClassProperty") - { - UObjectReference value; - stream.read(reinterpret_cast(&value), sizeof(value)); - ss << "\tObject: " << FormatHEX((uint32_t)value) << " = "; - if (value == 0) - ss << "none\n"; - else - ss << info.ObjRefToName(value) << std::endl; - } - else if (Type == "NameProperty" || Type == "ByteProperty") - { - UNameIndex value; - stream.read(reinterpret_cast(&value), sizeof(value)); - ss << "\tName: " << FormatHEX(value) << " = " << info.IndexToName(value) << std::endl; - } - else if (Type == "StrProperty") - { - uint32_t StrLength; - stream.read(reinterpret_cast(&StrLength), sizeof(StrLength)); - ss << "\tStrLength = " << FormatHEX(StrLength) << " = " << StrLength << std::endl; - if (StrLength > 0) - { - std::string str; - getline(stream, str, '\0'); - ss << "\tString = " << str << std::endl; - } - } - else if (Type == "ArrayProperty") - { - uint32_t NumElements; - stream.read(reinterpret_cast(&NumElements), sizeof(NumElements)); - ss << "\tNumElements = " << FormatHEX(NumElements) << " = " << NumElements << std::endl; - if ((NumElements > 0) && (PropertySize > 4)) - { - std::string ArrayInnerType = FindArrayType(Name, stream, info); - /*if (TryUnsafe == true && ArrayInnerType == "None") - { - ArrayInnerType = GuessArrayType(Name); - if (ArrayInnerType != "None") - ss << "\tUnsafe type guess:\n"; - }*/ - ss << "\tArrayInnerType = " << ArrayInnerType << std::endl; - UDefaultProperty InnerProperty; - InnerProperty.OwnerRef = OwnerRef; - InnerProperty.TryUnsafe = TryUnsafe; - InnerProperty.Type = ArrayInnerType; - InnerProperty.PropertySize = PropertySize - 4; - if (ArrayInnerType == "None") - { - if (TryUnsafe == true && - (InnerProperty.PropertySize/NumElements > 24 || - InnerProperty.PropertySize/NumElements == 16 || - InnerProperty.PropertySize/NumElements == 8 || - InnerProperty.PropertySize/NumElements == 4)) - { - InnerProperty.PropertySize /= NumElements; - for (unsigned i = 0; i < NumElements; ++i) - { - ss << "\t" << Name << "[" << i << "]:\n"; - ss << InnerProperty.DeserializeValue(stream, info); - } - } - else - { - ss << InnerProperty.DeserializeValue(stream, info); - } - } - else - { - InnerProperty.PropertySize /= NumElements; - for (unsigned i = 0; i < NumElements; ++i) - { - ss << "\t" << Name << "[" << i << "]:\n"; - ss << InnerProperty.DeserializeValue(stream, info); - } - } - } - } - else if (Type == "Vector") - { - float X, Y, Z; - stream.read(reinterpret_cast(&X), sizeof(X)); - stream.read(reinterpret_cast(&Y), sizeof(Y)); - stream.read(reinterpret_cast(&Z), sizeof(Z)); - ss << "\tVector (X, Y, Z) = (" - << FormatHEX(X) << ", " << FormatHEX(Y) << ", " << FormatHEX(Z) << ") = (" - << X << ", " << Y << ", " << Z << ")" << std::endl; - } - else if (Type == "Rotator") - { - int32_t P, Y, R; - stream.read(reinterpret_cast(&P), sizeof(P)); - stream.read(reinterpret_cast(&Y), sizeof(Y)); - stream.read(reinterpret_cast(&R), sizeof(R)); - ss << "\tRotator (Pitch, Yaw, Roll) = (" - << FormatHEX((uint32_t)P) << ", " << FormatHEX((uint32_t)Y) << ", " << FormatHEX((uint32_t)R) << ") = (" - << P << ", " << Y << ", " << R << ")" << std::endl; - } - else if (Type == "Vector2D") - { - float X, Y; - stream.read(reinterpret_cast(&X), sizeof(X)); - stream.read(reinterpret_cast(&Y), sizeof(Y)); - ss << "\tVector2D (X, Y) = (" - << FormatHEX(X) << ", " << FormatHEX(Y) << ") = (" - << X << ", " << Y << ")" << std::endl; - } - else if (Type == "Guid") - { - FGuid GUID; - stream.read(reinterpret_cast(&GUID), sizeof(GUID)); - ss << "\tGUID = " << FormatHEX(GUID) << std::endl; - } - else if (Type == "Color") - { - uint8_t R, G, B, A; - stream.read(reinterpret_cast(&R), sizeof(R)); - stream.read(reinterpret_cast(&G), sizeof(G)); - stream.read(reinterpret_cast(&B), sizeof(B)); - stream.read(reinterpret_cast(&A), sizeof(A)); - ss << "\tColor (R, G, B, A) = (" - << FormatHEX(R) << ", " << FormatHEX(G) << ", " << FormatHEX(B) << ", " << FormatHEX(A) << ") = (" - << (unsigned)R << ", " << (unsigned)G << ", " << (unsigned)B << ", " << (unsigned)A << ")" << std::endl; - } - else if (Type == "LinearColor") - { - float R, G, B, A; - stream.read(reinterpret_cast(&R), sizeof(R)); - stream.read(reinterpret_cast(&G), sizeof(G)); - stream.read(reinterpret_cast(&B), sizeof(B)); - stream.read(reinterpret_cast(&A), sizeof(A)); - ss << "\tLinearColor (R, G, B, A) = (" - << FormatHEX(R) << ", " << FormatHEX(G) << ", " << FormatHEX(B) << ", " << FormatHEX(A) << ") = (" - << R << ", " << G << ", " << B << ", " << A << ")" << std::endl; - } - else if (Type == "Box") - { - float X, Y, Z; - stream.read(reinterpret_cast(&X), sizeof(X)); - stream.read(reinterpret_cast(&Y), sizeof(Y)); - stream.read(reinterpret_cast(&Z), sizeof(Z)); - ss << "\tVector Min (X, Y, Z) = (" - << FormatHEX(X) << ", " << FormatHEX(Y) << ", " << FormatHEX(Z) << ") = (" - << X << ", " << Y << ", " << Z << ")" << std::endl; - stream.read(reinterpret_cast(&X), sizeof(X)); - stream.read(reinterpret_cast(&Y), sizeof(Y)); - stream.read(reinterpret_cast(&Z), sizeof(Z)); - ss << "\tVector Max (X, Y, Z) = (" - << FormatHEX(X) << ", " << FormatHEX(Y) << ", " << FormatHEX(Z) << ") = (" - << X << ", " << Y << ", " << Z << ")" << std::endl; - uint8_t byteVal = 0; - stream.read(reinterpret_cast(&byteVal), sizeof(byteVal)); - ss << "\tIsValid: " << FormatHEX(byteVal) << " = "; - if (byteVal == 0) - ss << "false\n"; - else - ss << "true\n"; - } - /// if it is big, it might be inner property list - else if(TryUnsafe == true && PropertySize > 24) - { - UDefaultPropertiesList SomeProperties; - ss << "Unsafe guess (it's a Property List):\n" << SomeProperties.Deserialize(stream, info, OwnerRef, true, false); - } - /// Guid? - else if(TryUnsafe == true && PropertySize == 16) - { - FGuid GUID; - stream.read(reinterpret_cast(&GUID), sizeof(GUID)); - ss << "\tUnsafe guess: GUID = " << FormatHEX(GUID) << std::endl; - } - /// if it is small, it might be NameIndex - else if(TryUnsafe == true && PropertySize == 8) - { - UNameIndex value; - stream.read(reinterpret_cast(&value), sizeof(value)); - ss << "\tUnsafe guess:\n"; - ss << "\tName: " << FormatHEX(value) << " = " << info.IndexToName(value) << std::endl; - } - /// if it is even smaller, it might be integer (or float) or object reference - else if(TryUnsafe == true && PropertySize == 4) - { - int32_t value; - stream.read(reinterpret_cast(&value), sizeof(value)); - ss << "\tUnsafe guess: " - << "It's an Integer: " << FormatHEX((uint32_t)value) << " = " << value - << " or a Reference: " << FormatHEX((uint32_t)value) << " -> " << info.ObjRefToName(value) << std::endl; - } - else - { - //stream.seekg(PropertySize, std::ios::cur); - //ss << "\tUnknown property!\n"; - if (PropertySize <= info.GetExportEntry(OwnerRef).SerialSize) - { - std::vector unk(PropertySize); - stream.read(unk.data(), unk.size()); - ss << "\tUnknown property: " << FormatHEX(unk) << std::endl; - } - } - return ss.str(); -} - -std::string UDefaultProperty::FindArrayType(std::string ArrName, std::istream& stream, UPKInfo& info) -{ - if (OwnerRef <= 0) - return "None"; - FObjectExport OwnerEntry = info.GetExportEntry(OwnerRef); - std::string OwnerName = OwnerEntry.FullName; - size_t pos = OwnerName.find("Default__"); - if (pos != std::string::npos) - { - OwnerName = OwnerName.substr(pos + 9); - } - std::string FullName = OwnerName + "." + ArrName; - UObjectReference ObjRef = info.FindObject(FullName); - if (ObjRef <= 0) - return "None"; - FObjectExport ArrayEntry = info.GetExportEntry(ObjRef); - if (ArrayEntry.Type == "ArrayProperty") - { - UArrayProperty ArrProperty; - size_t StreamPos = stream.tellg(); - stream.seekg(ArrayEntry.SerialOffset); - /// quick-deserialize property, as we need only it's inner type info - ArrProperty.SetRef(ObjRef); - ArrProperty.SetUnsafe(false); - ArrProperty.SetQuickMode(true); - ArrProperty.Deserialize(stream, info); - stream.seekg(StreamPos); - if (ArrProperty.GetInner() <= 0) - return "None"; - FObjectExport InnerEntry = info.GetExportEntry(ArrProperty.GetInner()); - return InnerEntry.Type; - } - return "None"; -} - -std::string UDefaultProperty::GuessArrayType(std::string ArrName) -{ - if (ArrName.find("Component") != std::string::npos) - { - return "ComponentProperty"; - } - else if (ArrName.find("Class") != std::string::npos) - { - return "ClassProperty"; - } - else if (ArrName.find("Interface") != std::string::npos) - { - return "InterfaceProperty"; - } - else if (ArrName.find("Object") != std::string::npos) - { - return "ObjectProperty"; - } - else if (ArrName.find("Name") != std::string::npos) - { - return "StringProperty"; - } - return "None"; -} - -std::string UObject::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << "UObject:\n"; - stream.read(reinterpret_cast(&ObjRef), sizeof(ObjRef)); - ss << "\tPrevObjRef = " << FormatHEX((uint32_t)ObjRef) << " -> " << info.ObjRefToName(ObjRef) << std::endl; - if (Type != GlobalType::UClass) - { - FObjectExport ThisTableEntry = info.GetExportEntry(ThisRef); - if (TryUnsafe == true && ThisRef > 0 && (ThisTableEntry.ObjectFlagsL & (uint32_t)UObjectFlagsL::HasStack)) - { - stream.seekg(22, std::ios::cur); - ss << "Can't deserialize stack: skipping!\n"; - } - ss << DefaultProperties.Deserialize(stream, info, ThisRef, TryUnsafe, QuickMode); - } - return ss.str(); -} - -std::string UField::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UObject::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UField:\n"; - FieldOffset = NextRefOffset = stream.tellg(); - stream.read(reinterpret_cast(&NextRef), sizeof(NextRef)); - ss << "\tNextRef = " << FormatHEX((uint32_t)NextRef) << " -> " << info.ObjRefToName(NextRef) << std::endl; - if (IsStructure()) - { - stream.read(reinterpret_cast(&ParentRef), sizeof(ParentRef)); - ss << "\tParentRef = " << FormatHEX((uint32_t)ParentRef) << " -> " << info.ObjRefToName(ParentRef) << std::endl; - } - FieldSize = (unsigned)stream.tellg() - (unsigned)FieldOffset; - return ss.str(); -} - -std::string UStruct::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UField::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UStruct:\n"; - StructOffset = stream.tellg(); - stream.read(reinterpret_cast(&ScriptTextRef), sizeof(ScriptTextRef)); - ss << "\tScriptTextRef = " << FormatHEX((uint32_t)ScriptTextRef) << " -> " << info.ObjRefToName(ScriptTextRef) << std::endl; - FirstChildRefOffset = stream.tellg(); - stream.read(reinterpret_cast(&FirstChildRef), sizeof(FirstChildRef)); - ss << "\tFirstChildRef = " << FormatHEX((uint32_t)FirstChildRef) << " -> " << info.ObjRefToName(FirstChildRef) << std::endl; - stream.read(reinterpret_cast(&CppTextRef), sizeof(CppTextRef)); - ss << "\tCppTextRef = " << FormatHEX((uint32_t)CppTextRef) << " -> " << info.ObjRefToName(CppTextRef) << std::endl; - stream.read(reinterpret_cast(&Line), sizeof(Line)); - ss << "\tLine = " << FormatHEX(Line) << std::endl; - stream.read(reinterpret_cast(&TextPos), sizeof(TextPos)); - ss << "\tTextPos = " << FormatHEX(TextPos) << std::endl; - stream.read(reinterpret_cast(&ScriptMemorySize), sizeof(ScriptMemorySize)); - ss << "\tScriptMemorySize = " << FormatHEX(ScriptMemorySize) << std::endl; - stream.read(reinterpret_cast(&ScriptSerialSize), sizeof(ScriptSerialSize)); - ss << "\tScriptSerialSize = " << FormatHEX(ScriptSerialSize) << std::endl; - /// prevent allocation errors, caused by bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - DataScript.resize(ScriptSerialSize); - ScriptOffset = stream.tellg(); - stream.read(DataScript.data(), DataScript.size()); - ss << "\tScript decompiler is not implemented!\n"; - StructSize = (unsigned)stream.tellg() - (unsigned)StructOffset; - return ss.str(); -} - -std::string UFunction::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UStruct::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UFunction:\n"; - FunctionOffset = stream.tellg(); - stream.read(reinterpret_cast(&NativeToken), sizeof(NativeToken)); - ss << "\tNativeToken = " << FormatHEX(NativeToken) << std::endl; - stream.read(reinterpret_cast(&OperPrecedence), sizeof(OperPrecedence)); - ss << "\tOperPrecedence = " << FormatHEX(OperPrecedence) << std::endl; - FlagsOffset = stream.tellg(); - stream.read(reinterpret_cast(&FunctionFlags), sizeof(FunctionFlags)); - ss << "\tFunctionFlags = " << FormatHEX(FunctionFlags) << std::endl; - ss << FormatFunctionFlags(FunctionFlags); - if (FunctionFlags & (uint32_t)UFunctionFlags::Net) - { - stream.read(reinterpret_cast(&RepOffset), sizeof(RepOffset)); - ss << "\tRepOffset = " << FormatHEX(RepOffset) << std::endl; - } - stream.read(reinterpret_cast(&NameIdx), sizeof(NameIdx)); - ss << "\tNameIdx = " << FormatHEX(NameIdx) << " -> " << info.IndexToName(NameIdx) << std::endl; - FunctionSize = (unsigned)stream.tellg() - (unsigned)FunctionOffset; - return ss.str(); -} - -std::string UScriptStruct::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UStruct::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UScriptStruct:\n"; - ScriptStructOffset = stream.tellg(); - FlagsOffset = stream.tellg(); - stream.read(reinterpret_cast(&StructFlags), sizeof(StructFlags)); - ss << "\tStructFlags = " << FormatHEX(StructFlags) << std::endl; - ss << FormatStructFlags(StructFlags); - ss << StructDefaultProperties.Deserialize(stream, info, ThisRef, TryUnsafe, QuickMode); - ScriptStructSize = (unsigned)stream.tellg() - (unsigned)ScriptStructOffset; - return ss.str(); -} - -std::string UState::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UStruct::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UState:\n"; - StateOffset = stream.tellg(); - stream.read(reinterpret_cast(&ProbeMask), sizeof(ProbeMask)); - ss << "\tProbeMask = " << FormatHEX(ProbeMask) << std::endl; - stream.read(reinterpret_cast(&LabelTableOffset), sizeof(LabelTableOffset)); - ss << "\tLabelTableOffset = " << FormatHEX(LabelTableOffset) << std::endl; - FlagsOffset = stream.tellg(); - stream.read(reinterpret_cast(&StateFlags), sizeof(StateFlags)); - ss << "\tStateFlags = " << FormatHEX(StateFlags) << std::endl; - ss << FormatStateFlags(StateFlags); - stream.read(reinterpret_cast(&StateMapSize), sizeof(StateMapSize)); - ss << "\tStateMapSize = " << FormatHEX(StateMapSize) << " (" << StateMapSize << ")" << std::endl; - StateMap.clear(); - if (StateMapSize > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - StateMapSize = 0; - for (unsigned i = 0; i < StateMapSize; ++i) - { - std::pair MapElement; - stream.read(reinterpret_cast(&MapElement), sizeof(MapElement)); - ss << "\tStateMap[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(MapElement.first) << " -> " << info.IndexToName(MapElement.first) << std::endl; - ss << "\t\t" << FormatHEX((uint32_t)MapElement.second) << " -> " << info.ObjRefToName(MapElement.second) << std::endl; - StateMap.push_back(MapElement); - } - StateSize = (unsigned)stream.tellg() - (unsigned)StateOffset; - return ss.str(); -} - -std::string UClass::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UState::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UClass:\n"; - FlagsOffset = stream.tellg(); - stream.read(reinterpret_cast(&ClassFlags), sizeof(ClassFlags)); - ss << "\tClassFlags = " << FormatHEX(ClassFlags) << std::endl; - ss << FormatClassFlags(ClassFlags); - stream.read(reinterpret_cast(&WithinRef), sizeof(WithinRef)); - ss << "\tWithinRef = " << FormatHEX((uint32_t)WithinRef) << " -> " << info.ObjRefToName(WithinRef) << std::endl; - stream.read(reinterpret_cast(&ConfigNameIdx), sizeof(ConfigNameIdx)); - ss << "\tConfigNameIdx = " << FormatHEX(ConfigNameIdx) << " -> " << info.IndexToName(ConfigNameIdx) << std::endl; - stream.read(reinterpret_cast(&NumComponents), sizeof(NumComponents)); - ss << "\tNumComponents = " << FormatHEX(NumComponents) << " (" << NumComponents << ")" << std::endl; - Components.clear(); - if (NumComponents > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NumComponents = 0; - for (unsigned i = 0; i < NumComponents; ++i) - { - std::pair MapElement; - stream.read(reinterpret_cast(&MapElement), sizeof(MapElement)); - ss << "\tComponents[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(MapElement.first) << " -> " << info.IndexToName(MapElement.first) << std::endl; - ss << "\t\t" << FormatHEX((uint32_t)MapElement.second) << " -> " << info.ObjRefToName(MapElement.second) << std::endl; - Components.push_back(MapElement); - } - stream.read(reinterpret_cast(&NumInterfaces), sizeof(NumInterfaces)); - ss << "\tNumInterfaces = " << FormatHEX(NumInterfaces) << " (" << NumInterfaces << ")" << std::endl; - Interfaces.clear(); - if (NumInterfaces > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NumInterfaces = 0; - for (unsigned i = 0; i < NumInterfaces; ++i) - { - std::pair MapElement; - stream.read(reinterpret_cast(&MapElement), sizeof(MapElement)); - ss << "\tInterfaces[" << i << "]:\n"; - ss << "\t\t" << FormatHEX((uint32_t)MapElement.first) << " -> " << info.ObjRefToName(MapElement.first) << std::endl; - ss << "\t\t" << FormatHEX(MapElement.second) << std::endl; - Interfaces.push_back(MapElement); - } - stream.read(reinterpret_cast(&NumDontSortCategories), sizeof(NumDontSortCategories)); - ss << "\tNumDontSortCategories = " << FormatHEX(NumDontSortCategories) << " (" << NumDontSortCategories << ")" << std::endl; - DontSortCategories.clear(); - if (NumDontSortCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NumDontSortCategories = 0; - for (unsigned i = 0; i < NumDontSortCategories; ++i) - { - UNameIndex Element; - stream.read(reinterpret_cast(&Element), sizeof(Element)); - ss << "\tDontSortCategories[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; - DontSortCategories.push_back(Element); - } - stream.read(reinterpret_cast(&NumHideCategories), sizeof(NumHideCategories)); - ss << "\tNumHideCategories = " << FormatHEX(NumHideCategories) << " (" << NumHideCategories << ")" << std::endl; - HideCategories.clear(); - if (NumHideCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NumHideCategories = 0; - for (unsigned i = 0; i < NumHideCategories; ++i) - { - UNameIndex Element; - stream.read(reinterpret_cast(&Element), sizeof(Element)); - ss << "\tHideCategories[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; - HideCategories.push_back(Element); - } - stream.read(reinterpret_cast(&NumAutoExpandCategories), sizeof(NumAutoExpandCategories)); - ss << "\tNumAutoExpandCategories = " << FormatHEX(NumAutoExpandCategories) << " (" << NumAutoExpandCategories << ")" << std::endl; - AutoExpandCategories.clear(); - if (NumAutoExpandCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NumAutoExpandCategories = 0; - for (unsigned i = 0; i < NumAutoExpandCategories; ++i) - { - UNameIndex Element; - stream.read(reinterpret_cast(&Element), sizeof(Element)); - ss << "\tAutoExpandCategories[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; - AutoExpandCategories.push_back(Element); - } - stream.read(reinterpret_cast(&NumAutoCollapseCategories), sizeof(NumAutoCollapseCategories)); - ss << "\tNumAutoCollapseCategories = " << FormatHEX(NumAutoCollapseCategories) << " (" << NumAutoCollapseCategories << ")" << std::endl; - AutoCollapseCategories.clear(); - if (NumAutoCollapseCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NumAutoCollapseCategories = 0; - for (unsigned i = 0; i < NumAutoCollapseCategories; ++i) - { - UNameIndex Element; - stream.read(reinterpret_cast(&Element), sizeof(Element)); - ss << "\tAutoCollapseCategories[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; - AutoCollapseCategories.push_back(Element); - } - stream.read(reinterpret_cast(&ForceScriptOrder), sizeof(ForceScriptOrder)); - ss << "\tForceScriptOrder = " << FormatHEX(ForceScriptOrder) << std::endl; - stream.read(reinterpret_cast(&NumClassGroups), sizeof(NumClassGroups)); - ss << "\tNumClassGroups = " << FormatHEX(NumClassGroups) << " (" << NumClassGroups << ")" << std::endl; - ClassGroups.clear(); - if (NumClassGroups > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NumClassGroups = 0; - for (unsigned i = 0; i < NumClassGroups; ++i) - { - UNameIndex Element; - stream.read(reinterpret_cast(&Element), sizeof(Element)); - ss << "\tClassGroups[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; - ClassGroups.push_back(Element); - } - stream.read(reinterpret_cast(&NativeClassNameLength), sizeof(NativeClassNameLength)); - ss << "\tNativeClassNameLength = " << FormatHEX(NativeClassNameLength) << std::endl; - if (NativeClassNameLength > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention - NativeClassNameLength = 0; - if (NativeClassNameLength > 0) - { - getline(stream, NativeClassName, '\0'); - ss << "\tNativeClassName = " << NativeClassName << std::endl; - } - stream.read(reinterpret_cast(&DLLBindName), sizeof(DLLBindName)); - ss << "\tDLLBindName = " << FormatHEX(DLLBindName) << " -> " << info.IndexToName(DLLBindName) << std::endl; - stream.read(reinterpret_cast(&DefaultRef), sizeof(DefaultRef)); - ss << "\tDefaultRef = " << FormatHEX((uint32_t)DefaultRef) << " -> " << info.ObjRefToName(DefaultRef) << std::endl; - return ss.str(); -} - -std::string UConst::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UField::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UConst:\n"; - stream.read(reinterpret_cast(&ValueLength), sizeof(ValueLength)); - ss << "\tValueLength = " << FormatHEX(ValueLength) << std::endl; - if (ValueLength > 0) - { - getline(stream, Value, '\0'); - ss << "\tValue = " << Value << std::endl; - } - return ss.str(); -} - -std::string UEnum::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UField::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UEnum:\n"; - stream.read(reinterpret_cast(&NumNames), sizeof(NumNames)); - ss << "\tNumNames = " << FormatHEX(NumNames) << " (" << NumNames << ")" << std::endl; - Names.clear(); - for (unsigned i = 0; i < NumNames; ++i) - { - UNameIndex Element; - stream.read(reinterpret_cast(&Element), sizeof(Element)); - ss << "\tNames[" << i << "]:\n"; - ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; - Names.push_back(Element); - } - return ss.str(); -} - -std::string UProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UField::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UProperty:\n"; - uint32_t tmpVal; - stream.read(reinterpret_cast(&tmpVal), sizeof(tmpVal)); - ArrayDim = tmpVal % (1 << 16); - ElementSize = tmpVal >> 16; - ss << "\tArrayDim = " << FormatHEX(ArrayDim) << " (" << ArrayDim << ")" << std::endl; - ss << "\tElementSize = " << FormatHEX(ElementSize) << " (" << ElementSize << ")" << std::endl; - FlagsOffset = stream.tellg(); - stream.read(reinterpret_cast(&PropertyFlagsL), sizeof(PropertyFlagsL)); - ss << "\tPropertyFlagsL = " << FormatHEX(PropertyFlagsL) << std::endl; - ss << FormatPropertyFlagsL(PropertyFlagsL); - stream.read(reinterpret_cast(&PropertyFlagsH), sizeof(PropertyFlagsH)); - ss << "\tPropertyFlagsH = " << FormatHEX(PropertyFlagsH) << std::endl; - ss << FormatPropertyFlagsH(PropertyFlagsH); - stream.read(reinterpret_cast(&CategoryIndex), sizeof(CategoryIndex)); - ss << "\tCategoryIndex = " << FormatHEX(CategoryIndex) << " -> " << info.IndexToName(CategoryIndex) << std::endl; - stream.read(reinterpret_cast(&ArrayEnumRef), sizeof(ArrayEnumRef)); - ss << "\tArrayEnumRef = " << FormatHEX((uint32_t)ArrayEnumRef) << " -> " << info.ObjRefToName(ArrayEnumRef) << std::endl; - if (PropertyFlagsL & (uint32_t)UPropertyFlagsL::Net) - { - stream.read(reinterpret_cast(&RepOffset), sizeof(RepOffset)); - ss << "\tRepOffset = " << FormatHEX(RepOffset) << std::endl; - } - return ss.str(); -} - -std::string UByteProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UByteProperty:\n"; - stream.read(reinterpret_cast(&EnumObjRef), sizeof(EnumObjRef)); - ss << "\tEnumObjRef = " << FormatHEX((uint32_t)EnumObjRef) << " -> " << info.ObjRefToName(EnumObjRef) << std::endl; - return ss.str(); -} - -std::string UObjectProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UObjectProperty:\n"; - stream.read(reinterpret_cast(&OtherObjRef), sizeof(OtherObjRef)); - ss << "\tOtherObjRef = " << FormatHEX((uint32_t)OtherObjRef) << " -> " << info.ObjRefToName(OtherObjRef) << std::endl; - return ss.str(); -} - -std::string UClassProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UObjectProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UClassProperty:\n"; - stream.read(reinterpret_cast(&ClassObjRef), sizeof(ClassObjRef)); - ss << "\tClassObjRef = " << FormatHEX((uint32_t)ClassObjRef) << " -> " << info.ObjRefToName(ClassObjRef) << std::endl; - return ss.str(); -} - -std::string UStructProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UStructProperty:\n"; - stream.read(reinterpret_cast(&StructObjRef), sizeof(StructObjRef)); - ss << "\tStructObjRef = " << FormatHEX((uint32_t)StructObjRef) << " -> " << info.ObjRefToName(StructObjRef) << std::endl; - return ss.str(); -} - -std::string UFixedArrayProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UFixedArrayProperty:\n"; - stream.read(reinterpret_cast(&InnerObjRef), sizeof(InnerObjRef)); - ss << "\tInnerObjRef = " << FormatHEX((uint32_t)InnerObjRef) << " -> " << info.ObjRefToName(InnerObjRef) << std::endl; - stream.read(reinterpret_cast(&Count), sizeof(Count)); - ss << "\tCount = " << FormatHEX(Count) << " (" << Count << ")" << std::endl; - return ss.str(); -} - -std::string UArrayProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UArrayProperty:\n"; - stream.read(reinterpret_cast(&InnerObjRef), sizeof(InnerObjRef)); - ss << "\tInnerObjRef = " << FormatHEX((uint32_t)InnerObjRef) << " -> " << info.ObjRefToName(InnerObjRef) << std::endl; - return ss.str(); -} - -std::string UDelegateProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UDelegateProperty:\n"; - stream.read(reinterpret_cast(&FunctionObjRef), sizeof(FunctionObjRef)); - ss << "\tFunctionObjRef = " << FormatHEX((uint32_t)FunctionObjRef) << " -> " << info.ObjRefToName(FunctionObjRef) << std::endl; - stream.read(reinterpret_cast(&DelegateObjRef), sizeof(DelegateObjRef)); - ss << "\tDelegateObjRef = " << FormatHEX((uint32_t)DelegateObjRef) << " -> " << info.ObjRefToName(DelegateObjRef) << std::endl; - return ss.str(); -} - -std::string UInterfaceProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UInterfaceProperty:\n"; - stream.read(reinterpret_cast(&InterfaceObjRef), sizeof(InterfaceObjRef)); - ss << "\tInterfaceObjRef = " << FormatHEX((uint32_t)InterfaceObjRef) << " -> " << info.ObjRefToName(InterfaceObjRef) << std::endl; - return ss.str(); -} - -std::string UMapProperty::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - ss << UProperty::Deserialize(stream, info); - /// check for bad data - if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - ss << "UMapProperty:\n"; - stream.read(reinterpret_cast(&KeyObjRef), sizeof(KeyObjRef)); - ss << "\tKeyObjRef = " << FormatHEX((uint32_t)KeyObjRef) << " -> " << info.ObjRefToName(KeyObjRef) << std::endl; - stream.read(reinterpret_cast(&ValueObjRef), sizeof(ValueObjRef)); - ss << "\tValueObjRef = " << FormatHEX((uint32_t)ValueObjRef) << " -> " << info.ObjRefToName(ValueObjRef) << std::endl; - return ss.str(); -} - -std::string ULevel::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - UObjectReference A; - uint32_t NumActors; - ss << UObject::Deserialize(stream, info); - uint32_t pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); - ss << "ULevel:\n"; - stream.read(reinterpret_cast(&A), sizeof(A)); - ss << "\tLevel object: " << FormatHEX((uint32_t)A) << " -> " << info.ObjRefToName(A) << std::endl; - stream.read(reinterpret_cast(&NumActors), sizeof(NumActors)); - ss << "\tNum actors: " << FormatHEX(NumActors) << " = " << NumActors << std::endl; - stream.read(reinterpret_cast(&A), sizeof(A)); - ss << "\tWorldInfo object: " << FormatHEX((uint32_t)A) << " -> " << info.ObjRefToName(A) << std::endl; - ss << "\tActors:\n"; - for (unsigned i = 0; i < NumActors; ++i) - { - stream.read(reinterpret_cast(&A), sizeof(A)); - Actors.push_back(A); - ss << "\t\t" << FormatHEX((char*)&A, sizeof(A)) << "\t//\t" << FormatHEX((uint32_t)A) << " -> " << info.ObjRefToName(A) << std::endl; - } - pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); - ss << "Stream relative position (debug info): " << FormatHEX(pos) << " (" << pos << ")\n"; - ss << "Object unknown, can't deserialize!\n"; - return ss.str(); -} - -std::string UObjectUnknown::Deserialize(std::istream& stream, UPKInfo& info) -{ - std::ostringstream ss; - /// prevent crashes while deserializing components - if (info.GetExportEntry(ThisRef).Type.find("Component") != std::string::npos) - { - ss << "Can't deserialize Components!\n"; - return ss.str(); - } - /// prevent crashes while deserializing FX_ - if (info.GetExportEntry(ThisRef).Type.find("BodySetup") != std::string::npos) - { - ss << "Can't deserialize BodySetup!\n"; - return ss.str(); - } - /// to be on a safe side: don't deserialize unknown objects - if (TryUnsafe == true) - { - ss << UObject::Deserialize(stream, info); - uint32_t pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); - ss << "Stream relative position (debug info): " << FormatHEX(pos) << " (" << pos << ")\n"; - if (pos == info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - } - ss << "UObjectUnknown:\n"; - ss << "\tObject unknown, can't deserialize!\n"; - return ss.str(); -} +#include "UObject.h" + +#include + +void UBulkDataMirror::SetBulkData(std::vector Data) +{ + BulkData = Data; + SavedBulkDataFlags = 0; + /// temporarily, until we know the exact meaning of this + SavedElementCount = SavedBulkDataSizeOnDisk = BulkData.size(); + SavedBulkDataOffsetInFile = 0; +} + +std::string UBulkDataMirror::Serialize() +{ + std::stringstream ss; + ss.write(reinterpret_cast(&SavedBulkDataFlags), 4); + ss.write(reinterpret_cast(&SavedElementCount), 4); + ss.write(reinterpret_cast(&SavedBulkDataSizeOnDisk), 4); + ss.write(reinterpret_cast(&SavedBulkDataOffsetInFile), 4); + if (BulkData.size() > 0) + { + ss.write(BulkData.data(), BulkData.size()); + } + return ss.str(); +} + +std::string UDefaultPropertiesList::Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe, bool quick) +{ + std::ostringstream ss; + ss << "UDefaultPropertiesList:\n"; + PropertyOffset = stream.tellg(); + DefaultProperties.clear(); + size_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; + UDefaultProperty Property; + do + { + Property = UDefaultProperty{}; + ss << Property.Deserialize(stream, info, owner, unsafe, quick); + DefaultProperties.push_back(Property); + } while (Property.GetName() != "None" && stream.good() && (size_t)stream.tellg() < maxOffset); + PropertySize = (unsigned)stream.tellg() - (unsigned)PropertyOffset; + return ss.str(); +} + +std::string UDefaultProperty::Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe, bool quick) +{ + Init(owner, unsafe, quick); + size_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; + std::ostringstream ss; + ss << "UDefaultProperty:\n"; + stream.read(reinterpret_cast(&NameIdx), sizeof(NameIdx)); + Name = info.IndexToName(NameIdx); + ss << "\tNameIdx: " << FormatHEX(NameIdx) << " -> " << info.IndexToName(NameIdx) << std::endl; + if (Name != "None") + { + stream.read(reinterpret_cast(&TypeIdx), sizeof(TypeIdx)); + ss << "\tTypeIdx: " << FormatHEX(TypeIdx) << " -> " << info.IndexToName(TypeIdx) << std::endl; + stream.read(reinterpret_cast(&PropertySize), sizeof(PropertySize)); + ss << "\tPropertySize: " << FormatHEX(PropertySize) << std::endl; + /// prevent long loop, caused by bad data + if ((int)stream.tellg() + (int)PropertySize > (int)maxOffset) + return ss.str(); + stream.read(reinterpret_cast(&ArrayIdx), sizeof(ArrayIdx)); + ss << "\tArrayIdx: " << FormatHEX(ArrayIdx) << std::endl; + Type = info.IndexToName(TypeIdx); + if (Type == "BoolProperty") + { + stream.read(reinterpret_cast(&BoolValue), sizeof(BoolValue)); + ss << "\tBoolean value: " << FormatHEX(BoolValue) << " = "; + if (BoolValue == 0) + ss << "false\n"; + else + ss << "true\n"; + } + if (Type == "StructProperty" || Type == "ByteProperty") + { + stream.read(reinterpret_cast(&InnerNameIdx), sizeof(InnerNameIdx)); + ss << "\tInnerNameIdx: " << FormatHEX(InnerNameIdx) << " -> " << info.IndexToName(InnerNameIdx) << std::endl; + if (Type == "StructProperty") + Type = info.IndexToName(InnerNameIdx); + } + if (PropertySize > 0) + { + size_t offset = stream.tellg(); + InnerValue.resize(PropertySize); + stream.read(InnerValue.data(), InnerValue.size()); + size_t offset2 = stream.tellg(); + if (QuickMode == false) + { + stream.seekg(offset); + ss << DeserializeValue(stream, info); + } + stream.seekg(offset2); + } + } + return ss.str(); +} + +std::string UDefaultProperty::DeserializeValue(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + if (Type == "IntProperty") + { + int32_t value; + stream.read(reinterpret_cast(&value), sizeof(value)); + ss << "\tInteger: " << FormatHEX((uint32_t)value) << " = " << value << std::endl; + } + else if (Type == "FloatProperty") + { + float value; + stream.read(reinterpret_cast(&value), sizeof(value)); + ss << "\tFloat: " << FormatHEX(value) << " = " << value << std::endl; + } + else if (Type == "ObjectProperty" || + Type == "InterfaceProperty" || + Type == "ComponentProperty" || + Type == "ClassProperty") + { + UObjectReference value; + stream.read(reinterpret_cast(&value), sizeof(value)); + ss << "\tObject: " << FormatHEX((uint32_t)value) << " = "; + if (value == 0) + ss << "none\n"; + else + ss << info.ObjRefToName(value) << std::endl; + } + else if (Type == "NameProperty" || Type == "ByteProperty") + { + UNameIndex value; + stream.read(reinterpret_cast(&value), sizeof(value)); + ss << "\tName: " << FormatHEX(value) << " = " << info.IndexToName(value) << std::endl; + } + else if (Type == "StrProperty") + { + uint32_t StrLength; + stream.read(reinterpret_cast(&StrLength), sizeof(StrLength)); + ss << "\tStrLength = " << FormatHEX(StrLength) << " = " << StrLength << std::endl; + if (StrLength > 0) + { + std::string str; + getline(stream, str, '\0'); + ss << "\tString = " << str << std::endl; + } + } + else if (Type == "ArrayProperty") + { + uint32_t NumElements; + stream.read(reinterpret_cast(&NumElements), sizeof(NumElements)); + ss << "\tNumElements = " << FormatHEX(NumElements) << " = " << NumElements << std::endl; + if ((NumElements > 0) && (PropertySize > 4)) + { + std::string ArrayInnerType = FindArrayType(Name, stream, info); + /*if (TryUnsafe == true && ArrayInnerType == "None") + { + ArrayInnerType = GuessArrayType(Name); + if (ArrayInnerType != "None") + ss << "\tUnsafe type guess:\n"; + }*/ + ss << "\tArrayInnerType = " << ArrayInnerType << std::endl; + UDefaultProperty InnerProperty; + InnerProperty.OwnerRef = OwnerRef; + InnerProperty.TryUnsafe = TryUnsafe; + InnerProperty.Type = ArrayInnerType; + InnerProperty.PropertySize = PropertySize - 4; + if (ArrayInnerType == "None") + { + if (TryUnsafe == true && + (InnerProperty.PropertySize/NumElements > 24 || + InnerProperty.PropertySize/NumElements == 16 || + InnerProperty.PropertySize/NumElements == 8 || + InnerProperty.PropertySize/NumElements == 4)) + { + InnerProperty.PropertySize /= NumElements; + for (unsigned i = 0; i < NumElements; ++i) + { + ss << "\t" << Name << "[" << i << "]:\n"; + ss << InnerProperty.DeserializeValue(stream, info); + } + } + else + { + ss << InnerProperty.DeserializeValue(stream, info); + } + } + else + { + InnerProperty.PropertySize /= NumElements; + for (unsigned i = 0; i < NumElements; ++i) + { + ss << "\t" << Name << "[" << i << "]:\n"; + ss << InnerProperty.DeserializeValue(stream, info); + } + } + } + } + else if (Type == "Vector") + { + float X, Y, Z; + stream.read(reinterpret_cast(&X), sizeof(X)); + stream.read(reinterpret_cast(&Y), sizeof(Y)); + stream.read(reinterpret_cast(&Z), sizeof(Z)); + ss << "\tVector (X, Y, Z) = (" + << FormatHEX(X) << ", " << FormatHEX(Y) << ", " << FormatHEX(Z) << ") = (" + << X << ", " << Y << ", " << Z << ")" << std::endl; + } + else if (Type == "Rotator") + { + int32_t P, Y, R; + stream.read(reinterpret_cast(&P), sizeof(P)); + stream.read(reinterpret_cast(&Y), sizeof(Y)); + stream.read(reinterpret_cast(&R), sizeof(R)); + ss << "\tRotator (Pitch, Yaw, Roll) = (" + << FormatHEX((uint32_t)P) << ", " << FormatHEX((uint32_t)Y) << ", " << FormatHEX((uint32_t)R) << ") = (" + << P << ", " << Y << ", " << R << ")" << std::endl; + } + else if (Type == "Vector2D") + { + float X, Y; + stream.read(reinterpret_cast(&X), sizeof(X)); + stream.read(reinterpret_cast(&Y), sizeof(Y)); + ss << "\tVector2D (X, Y) = (" + << FormatHEX(X) << ", " << FormatHEX(Y) << ") = (" + << X << ", " << Y << ")" << std::endl; + } + else if (Type == "Guid") + { + FGuid GUID; + stream.read(reinterpret_cast(&GUID), sizeof(GUID)); + ss << "\tGUID = " << FormatHEX(GUID) << std::endl; + } + else if (Type == "Color") + { + uint8_t R, G, B, A; + stream.read(reinterpret_cast(&R), sizeof(R)); + stream.read(reinterpret_cast(&G), sizeof(G)); + stream.read(reinterpret_cast(&B), sizeof(B)); + stream.read(reinterpret_cast(&A), sizeof(A)); + ss << "\tColor (R, G, B, A) = (" + << FormatHEX(R) << ", " << FormatHEX(G) << ", " << FormatHEX(B) << ", " << FormatHEX(A) << ") = (" + << (unsigned)R << ", " << (unsigned)G << ", " << (unsigned)B << ", " << (unsigned)A << ")" << std::endl; + } + else if (Type == "LinearColor") + { + float R, G, B, A; + stream.read(reinterpret_cast(&R), sizeof(R)); + stream.read(reinterpret_cast(&G), sizeof(G)); + stream.read(reinterpret_cast(&B), sizeof(B)); + stream.read(reinterpret_cast(&A), sizeof(A)); + ss << "\tLinearColor (R, G, B, A) = (" + << FormatHEX(R) << ", " << FormatHEX(G) << ", " << FormatHEX(B) << ", " << FormatHEX(A) << ") = (" + << R << ", " << G << ", " << B << ", " << A << ")" << std::endl; + } + else if (Type == "Box") + { + float X, Y, Z; + stream.read(reinterpret_cast(&X), sizeof(X)); + stream.read(reinterpret_cast(&Y), sizeof(Y)); + stream.read(reinterpret_cast(&Z), sizeof(Z)); + ss << "\tVector Min (X, Y, Z) = (" + << FormatHEX(X) << ", " << FormatHEX(Y) << ", " << FormatHEX(Z) << ") = (" + << X << ", " << Y << ", " << Z << ")" << std::endl; + stream.read(reinterpret_cast(&X), sizeof(X)); + stream.read(reinterpret_cast(&Y), sizeof(Y)); + stream.read(reinterpret_cast(&Z), sizeof(Z)); + ss << "\tVector Max (X, Y, Z) = (" + << FormatHEX(X) << ", " << FormatHEX(Y) << ", " << FormatHEX(Z) << ") = (" + << X << ", " << Y << ", " << Z << ")" << std::endl; + uint8_t byteVal = 0; + stream.read(reinterpret_cast(&byteVal), sizeof(byteVal)); + ss << "\tIsValid: " << FormatHEX(byteVal) << " = "; + if (byteVal == 0) + ss << "false\n"; + else + ss << "true\n"; + } + /// if it is big, it might be inner property list + else if(TryUnsafe == true && PropertySize > 24) + { + UDefaultPropertiesList SomeProperties; + ss << "Unsafe guess (it's a Property List):\n" << SomeProperties.Deserialize(stream, info, OwnerRef, true, false); + } + /// Guid? + else if(TryUnsafe == true && PropertySize == 16) + { + FGuid GUID; + stream.read(reinterpret_cast(&GUID), sizeof(GUID)); + ss << "\tUnsafe guess: GUID = " << FormatHEX(GUID) << std::endl; + } + /// if it is small, it might be NameIndex + else if(TryUnsafe == true && PropertySize == 8) + { + UNameIndex value; + stream.read(reinterpret_cast(&value), sizeof(value)); + ss << "\tUnsafe guess:\n"; + ss << "\tName: " << FormatHEX(value) << " = " << info.IndexToName(value) << std::endl; + } + /// if it is even smaller, it might be integer (or float) or object reference + else if(TryUnsafe == true && PropertySize == 4) + { + int32_t value; + stream.read(reinterpret_cast(&value), sizeof(value)); + ss << "\tUnsafe guess: " + << "It's an Integer: " << FormatHEX((uint32_t)value) << " = " << value + << " or a Reference: " << FormatHEX((uint32_t)value) << " -> " << info.ObjRefToName(value) << std::endl; + } + else + { + //stream.seekg(PropertySize, std::ios::cur); + //ss << "\tUnknown property!\n"; + if (PropertySize <= info.GetExportEntry(OwnerRef).SerialSize) + { + std::vector unk(PropertySize); + stream.read(unk.data(), unk.size()); + ss << "\tUnknown property: " << FormatHEX(unk) << std::endl; + } + } + return ss.str(); +} + +std::string UDefaultProperty::FindArrayType(std::string ArrName, std::istream& stream, UPKInfo& info) +{ + if (OwnerRef <= 0) + return "None"; + FObjectExport OwnerEntry = info.GetExportEntry(OwnerRef); + std::string OwnerName = OwnerEntry.FullName; + size_t pos = OwnerName.find("Default__"); + if (pos != std::string::npos) + { + OwnerName = OwnerName.substr(pos + 9); + } + std::string FullName = OwnerName + "." + ArrName; + UObjectReference ObjRef = info.FindObject(FullName); + if (ObjRef <= 0) + return "None"; + FObjectExport ArrayEntry = info.GetExportEntry(ObjRef); + if (ArrayEntry.Type == "ArrayProperty") + { + UArrayProperty ArrProperty; + size_t StreamPos = stream.tellg(); + stream.seekg(ArrayEntry.SerialOffset); + /// quick-deserialize property, as we need only it's inner type info + ArrProperty.SetRef(ObjRef); + ArrProperty.SetUnsafe(false); + ArrProperty.SetQuickMode(true); + ArrProperty.Deserialize(stream, info); + stream.seekg(StreamPos); + if (ArrProperty.GetInner() <= 0) + return "None"; + FObjectExport InnerEntry = info.GetExportEntry(ArrProperty.GetInner()); + return InnerEntry.Type; + } + return "None"; +} + +std::string UDefaultProperty::GuessArrayType(std::string ArrName) +{ + if (ArrName.find("Component") != std::string::npos) + { + return "ComponentProperty"; + } + else if (ArrName.find("Class") != std::string::npos) + { + return "ClassProperty"; + } + else if (ArrName.find("Interface") != std::string::npos) + { + return "InterfaceProperty"; + } + else if (ArrName.find("Object") != std::string::npos) + { + return "ObjectProperty"; + } + else if (ArrName.find("Name") != std::string::npos) + { + return "StringProperty"; + } + return "None"; +} + +std::string UObject::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << "UObject:\n"; + stream.read(reinterpret_cast(&ObjRef), sizeof(ObjRef)); + ss << "\tPrevObjRef = " << FormatHEX((uint32_t)ObjRef) << " -> " << info.ObjRefToName(ObjRef) << std::endl; + if (Type != GlobalType::UClass) + { + FObjectExport ThisTableEntry = info.GetExportEntry(ThisRef); + if (TryUnsafe == true && ThisRef > 0 && (ThisTableEntry.ObjectFlagsL & (uint32_t)UObjectFlagsL::HasStack)) + { + stream.seekg(22, std::ios::cur); + ss << "Can't deserialize stack: skipping!\n"; + } + ss << DefaultProperties.Deserialize(stream, info, ThisRef, TryUnsafe, QuickMode); + } + return ss.str(); +} + +std::string UField::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UObject::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UField:\n"; + FieldOffset = NextRefOffset = stream.tellg(); + stream.read(reinterpret_cast(&NextRef), sizeof(NextRef)); + ss << "\tNextRef = " << FormatHEX((uint32_t)NextRef) << " -> " << info.ObjRefToName(NextRef) << std::endl; + if (IsStructure()) + { + stream.read(reinterpret_cast(&ParentRef), sizeof(ParentRef)); + ss << "\tParentRef = " << FormatHEX((uint32_t)ParentRef) << " -> " << info.ObjRefToName(ParentRef) << std::endl; + } + FieldSize = (unsigned)stream.tellg() - (unsigned)FieldOffset; + return ss.str(); +} + +std::string UStruct::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UField::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UStruct:\n"; + StructOffset = stream.tellg(); + stream.read(reinterpret_cast(&ScriptTextRef), sizeof(ScriptTextRef)); + ss << "\tScriptTextRef = " << FormatHEX((uint32_t)ScriptTextRef) << " -> " << info.ObjRefToName(ScriptTextRef) << std::endl; + FirstChildRefOffset = stream.tellg(); + stream.read(reinterpret_cast(&FirstChildRef), sizeof(FirstChildRef)); + ss << "\tFirstChildRef = " << FormatHEX((uint32_t)FirstChildRef) << " -> " << info.ObjRefToName(FirstChildRef) << std::endl; + stream.read(reinterpret_cast(&CppTextRef), sizeof(CppTextRef)); + ss << "\tCppTextRef = " << FormatHEX((uint32_t)CppTextRef) << " -> " << info.ObjRefToName(CppTextRef) << std::endl; + stream.read(reinterpret_cast(&Line), sizeof(Line)); + ss << "\tLine = " << FormatHEX(Line) << std::endl; + stream.read(reinterpret_cast(&TextPos), sizeof(TextPos)); + ss << "\tTextPos = " << FormatHEX(TextPos) << std::endl; + stream.read(reinterpret_cast(&ScriptMemorySize), sizeof(ScriptMemorySize)); + ss << "\tScriptMemorySize = " << FormatHEX(ScriptMemorySize) << std::endl; + stream.read(reinterpret_cast(&ScriptSerialSize), sizeof(ScriptSerialSize)); + ss << "\tScriptSerialSize = " << FormatHEX(ScriptSerialSize) << std::endl; + /// prevent allocation errors, caused by bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + DataScript.resize(ScriptSerialSize); + ScriptOffset = stream.tellg(); + stream.read(DataScript.data(), DataScript.size()); + ss << "\tScript decompiler is not implemented!\n"; + StructSize = (unsigned)stream.tellg() - (unsigned)StructOffset; + return ss.str(); +} + +std::string UFunction::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UStruct::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UFunction:\n"; + FunctionOffset = stream.tellg(); + stream.read(reinterpret_cast(&NativeToken), sizeof(NativeToken)); + ss << "\tNativeToken = " << FormatHEX(NativeToken) << std::endl; + stream.read(reinterpret_cast(&OperPrecedence), sizeof(OperPrecedence)); + ss << "\tOperPrecedence = " << FormatHEX(OperPrecedence) << std::endl; + FlagsOffset = stream.tellg(); + stream.read(reinterpret_cast(&FunctionFlags), sizeof(FunctionFlags)); + ss << "\tFunctionFlags = " << FormatHEX(FunctionFlags) << std::endl; + ss << FormatFunctionFlags(FunctionFlags); + if (FunctionFlags & (uint32_t)UFunctionFlags::Net) + { + stream.read(reinterpret_cast(&RepOffset), sizeof(RepOffset)); + ss << "\tRepOffset = " << FormatHEX(RepOffset) << std::endl; + } + stream.read(reinterpret_cast(&NameIdx), sizeof(NameIdx)); + ss << "\tNameIdx = " << FormatHEX(NameIdx) << " -> " << info.IndexToName(NameIdx) << std::endl; + FunctionSize = (unsigned)stream.tellg() - (unsigned)FunctionOffset; + return ss.str(); +} + +std::string UScriptStruct::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UStruct::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UScriptStruct:\n"; + ScriptStructOffset = stream.tellg(); + FlagsOffset = stream.tellg(); + stream.read(reinterpret_cast(&StructFlags), sizeof(StructFlags)); + ss << "\tStructFlags = " << FormatHEX(StructFlags) << std::endl; + ss << FormatStructFlags(StructFlags); + ss << StructDefaultProperties.Deserialize(stream, info, ThisRef, TryUnsafe, QuickMode); + ScriptStructSize = (unsigned)stream.tellg() - (unsigned)ScriptStructOffset; + return ss.str(); +} + +std::string UState::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UStruct::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UState:\n"; + StateOffset = stream.tellg(); + stream.read(reinterpret_cast(&ProbeMask), sizeof(ProbeMask)); + ss << "\tProbeMask = " << FormatHEX(ProbeMask) << std::endl; + stream.read(reinterpret_cast(&LabelTableOffset), sizeof(LabelTableOffset)); + ss << "\tLabelTableOffset = " << FormatHEX(LabelTableOffset) << std::endl; + FlagsOffset = stream.tellg(); + stream.read(reinterpret_cast(&StateFlags), sizeof(StateFlags)); + ss << "\tStateFlags = " << FormatHEX(StateFlags) << std::endl; + ss << FormatStateFlags(StateFlags); + stream.read(reinterpret_cast(&StateMapSize), sizeof(StateMapSize)); + ss << "\tStateMapSize = " << FormatHEX(StateMapSize) << " (" << StateMapSize << ")" << std::endl; + StateMap.clear(); + if (StateMapSize > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + StateMapSize = 0; + for (unsigned i = 0; i < StateMapSize; ++i) + { + std::pair MapElement; + stream.read(reinterpret_cast(&MapElement), sizeof(MapElement)); + ss << "\tStateMap[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(MapElement.first) << " -> " << info.IndexToName(MapElement.first) << std::endl; + ss << "\t\t" << FormatHEX((uint32_t)MapElement.second) << " -> " << info.ObjRefToName(MapElement.second) << std::endl; + StateMap.push_back(MapElement); + } + StateSize = (unsigned)stream.tellg() - (unsigned)StateOffset; + return ss.str(); +} + +std::string UClass::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UState::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UClass:\n"; + FlagsOffset = stream.tellg(); + stream.read(reinterpret_cast(&ClassFlags), sizeof(ClassFlags)); + ss << "\tClassFlags = " << FormatHEX(ClassFlags) << std::endl; + ss << FormatClassFlags(ClassFlags); + stream.read(reinterpret_cast(&WithinRef), sizeof(WithinRef)); + ss << "\tWithinRef = " << FormatHEX((uint32_t)WithinRef) << " -> " << info.ObjRefToName(WithinRef) << std::endl; + stream.read(reinterpret_cast(&ConfigNameIdx), sizeof(ConfigNameIdx)); + ss << "\tConfigNameIdx = " << FormatHEX(ConfigNameIdx) << " -> " << info.IndexToName(ConfigNameIdx) << std::endl; + stream.read(reinterpret_cast(&NumComponents), sizeof(NumComponents)); + ss << "\tNumComponents = " << FormatHEX(NumComponents) << " (" << NumComponents << ")" << std::endl; + Components.clear(); + if (NumComponents > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NumComponents = 0; + for (unsigned i = 0; i < NumComponents; ++i) + { + std::pair MapElement; + stream.read(reinterpret_cast(&MapElement), sizeof(MapElement)); + ss << "\tComponents[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(MapElement.first) << " -> " << info.IndexToName(MapElement.first) << std::endl; + ss << "\t\t" << FormatHEX((uint32_t)MapElement.second) << " -> " << info.ObjRefToName(MapElement.second) << std::endl; + Components.push_back(MapElement); + } + stream.read(reinterpret_cast(&NumInterfaces), sizeof(NumInterfaces)); + ss << "\tNumInterfaces = " << FormatHEX(NumInterfaces) << " (" << NumInterfaces << ")" << std::endl; + Interfaces.clear(); + if (NumInterfaces > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NumInterfaces = 0; + for (unsigned i = 0; i < NumInterfaces; ++i) + { + std::pair MapElement; + stream.read(reinterpret_cast(&MapElement), sizeof(MapElement)); + ss << "\tInterfaces[" << i << "]:\n"; + ss << "\t\t" << FormatHEX((uint32_t)MapElement.first) << " -> " << info.ObjRefToName(MapElement.first) << std::endl; + ss << "\t\t" << FormatHEX(MapElement.second) << std::endl; + Interfaces.push_back(MapElement); + } + stream.read(reinterpret_cast(&NumDontSortCategories), sizeof(NumDontSortCategories)); + ss << "\tNumDontSortCategories = " << FormatHEX(NumDontSortCategories) << " (" << NumDontSortCategories << ")" << std::endl; + DontSortCategories.clear(); + if (NumDontSortCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NumDontSortCategories = 0; + for (unsigned i = 0; i < NumDontSortCategories; ++i) + { + UNameIndex Element; + stream.read(reinterpret_cast(&Element), sizeof(Element)); + ss << "\tDontSortCategories[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; + DontSortCategories.push_back(Element); + } + stream.read(reinterpret_cast(&NumHideCategories), sizeof(NumHideCategories)); + ss << "\tNumHideCategories = " << FormatHEX(NumHideCategories) << " (" << NumHideCategories << ")" << std::endl; + HideCategories.clear(); + if (NumHideCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NumHideCategories = 0; + for (unsigned i = 0; i < NumHideCategories; ++i) + { + UNameIndex Element; + stream.read(reinterpret_cast(&Element), sizeof(Element)); + ss << "\tHideCategories[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; + HideCategories.push_back(Element); + } + stream.read(reinterpret_cast(&NumAutoExpandCategories), sizeof(NumAutoExpandCategories)); + ss << "\tNumAutoExpandCategories = " << FormatHEX(NumAutoExpandCategories) << " (" << NumAutoExpandCategories << ")" << std::endl; + AutoExpandCategories.clear(); + if (NumAutoExpandCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NumAutoExpandCategories = 0; + for (unsigned i = 0; i < NumAutoExpandCategories; ++i) + { + UNameIndex Element; + stream.read(reinterpret_cast(&Element), sizeof(Element)); + ss << "\tAutoExpandCategories[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; + AutoExpandCategories.push_back(Element); + } + stream.read(reinterpret_cast(&NumAutoCollapseCategories), sizeof(NumAutoCollapseCategories)); + ss << "\tNumAutoCollapseCategories = " << FormatHEX(NumAutoCollapseCategories) << " (" << NumAutoCollapseCategories << ")" << std::endl; + AutoCollapseCategories.clear(); + if (NumAutoCollapseCategories > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NumAutoCollapseCategories = 0; + for (unsigned i = 0; i < NumAutoCollapseCategories; ++i) + { + UNameIndex Element; + stream.read(reinterpret_cast(&Element), sizeof(Element)); + ss << "\tAutoCollapseCategories[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; + AutoCollapseCategories.push_back(Element); + } + stream.read(reinterpret_cast(&ForceScriptOrder), sizeof(ForceScriptOrder)); + ss << "\tForceScriptOrder = " << FormatHEX(ForceScriptOrder) << std::endl; + stream.read(reinterpret_cast(&NumClassGroups), sizeof(NumClassGroups)); + ss << "\tNumClassGroups = " << FormatHEX(NumClassGroups) << " (" << NumClassGroups << ")" << std::endl; + ClassGroups.clear(); + if (NumClassGroups > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NumClassGroups = 0; + for (unsigned i = 0; i < NumClassGroups; ++i) + { + UNameIndex Element; + stream.read(reinterpret_cast(&Element), sizeof(Element)); + ss << "\tClassGroups[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; + ClassGroups.push_back(Element); + } + stream.read(reinterpret_cast(&NativeClassNameLength), sizeof(NativeClassNameLength)); + ss << "\tNativeClassNameLength = " << FormatHEX(NativeClassNameLength) << std::endl; + if (NativeClassNameLength > info.GetExportEntry(ThisRef).SerialSize) /// bad data malloc error prevention + NativeClassNameLength = 0; + if (NativeClassNameLength > 0) + { + getline(stream, NativeClassName, '\0'); + ss << "\tNativeClassName = " << NativeClassName << std::endl; + } + stream.read(reinterpret_cast(&DLLBindName), sizeof(DLLBindName)); + ss << "\tDLLBindName = " << FormatHEX(DLLBindName) << " -> " << info.IndexToName(DLLBindName) << std::endl; + stream.read(reinterpret_cast(&DefaultRef), sizeof(DefaultRef)); + ss << "\tDefaultRef = " << FormatHEX((uint32_t)DefaultRef) << " -> " << info.ObjRefToName(DefaultRef) << std::endl; + return ss.str(); +} + +std::string UConst::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UField::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UConst:\n"; + stream.read(reinterpret_cast(&ValueLength), sizeof(ValueLength)); + ss << "\tValueLength = " << FormatHEX(ValueLength) << std::endl; + if (ValueLength > 0) + { + getline(stream, Value, '\0'); + ss << "\tValue = " << Value << std::endl; + } + return ss.str(); +} + +std::string UEnum::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UField::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UEnum:\n"; + stream.read(reinterpret_cast(&NumNames), sizeof(NumNames)); + ss << "\tNumNames = " << FormatHEX(NumNames) << " (" << NumNames << ")" << std::endl; + Names.clear(); + for (unsigned i = 0; i < NumNames; ++i) + { + UNameIndex Element; + stream.read(reinterpret_cast(&Element), sizeof(Element)); + ss << "\tNames[" << i << "]:\n"; + ss << "\t\t" << FormatHEX(Element) << " -> " << info.IndexToName(Element) << std::endl; + Names.push_back(Element); + } + return ss.str(); +} + +std::string UProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UField::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UProperty:\n"; + uint32_t tmpVal; + stream.read(reinterpret_cast(&tmpVal), sizeof(tmpVal)); + ArrayDim = tmpVal % (1 << 16); + ElementSize = tmpVal >> 16; + ss << "\tArrayDim = " << FormatHEX(ArrayDim) << " (" << ArrayDim << ")" << std::endl; + ss << "\tElementSize = " << FormatHEX(ElementSize) << " (" << ElementSize << ")" << std::endl; + FlagsOffset = stream.tellg(); + stream.read(reinterpret_cast(&PropertyFlagsL), sizeof(PropertyFlagsL)); + ss << "\tPropertyFlagsL = " << FormatHEX(PropertyFlagsL) << std::endl; + ss << FormatPropertyFlagsL(PropertyFlagsL); + stream.read(reinterpret_cast(&PropertyFlagsH), sizeof(PropertyFlagsH)); + ss << "\tPropertyFlagsH = " << FormatHEX(PropertyFlagsH) << std::endl; + ss << FormatPropertyFlagsH(PropertyFlagsH); + stream.read(reinterpret_cast(&CategoryIndex), sizeof(CategoryIndex)); + ss << "\tCategoryIndex = " << FormatHEX(CategoryIndex) << " -> " << info.IndexToName(CategoryIndex) << std::endl; + stream.read(reinterpret_cast(&ArrayEnumRef), sizeof(ArrayEnumRef)); + ss << "\tArrayEnumRef = " << FormatHEX((uint32_t)ArrayEnumRef) << " -> " << info.ObjRefToName(ArrayEnumRef) << std::endl; + if (PropertyFlagsL & (uint32_t)UPropertyFlagsL::Net) + { + stream.read(reinterpret_cast(&RepOffset), sizeof(RepOffset)); + ss << "\tRepOffset = " << FormatHEX(RepOffset) << std::endl; + } + return ss.str(); +} + +std::string UByteProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UByteProperty:\n"; + stream.read(reinterpret_cast(&EnumObjRef), sizeof(EnumObjRef)); + ss << "\tEnumObjRef = " << FormatHEX((uint32_t)EnumObjRef) << " -> " << info.ObjRefToName(EnumObjRef) << std::endl; + return ss.str(); +} + +std::string UObjectProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UObjectProperty:\n"; + stream.read(reinterpret_cast(&OtherObjRef), sizeof(OtherObjRef)); + ss << "\tOtherObjRef = " << FormatHEX((uint32_t)OtherObjRef) << " -> " << info.ObjRefToName(OtherObjRef) << std::endl; + return ss.str(); +} + +std::string UClassProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UObjectProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UClassProperty:\n"; + stream.read(reinterpret_cast(&ClassObjRef), sizeof(ClassObjRef)); + ss << "\tClassObjRef = " << FormatHEX((uint32_t)ClassObjRef) << " -> " << info.ObjRefToName(ClassObjRef) << std::endl; + return ss.str(); +} + +std::string UStructProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UStructProperty:\n"; + stream.read(reinterpret_cast(&StructObjRef), sizeof(StructObjRef)); + ss << "\tStructObjRef = " << FormatHEX((uint32_t)StructObjRef) << " -> " << info.ObjRefToName(StructObjRef) << std::endl; + return ss.str(); +} + +std::string UFixedArrayProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UFixedArrayProperty:\n"; + stream.read(reinterpret_cast(&InnerObjRef), sizeof(InnerObjRef)); + ss << "\tInnerObjRef = " << FormatHEX((uint32_t)InnerObjRef) << " -> " << info.ObjRefToName(InnerObjRef) << std::endl; + stream.read(reinterpret_cast(&Count), sizeof(Count)); + ss << "\tCount = " << FormatHEX(Count) << " (" << Count << ")" << std::endl; + return ss.str(); +} + +std::string UArrayProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UArrayProperty:\n"; + stream.read(reinterpret_cast(&InnerObjRef), sizeof(InnerObjRef)); + ss << "\tInnerObjRef = " << FormatHEX((uint32_t)InnerObjRef) << " -> " << info.ObjRefToName(InnerObjRef) << std::endl; + return ss.str(); +} + +std::string UDelegateProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UDelegateProperty:\n"; + stream.read(reinterpret_cast(&FunctionObjRef), sizeof(FunctionObjRef)); + ss << "\tFunctionObjRef = " << FormatHEX((uint32_t)FunctionObjRef) << " -> " << info.ObjRefToName(FunctionObjRef) << std::endl; + stream.read(reinterpret_cast(&DelegateObjRef), sizeof(DelegateObjRef)); + ss << "\tDelegateObjRef = " << FormatHEX((uint32_t)DelegateObjRef) << " -> " << info.ObjRefToName(DelegateObjRef) << std::endl; + return ss.str(); +} + +std::string UInterfaceProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UInterfaceProperty:\n"; + stream.read(reinterpret_cast(&InterfaceObjRef), sizeof(InterfaceObjRef)); + ss << "\tInterfaceObjRef = " << FormatHEX((uint32_t)InterfaceObjRef) << " -> " << info.ObjRefToName(InterfaceObjRef) << std::endl; + return ss.str(); +} + +std::string UMapProperty::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + ss << UProperty::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + ss << "UMapProperty:\n"; + stream.read(reinterpret_cast(&KeyObjRef), sizeof(KeyObjRef)); + ss << "\tKeyObjRef = " << FormatHEX((uint32_t)KeyObjRef) << " -> " << info.ObjRefToName(KeyObjRef) << std::endl; + stream.read(reinterpret_cast(&ValueObjRef), sizeof(ValueObjRef)); + ss << "\tValueObjRef = " << FormatHEX((uint32_t)ValueObjRef) << " -> " << info.ObjRefToName(ValueObjRef) << std::endl; + return ss.str(); +} + +std::string ULevel::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + UObjectReference A; + uint32_t NumActors; + ss << UObject::Deserialize(stream, info); + uint32_t pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); + ss << "ULevel:\n"; + stream.read(reinterpret_cast(&A), sizeof(A)); + ss << "\tLevel object: " << FormatHEX((uint32_t)A) << " -> " << info.ObjRefToName(A) << std::endl; + stream.read(reinterpret_cast(&NumActors), sizeof(NumActors)); + ss << "\tNum actors: " << FormatHEX(NumActors) << " = " << NumActors << std::endl; + stream.read(reinterpret_cast(&A), sizeof(A)); + ss << "\tWorldInfo object: " << FormatHEX((uint32_t)A) << " -> " << info.ObjRefToName(A) << std::endl; + ss << "\tActors:\n"; + for (unsigned i = 0; i < NumActors; ++i) + { + stream.read(reinterpret_cast(&A), sizeof(A)); + Actors.push_back(A); + ss << "\t\t" << FormatHEX((char*)&A, sizeof(A)) << "\t//\t" << FormatHEX((uint32_t)A) << " -> " << info.ObjRefToName(A) << std::endl; + } + pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); + ss << "Stream relative position (debug info): " << FormatHEX(pos) << " (" << pos << ")\n"; + ss << "Object unknown, can't deserialize!\n"; + return ss.str(); +} + +std::string UObjectUnknown::Deserialize(std::istream& stream, UPKInfo& info) +{ + std::ostringstream ss; + /// prevent crashes while deserializing components + if (info.GetExportEntry(ThisRef).Type.find("Component") != std::string::npos) + { + ss << "Can't deserialize Components!\n"; + return ss.str(); + } + /// prevent crashes while deserializing FX_ + if (info.GetExportEntry(ThisRef).Type.find("BodySetup") != std::string::npos) + { + ss << "Can't deserialize BodySetup!\n"; + return ss.str(); + } + /// to be on a safe side: don't deserialize unknown objects + if (TryUnsafe == true) + { + ss << UObject::Deserialize(stream, info); + uint32_t pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); + ss << "Stream relative position (debug info): " << FormatHEX(pos) << " (" << pos << ")\n"; + if (pos == info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); + } + ss << "UObjectUnknown:\n"; + ss << "\tObject unknown, can't deserialize!\n"; + return ss.str(); +} diff --git a/UObject.h b/UObject.h index 5eb0657..b1cd968 100644 --- a/UObject.h +++ b/UObject.h @@ -1,479 +1,479 @@ -#ifndef UOBJECT_H -#define UOBJECT_H - -#include -#include -#include -#include - -#include "UPKInfo.h" - -/// global type enumeration -enum class GlobalType -{ - None = 0, - UObject = 1, - UField = 2, - UConst = 3, - UEnum = 4, - UProperty = 5, - UByteProperty = 6, - UIntProperty = 7, - UBoolProperty = 8, - UFloatProperty = 9, - UObjectProperty = 10, - UClassProperty = 11, - UNameProperty = 12, - UStructProperty = 13, - UStrProperty = 14, - UArrayProperty = 15, - UStruct = 16, - UScriptStruct = 17, - UFunction = 18, - UState = 19, - UClass = 20, - UTextBuffer = 21, - UObjectUnknown = 22, - UFixedArrayProperty = 23, - UComponentProperty = 24, - UDelegateProperty = 25, - UInterfaceProperty = 26, - UMapProperty = 27, - ULevel = 28 -}; - -class UBulkDataMirror -{ -public: - UBulkDataMirror(): SavedBulkDataFlags(0), SavedElementCount(0), SavedBulkDataSizeOnDisk(0), SavedBulkDataOffsetInFile(0) {} - void SetBulkData(std::vector Data); - void SetFileOffset(size_t offset) { SavedBulkDataOffsetInFile = offset; } - std::string Serialize(); - size_t GetBulkDataRelOffset() { return 16; } -protected: - /// persistent - uint32_t SavedBulkDataFlags; - uint32_t SavedElementCount; - uint32_t SavedBulkDataSizeOnDisk; - uint32_t SavedBulkDataOffsetInFile; - std::vector BulkData; -}; - -class UDefaultProperty -{ -public: - UDefaultProperty(): Name("None"), Type("None"), OwnerRef(0), TryUnsafe(0), QuickMode(0) {} - ~UDefaultProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe = false, bool quick = false); - void Init(UObjectReference owner, bool unsafe = false, bool quick = false) { OwnerRef = owner; TryUnsafe = unsafe; QuickMode = quick; } - std::string GetName() { return Name; } - std::string DeserializeValue(std::istream& stream, UPKInfo& info); - std::string FindArrayType(std::string ArrName, std::istream& stream, UPKInfo& info); - std::string GuessArrayType(std::string ArrName); -protected: - /// persistent - UNameIndex NameIdx; - UNameIndex TypeIdx; - uint32_t PropertySize; - uint32_t ArrayIdx; - uint8_t BoolValue; /// for BoolProperty only - UNameIndex InnerNameIdx; /// for StructProperty and ByteProperty only - std::vector InnerValue; - /// memory - std::string Name; - std::string Type; - UObjectReference OwnerRef; - bool TryUnsafe; - bool QuickMode; -}; - -class UDefaultPropertiesList -{ -public: - UDefaultPropertiesList() {} - ~UDefaultPropertiesList() {} - std::string Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe = false, bool quick = false); -protected: - std::vector DefaultProperties; - size_t PropertyOffset; - size_t PropertySize; -}; - -/// parent class of all Unreal objects -class UObject -{ -public: - UObject(): Type(GlobalType::UObject), ThisRef(0), FlagsOffset(0), TryUnsafe(0), QuickMode(0) {} - virtual ~UObject() {} - virtual std::string Deserialize(std::istream& stream, UPKInfo& info); - void SetRef(UObjectReference thisRef) { ThisRef = thisRef; } - void SetUnsafe(bool val) { TryUnsafe = val; } - void SetQuickMode(bool val) { QuickMode = val; } - virtual bool IsStructure() { return false; } - virtual bool IsProperty() { return false; } - virtual bool IsState() { return false; } -protected: - /// persistent - UObjectReference ObjRef; /// Next object (Linker-related) - UDefaultPropertiesList DefaultProperties; /// for non-Class objects only - /// memory - GlobalType Type; - UObjectReference ThisRef; - size_t FlagsOffset; - bool TryUnsafe; - bool QuickMode; -}; - -class UObjectNone: public UObject -{ -public: - UObjectNone() { Type = GlobalType::None; } - ~UObjectNone() {} -protected: -}; - -class UField: public UObject -{ -public: - UField() { Type = GlobalType::UField; } - ~UField() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); - UObjectReference GetNextRef() { return NextRef; } - size_t GetNextRefOffset() { return NextRefOffset; } -protected: - /// persistent - UObjectReference NextRef; - UObjectReference ParentRef; /// for Struct objects only - /// memory - size_t FieldOffset; - size_t FieldSize; - size_t NextRefOffset; -}; - -class UStruct: public UField -{ -public: - UStruct() { Type = GlobalType::UStruct; } - ~UStruct() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); - bool IsStructure() { return true; } - UObjectReference GetFirstChildRef() { return FirstChildRef; } - uint32_t GetScriptSerialSize() { return ScriptSerialSize; } - uint32_t GetScriptMemorySize() { return ScriptMemorySize; } - size_t GetScriptOffset() { return ScriptOffset; } - size_t GetFirstChildRefOffset() { return FirstChildRefOffset; } -protected: - /// persistent - UObjectReference ScriptTextRef; - UObjectReference FirstChildRef; - UObjectReference CppTextRef; - uint32_t Line; - uint32_t TextPos; - uint32_t ScriptMemorySize; - uint32_t ScriptSerialSize; - std::vector DataScript; - /// memory - size_t StructOffset; - size_t StructSize; - size_t ScriptOffset; - size_t FirstChildRefOffset; -}; - -class UFunction: public UStruct -{ -public: - UFunction() { Type = GlobalType::UFunction; } - ~UFunction() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - uint16_t NativeToken; - uint8_t OperPrecedence; - uint32_t FunctionFlags; - uint16_t RepOffset; - UNameIndex NameIdx; - /// memory - size_t FunctionOffset; - size_t FunctionSize; -}; - -class UScriptStruct: public UStruct -{ -public: - UScriptStruct() { Type = GlobalType::UScriptStruct; } - ~UScriptStruct() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - uint32_t StructFlags; - UDefaultPropertiesList StructDefaultProperties; - /// memory - size_t ScriptStructOffset; - size_t ScriptStructSize; -}; - -class UState: public UStruct -{ -public: - UState() { Type = GlobalType::UState; } - ~UState() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); - bool IsState() { return true; } -protected: - /// persistent - uint32_t ProbeMask; - uint16_t LabelTableOffset; - uint32_t StateFlags; - uint32_t StateMapSize; - std::vector > StateMap; - /// memory - size_t StateOffset; - size_t StateSize; -}; - -class UClass: public UState -{ -public: - UClass() { Type = GlobalType::UClass; } - ~UClass() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - uint32_t ClassFlags; - UObjectReference WithinRef; - UNameIndex ConfigNameIdx; - uint32_t NumComponents; - std::vector > Components; - uint32_t NumInterfaces; - std::vector > Interfaces; - uint32_t NumDontSortCategories; - std::vector DontSortCategories; - uint32_t NumHideCategories; - std::vector HideCategories; - uint32_t NumAutoExpandCategories; - std::vector AutoExpandCategories; - uint32_t NumAutoCollapseCategories; - std::vector AutoCollapseCategories; - uint32_t ForceScriptOrder; - uint32_t NumClassGroups; - std::vector ClassGroups; - uint32_t NativeClassNameLength; - std::string NativeClassName; - UNameIndex DLLBindName; - UObjectReference DefaultRef; -}; - -class UConst: public UField -{ -public: - UConst() { Type = GlobalType::UConst; } - ~UConst() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - uint32_t ValueLength; - std::string Value; -}; - -class UEnum: public UField -{ -public: - UEnum() { Type = GlobalType::UEnum; } - ~UEnum() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - uint32_t NumNames; - std::vector Names; -}; - -class UProperty: public UField -{ -public: - UProperty() { Type = GlobalType::UProperty; } - ~UProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); - bool IsProperty() { return true; } -protected: - /// persistent - uint16_t ArrayDim; - uint16_t ElementSize; - uint32_t PropertyFlagsL; - uint32_t PropertyFlagsH; - UNameIndex CategoryIndex; - UObjectReference ArrayEnumRef; - uint16_t RepOffset; -}; - -class UByteProperty: public UProperty -{ -public: - UByteProperty() { Type = GlobalType::UByteProperty; } - ~UByteProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference EnumObjRef; -}; - -class UIntProperty: public UProperty -{ -public: - UIntProperty() { Type = GlobalType::UIntProperty; } - ~UIntProperty() {} -protected: -}; - -class UBoolProperty: public UProperty -{ -public: - UBoolProperty() { Type = GlobalType::UBoolProperty; } - ~UBoolProperty() {} -protected: -}; - -class UFloatProperty: public UProperty -{ -public: - UFloatProperty() { Type = GlobalType::UFloatProperty; } - ~UFloatProperty() {} -protected: -}; - -class UObjectProperty: public UProperty -{ -public: - UObjectProperty() { Type = GlobalType::UObjectProperty; } - ~UObjectProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference OtherObjRef; -}; - -class UClassProperty: public UObjectProperty -{ -public: - UClassProperty() { Type = GlobalType::UClassProperty; } - ~UClassProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference ClassObjRef; -}; - -class UComponentProperty: public UObjectProperty -{ -public: - UComponentProperty() { Type = GlobalType::UComponentProperty; } - ~UComponentProperty() {} -protected: -}; - -class UNameProperty: public UProperty -{ -public: - UNameProperty() { Type = GlobalType::UNameProperty; } - ~UNameProperty() {} -protected: -}; - -class UStructProperty: public UProperty -{ -public: - UStructProperty() { Type = GlobalType::UStructProperty; } - ~UStructProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference StructObjRef; -}; - -class UStrProperty: public UProperty -{ -public: - UStrProperty() { Type = GlobalType::UStrProperty; } - ~UStrProperty() {} -protected: -}; - -class UFixedArrayProperty: public UProperty -{ -public: - UFixedArrayProperty() { Type = GlobalType::UFixedArrayProperty; } - ~UFixedArrayProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference InnerObjRef; - uint32_t Count; -}; - -class UArrayProperty: public UProperty -{ -public: - UArrayProperty() { Type = GlobalType::UArrayProperty; } - ~UArrayProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); - UObjectReference GetInner() { return InnerObjRef; } -protected: - /// persistent - UObjectReference InnerObjRef; -}; - -class UDelegateProperty: public UProperty -{ -public: - UDelegateProperty() { Type = GlobalType::UDelegateProperty; } - ~UDelegateProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference FunctionObjRef; - UObjectReference DelegateObjRef; -}; - -class UInterfaceProperty: public UProperty -{ -public: - UInterfaceProperty() { Type = GlobalType::UInterfaceProperty; } - ~UInterfaceProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference InterfaceObjRef; -}; - -class UMapProperty: public UProperty -{ -public: - UMapProperty() { Type = GlobalType::UMapProperty; } - ~UMapProperty() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// persistent - UObjectReference KeyObjRef; - UObjectReference ValueObjRef; -}; - -class ULevel: public UObject -{ -public: - ULevel() { Type = GlobalType::ULevel; } - ~ULevel() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: - /// database - std::vector Actors; -}; - -class UObjectUnknown: public UObject -{ -public: - UObjectUnknown() { Type = GlobalType::UObjectUnknown; } - ~UObjectUnknown() {} - std::string Deserialize(std::istream& stream, UPKInfo& info); -protected: -}; - -#endif // UOBJECT_H +#ifndef UOBJECT_H +#define UOBJECT_H + +#include +#include +#include +#include + +#include "UPKInfo.h" + +/// global type enumeration +enum class GlobalType +{ + None = 0, + UObject = 1, + UField = 2, + UConst = 3, + UEnum = 4, + UProperty = 5, + UByteProperty = 6, + UIntProperty = 7, + UBoolProperty = 8, + UFloatProperty = 9, + UObjectProperty = 10, + UClassProperty = 11, + UNameProperty = 12, + UStructProperty = 13, + UStrProperty = 14, + UArrayProperty = 15, + UStruct = 16, + UScriptStruct = 17, + UFunction = 18, + UState = 19, + UClass = 20, + UTextBuffer = 21, + UObjectUnknown = 22, + UFixedArrayProperty = 23, + UComponentProperty = 24, + UDelegateProperty = 25, + UInterfaceProperty = 26, + UMapProperty = 27, + ULevel = 28 +}; + +class UBulkDataMirror +{ +public: + UBulkDataMirror(): SavedBulkDataFlags(0), SavedElementCount(0), SavedBulkDataSizeOnDisk(0), SavedBulkDataOffsetInFile(0) {} + void SetBulkData(std::vector Data); + void SetFileOffset(size_t offset) { SavedBulkDataOffsetInFile = offset; } + std::string Serialize(); + size_t GetBulkDataRelOffset() { return 16; } +protected: + /// persistent + uint32_t SavedBulkDataFlags; + uint32_t SavedElementCount; + uint32_t SavedBulkDataSizeOnDisk; + uint32_t SavedBulkDataOffsetInFile; + std::vector BulkData; +}; + +class UDefaultProperty +{ +public: + UDefaultProperty(): Name("None"), Type("None"), OwnerRef(0), TryUnsafe(0), QuickMode(0) {} + ~UDefaultProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe = false, bool quick = false); + void Init(UObjectReference owner, bool unsafe = false, bool quick = false) { OwnerRef = owner; TryUnsafe = unsafe; QuickMode = quick; } + std::string GetName() { return Name; } + std::string DeserializeValue(std::istream& stream, UPKInfo& info); + std::string FindArrayType(std::string ArrName, std::istream& stream, UPKInfo& info); + std::string GuessArrayType(std::string ArrName); +protected: + /// persistent + UNameIndex NameIdx; + UNameIndex TypeIdx; + uint32_t PropertySize; + uint32_t ArrayIdx; + uint8_t BoolValue; /// for BoolProperty only + UNameIndex InnerNameIdx; /// for StructProperty and ByteProperty only + std::vector InnerValue; + /// memory + std::string Name; + std::string Type; + UObjectReference OwnerRef; + bool TryUnsafe; + bool QuickMode; +}; + +class UDefaultPropertiesList +{ +public: + UDefaultPropertiesList() {} + ~UDefaultPropertiesList() {} + std::string Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe = false, bool quick = false); +protected: + std::vector DefaultProperties; + size_t PropertyOffset; + size_t PropertySize; +}; + +/// parent class of all Unreal objects +class UObject +{ +public: + UObject(): Type(GlobalType::UObject), ThisRef(0), FlagsOffset(0), TryUnsafe(0), QuickMode(0) {} + virtual ~UObject() {} + virtual std::string Deserialize(std::istream& stream, UPKInfo& info); + void SetRef(UObjectReference thisRef) { ThisRef = thisRef; } + void SetUnsafe(bool val) { TryUnsafe = val; } + void SetQuickMode(bool val) { QuickMode = val; } + virtual bool IsStructure() { return false; } + virtual bool IsProperty() { return false; } + virtual bool IsState() { return false; } +protected: + /// persistent + UObjectReference ObjRef; /// Next object (Linker-related) + UDefaultPropertiesList DefaultProperties; /// for non-Class objects only + /// memory + GlobalType Type; + UObjectReference ThisRef; + size_t FlagsOffset; + bool TryUnsafe; + bool QuickMode; +}; + +class UObjectNone: public UObject +{ +public: + UObjectNone() { Type = GlobalType::None; } + ~UObjectNone() {} +protected: +}; + +class UField: public UObject +{ +public: + UField() { Type = GlobalType::UField; } + ~UField() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); + UObjectReference GetNextRef() { return NextRef; } + size_t GetNextRefOffset() { return NextRefOffset; } +protected: + /// persistent + UObjectReference NextRef; + UObjectReference ParentRef; /// for Struct objects only + /// memory + size_t FieldOffset; + size_t FieldSize; + size_t NextRefOffset; +}; + +class UStruct: public UField +{ +public: + UStruct() { Type = GlobalType::UStruct; } + ~UStruct() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); + bool IsStructure() { return true; } + UObjectReference GetFirstChildRef() { return FirstChildRef; } + uint32_t GetScriptSerialSize() { return ScriptSerialSize; } + uint32_t GetScriptMemorySize() { return ScriptMemorySize; } + size_t GetScriptOffset() { return ScriptOffset; } + size_t GetFirstChildRefOffset() { return FirstChildRefOffset; } +protected: + /// persistent + UObjectReference ScriptTextRef; + UObjectReference FirstChildRef; + UObjectReference CppTextRef; + uint32_t Line; + uint32_t TextPos; + uint32_t ScriptMemorySize; + uint32_t ScriptSerialSize; + std::vector DataScript; + /// memory + size_t StructOffset; + size_t StructSize; + size_t ScriptOffset; + size_t FirstChildRefOffset; +}; + +class UFunction: public UStruct +{ +public: + UFunction() { Type = GlobalType::UFunction; } + ~UFunction() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + uint16_t NativeToken; + uint8_t OperPrecedence; + uint32_t FunctionFlags; + uint16_t RepOffset; + UNameIndex NameIdx; + /// memory + size_t FunctionOffset; + size_t FunctionSize; +}; + +class UScriptStruct: public UStruct +{ +public: + UScriptStruct() { Type = GlobalType::UScriptStruct; } + ~UScriptStruct() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + uint32_t StructFlags; + UDefaultPropertiesList StructDefaultProperties; + /// memory + size_t ScriptStructOffset; + size_t ScriptStructSize; +}; + +class UState: public UStruct +{ +public: + UState() { Type = GlobalType::UState; } + ~UState() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); + bool IsState() { return true; } +protected: + /// persistent + uint32_t ProbeMask; + uint16_t LabelTableOffset; + uint32_t StateFlags; + uint32_t StateMapSize; + std::vector > StateMap; + /// memory + size_t StateOffset; + size_t StateSize; +}; + +class UClass: public UState +{ +public: + UClass() { Type = GlobalType::UClass; } + ~UClass() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + uint32_t ClassFlags; + UObjectReference WithinRef; + UNameIndex ConfigNameIdx; + uint32_t NumComponents; + std::vector > Components; + uint32_t NumInterfaces; + std::vector > Interfaces; + uint32_t NumDontSortCategories; + std::vector DontSortCategories; + uint32_t NumHideCategories; + std::vector HideCategories; + uint32_t NumAutoExpandCategories; + std::vector AutoExpandCategories; + uint32_t NumAutoCollapseCategories; + std::vector AutoCollapseCategories; + uint32_t ForceScriptOrder; + uint32_t NumClassGroups; + std::vector ClassGroups; + uint32_t NativeClassNameLength; + std::string NativeClassName; + UNameIndex DLLBindName; + UObjectReference DefaultRef; +}; + +class UConst: public UField +{ +public: + UConst() { Type = GlobalType::UConst; } + ~UConst() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + uint32_t ValueLength; + std::string Value; +}; + +class UEnum: public UField +{ +public: + UEnum() { Type = GlobalType::UEnum; } + ~UEnum() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + uint32_t NumNames; + std::vector Names; +}; + +class UProperty: public UField +{ +public: + UProperty() { Type = GlobalType::UProperty; } + ~UProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); + bool IsProperty() { return true; } +protected: + /// persistent + uint16_t ArrayDim; + uint16_t ElementSize; + uint32_t PropertyFlagsL; + uint32_t PropertyFlagsH; + UNameIndex CategoryIndex; + UObjectReference ArrayEnumRef; + uint16_t RepOffset; +}; + +class UByteProperty: public UProperty +{ +public: + UByteProperty() { Type = GlobalType::UByteProperty; } + ~UByteProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference EnumObjRef; +}; + +class UIntProperty: public UProperty +{ +public: + UIntProperty() { Type = GlobalType::UIntProperty; } + ~UIntProperty() {} +protected: +}; + +class UBoolProperty: public UProperty +{ +public: + UBoolProperty() { Type = GlobalType::UBoolProperty; } + ~UBoolProperty() {} +protected: +}; + +class UFloatProperty: public UProperty +{ +public: + UFloatProperty() { Type = GlobalType::UFloatProperty; } + ~UFloatProperty() {} +protected: +}; + +class UObjectProperty: public UProperty +{ +public: + UObjectProperty() { Type = GlobalType::UObjectProperty; } + ~UObjectProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference OtherObjRef; +}; + +class UClassProperty: public UObjectProperty +{ +public: + UClassProperty() { Type = GlobalType::UClassProperty; } + ~UClassProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference ClassObjRef; +}; + +class UComponentProperty: public UObjectProperty +{ +public: + UComponentProperty() { Type = GlobalType::UComponentProperty; } + ~UComponentProperty() {} +protected: +}; + +class UNameProperty: public UProperty +{ +public: + UNameProperty() { Type = GlobalType::UNameProperty; } + ~UNameProperty() {} +protected: +}; + +class UStructProperty: public UProperty +{ +public: + UStructProperty() { Type = GlobalType::UStructProperty; } + ~UStructProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference StructObjRef; +}; + +class UStrProperty: public UProperty +{ +public: + UStrProperty() { Type = GlobalType::UStrProperty; } + ~UStrProperty() {} +protected: +}; + +class UFixedArrayProperty: public UProperty +{ +public: + UFixedArrayProperty() { Type = GlobalType::UFixedArrayProperty; } + ~UFixedArrayProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference InnerObjRef; + uint32_t Count; +}; + +class UArrayProperty: public UProperty +{ +public: + UArrayProperty() { Type = GlobalType::UArrayProperty; } + ~UArrayProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); + UObjectReference GetInner() { return InnerObjRef; } +protected: + /// persistent + UObjectReference InnerObjRef; +}; + +class UDelegateProperty: public UProperty +{ +public: + UDelegateProperty() { Type = GlobalType::UDelegateProperty; } + ~UDelegateProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference FunctionObjRef; + UObjectReference DelegateObjRef; +}; + +class UInterfaceProperty: public UProperty +{ +public: + UInterfaceProperty() { Type = GlobalType::UInterfaceProperty; } + ~UInterfaceProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference InterfaceObjRef; +}; + +class UMapProperty: public UProperty +{ +public: + UMapProperty() { Type = GlobalType::UMapProperty; } + ~UMapProperty() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// persistent + UObjectReference KeyObjRef; + UObjectReference ValueObjRef; +}; + +class ULevel: public UObject +{ +public: + ULevel() { Type = GlobalType::ULevel; } + ~ULevel() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: + /// database + std::vector Actors; +}; + +class UObjectUnknown: public UObject +{ +public: + UObjectUnknown() { Type = GlobalType::UObjectUnknown; } + ~UObjectUnknown() {} + std::string Deserialize(std::istream& stream, UPKInfo& info); +protected: +}; + +#endif // UOBJECT_H diff --git a/UObjectFactory.cpp b/UObjectFactory.cpp index 48795cb..1c49ef6 100644 --- a/UObjectFactory.cpp +++ b/UObjectFactory.cpp @@ -1,242 +1,242 @@ -#include "UObjectFactory.h" - -GlobalType UObjectFactory::NameToType(std::string name) -{ - if (name == "None") - { - return GlobalType::None; - } - else if (name == "Object") - { - return GlobalType::UObject; - } - else if (name == "Field") - { - return GlobalType::UField; - } - else if (name == "Const") - { - return GlobalType::UConst; - } - else if (name == "Enum") - { - return GlobalType::UEnum; - } - else if (name == "Property") - { - return GlobalType::UProperty; - } - else if (name == "ByteProperty") - { - return GlobalType::UByteProperty; - } - else if (name == "IntProperty") - { - return GlobalType::UIntProperty; - } - else if (name == "BoolProperty") - { - return GlobalType::UBoolProperty; - } - else if (name == "FloatProperty") - { - return GlobalType::UFloatProperty; - } - else if (name == "ObjectProperty") - { - return GlobalType::UObjectProperty; - } - else if (name == "ClassProperty") - { - return GlobalType::UClassProperty; - } - else if (name == "ComponentProperty") - { - return GlobalType::UComponentProperty; - } - else if (name == "NameProperty") - { - return GlobalType::UNameProperty; - } - else if (name == "StructProperty") - { - return GlobalType::UStructProperty; - } - else if (name == "StrProperty") - { - return GlobalType::UStrProperty; - } - else if (name == "ArrayProperty") - { - return GlobalType::UArrayProperty; - } - else if (name == "FixedArrayProperty") - { - return GlobalType::UFixedArrayProperty; - } - else if (name == "DelegateProperty") - { - return GlobalType::UDelegateProperty; - } - else if (name == "InterfaceProperty") - { - return GlobalType::UInterfaceProperty; - } - else if (name == "MapProperty") - { - return GlobalType::UMapProperty; - } - else if (name == "Struct") - { - return GlobalType::UStruct; - } - else if (name == "ScriptStruct") - { - return GlobalType::UScriptStruct; - } - else if (name == "Function") - { - return GlobalType::UFunction; - } - else if (name == "State") - { - return GlobalType::UState; - } - else if (name == "Class") - { - return GlobalType::UClass; - } - else if (name == "TextBuffer") - { - return GlobalType::UTextBuffer; - } - else if (name == "Level") - { - return GlobalType::ULevel; - } - else - { - return GlobalType::UObjectUnknown; - } -} - -UObject* UObjectFactory::Create(std::string name) -{ - return Create(NameToType(name)); -} - -UObject* UObjectFactory::Create(GlobalType Type) -{ - if (Type == GlobalType::None) - { - return new UObjectNone; /// special UE null-object - } - else if (Type == GlobalType::UObject) - { - return new UObject; - } - else if (Type == GlobalType::UField) - { - return new UField; - } - else if (Type == GlobalType::UConst) - { - return new UConst; - } - else if (Type == GlobalType::UEnum) - { - return new UEnum; - } - else if (Type == GlobalType::UProperty) - { - return new UProperty; - } - else if (Type == GlobalType::UByteProperty) - { - return new UByteProperty; - } - else if (Type == GlobalType::UIntProperty) - { - return new UIntProperty; - } - else if (Type == GlobalType::UBoolProperty) - { - return new UBoolProperty; - } - else if (Type == GlobalType::UFloatProperty) - { - return new UFloatProperty; - } - else if (Type == GlobalType::UObjectProperty) - { - return new UObjectProperty; - } - else if (Type == GlobalType::UClassProperty) - { - return new UClassProperty; - } - else if (Type == GlobalType::UComponentProperty) - { - return new UComponentProperty; - } - else if (Type == GlobalType::UNameProperty) - { - return new UNameProperty; - } - else if (Type == GlobalType::UStructProperty) - { - return new UStructProperty; - } - else if (Type == GlobalType::UStrProperty) - { - return new UStrProperty; - } - else if (Type == GlobalType::UArrayProperty) - { - return new UArrayProperty; - } - else if (Type == GlobalType::UFixedArrayProperty) - { - return new UFixedArrayProperty; - } - else if (Type == GlobalType::UDelegateProperty) - { - return new UDelegateProperty; - } - else if (Type == GlobalType::UInterfaceProperty) - { - return new UInterfaceProperty; - } - else if (Type == GlobalType::UMapProperty) - { - return new UMapProperty; - } - else if (Type == GlobalType::UStruct) - { - return new UStruct; - } - else if (Type == GlobalType::UScriptStruct) - { - return new UScriptStruct; - } - else if (Type == GlobalType::UFunction) - { - return new UFunction; - } - else if (Type == GlobalType::UState) - { - return new UState; - } - else if (Type == GlobalType::UClass) - { - return new UClass; - } - else if (Type == GlobalType::ULevel) - { - return new ULevel; - } - else - { - return new UObjectUnknown; /// special unknown object - } -} +#include "UObjectFactory.h" + +GlobalType UObjectFactory::NameToType(std::string name) +{ + if (name == "None") + { + return GlobalType::None; + } + else if (name == "Object") + { + return GlobalType::UObject; + } + else if (name == "Field") + { + return GlobalType::UField; + } + else if (name == "Const") + { + return GlobalType::UConst; + } + else if (name == "Enum") + { + return GlobalType::UEnum; + } + else if (name == "Property") + { + return GlobalType::UProperty; + } + else if (name == "ByteProperty") + { + return GlobalType::UByteProperty; + } + else if (name == "IntProperty") + { + return GlobalType::UIntProperty; + } + else if (name == "BoolProperty") + { + return GlobalType::UBoolProperty; + } + else if (name == "FloatProperty") + { + return GlobalType::UFloatProperty; + } + else if (name == "ObjectProperty") + { + return GlobalType::UObjectProperty; + } + else if (name == "ClassProperty") + { + return GlobalType::UClassProperty; + } + else if (name == "ComponentProperty") + { + return GlobalType::UComponentProperty; + } + else if (name == "NameProperty") + { + return GlobalType::UNameProperty; + } + else if (name == "StructProperty") + { + return GlobalType::UStructProperty; + } + else if (name == "StrProperty") + { + return GlobalType::UStrProperty; + } + else if (name == "ArrayProperty") + { + return GlobalType::UArrayProperty; + } + else if (name == "FixedArrayProperty") + { + return GlobalType::UFixedArrayProperty; + } + else if (name == "DelegateProperty") + { + return GlobalType::UDelegateProperty; + } + else if (name == "InterfaceProperty") + { + return GlobalType::UInterfaceProperty; + } + else if (name == "MapProperty") + { + return GlobalType::UMapProperty; + } + else if (name == "Struct") + { + return GlobalType::UStruct; + } + else if (name == "ScriptStruct") + { + return GlobalType::UScriptStruct; + } + else if (name == "Function") + { + return GlobalType::UFunction; + } + else if (name == "State") + { + return GlobalType::UState; + } + else if (name == "Class") + { + return GlobalType::UClass; + } + else if (name == "TextBuffer") + { + return GlobalType::UTextBuffer; + } + else if (name == "Level") + { + return GlobalType::ULevel; + } + else + { + return GlobalType::UObjectUnknown; + } +} + +UObject* UObjectFactory::Create(std::string name) +{ + return Create(NameToType(name)); +} + +UObject* UObjectFactory::Create(GlobalType Type) +{ + if (Type == GlobalType::None) + { + return new UObjectNone; /// special UE null-object + } + else if (Type == GlobalType::UObject) + { + return new UObject; + } + else if (Type == GlobalType::UField) + { + return new UField; + } + else if (Type == GlobalType::UConst) + { + return new UConst; + } + else if (Type == GlobalType::UEnum) + { + return new UEnum; + } + else if (Type == GlobalType::UProperty) + { + return new UProperty; + } + else if (Type == GlobalType::UByteProperty) + { + return new UByteProperty; + } + else if (Type == GlobalType::UIntProperty) + { + return new UIntProperty; + } + else if (Type == GlobalType::UBoolProperty) + { + return new UBoolProperty; + } + else if (Type == GlobalType::UFloatProperty) + { + return new UFloatProperty; + } + else if (Type == GlobalType::UObjectProperty) + { + return new UObjectProperty; + } + else if (Type == GlobalType::UClassProperty) + { + return new UClassProperty; + } + else if (Type == GlobalType::UComponentProperty) + { + return new UComponentProperty; + } + else if (Type == GlobalType::UNameProperty) + { + return new UNameProperty; + } + else if (Type == GlobalType::UStructProperty) + { + return new UStructProperty; + } + else if (Type == GlobalType::UStrProperty) + { + return new UStrProperty; + } + else if (Type == GlobalType::UArrayProperty) + { + return new UArrayProperty; + } + else if (Type == GlobalType::UFixedArrayProperty) + { + return new UFixedArrayProperty; + } + else if (Type == GlobalType::UDelegateProperty) + { + return new UDelegateProperty; + } + else if (Type == GlobalType::UInterfaceProperty) + { + return new UInterfaceProperty; + } + else if (Type == GlobalType::UMapProperty) + { + return new UMapProperty; + } + else if (Type == GlobalType::UStruct) + { + return new UStruct; + } + else if (Type == GlobalType::UScriptStruct) + { + return new UScriptStruct; + } + else if (Type == GlobalType::UFunction) + { + return new UFunction; + } + else if (Type == GlobalType::UState) + { + return new UState; + } + else if (Type == GlobalType::UClass) + { + return new UClass; + } + else if (Type == GlobalType::ULevel) + { + return new ULevel; + } + else + { + return new UObjectUnknown; /// special unknown object + } +} diff --git a/UObjectFactory.h b/UObjectFactory.h index 7518d47..5e7a00d 100644 --- a/UObjectFactory.h +++ b/UObjectFactory.h @@ -1,17 +1,17 @@ -#ifndef UOBJECTFACTORY_H -#define UOBJECTFACTORY_H - -#include "UObject.h" - -/// object factory to create Unreal objects of specified type -class UObjectFactory -{ - public: - UObjectFactory() {}; - ~UObjectFactory() {}; - static UObject* Create(GlobalType Type); - static UObject* Create(std::string name); - static GlobalType NameToType(std::string name); -}; - -#endif // UOBJECTFACTORY_H +#ifndef UOBJECTFACTORY_H +#define UOBJECTFACTORY_H + +#include "UObject.h" + +/// object factory to create Unreal objects of specified type +class UObjectFactory +{ + public: + UObjectFactory() {}; + ~UObjectFactory() {}; + static UObject* Create(GlobalType Type); + static UObject* Create(std::string name); + static GlobalType NameToType(std::string name); +}; + +#endif // UOBJECTFACTORY_H diff --git a/UPKInfo.h b/UPKInfo.h index 6322a3c..797a54d 100644 --- a/UPKInfo.h +++ b/UPKInfo.h @@ -1,240 +1,240 @@ -/// -/// This is the class to extract header information from UPK files: -/// summary, compressed chunks, name table, import table, export table and -/// depends table -/// -#ifndef UPKINFO_H -#define UPKINFO_H - -#include -#include - -#include "UFlags.h" - -enum class UPKReadErrors -{ - NoErrors = 0, - FileError, - BadSignature, - BadVersion, - IsCompressed -}; - -typedef int32_t UObjectReference; - -struct FGuid -{ - /// persistent - uint32_t GUID_A; - uint32_t GUID_B; - uint32_t GUID_C; - uint32_t GUID_D; -}; - -struct FGenerationInfo -{ - int32_t ExportCount; - int32_t NameCount; - int32_t NetObjectCount; -}; - -struct FCompressedChunkBlock -{ - uint32_t CompressedSize; - uint32_t UncompressedSize; -}; - -struct FCompressedChunkHeader -{ - uint32_t Signature; // equals to PACKAGE_FILE_TAG (0x9E2A83C1) - uint32_t BlockSize; // maximal size of uncompressed block, always the same - uint32_t CompressedSize; - uint32_t UncompressedSize; - uint32_t NumBlocks; - std::vector Blocks; -}; - -struct FCompressedChunk -{ - uint32_t UncompressedOffset; - uint32_t UncompressedSize; - uint32_t CompressedOffset; - uint32_t CompressedSize; -}; - -struct UNameIndex -{ - uint32_t NameTableIdx; - uint32_t Numeric; -}; - -struct FPackageFileSummary -{ - /// persistent - uint32_t Signature; - uint16_t Version; - uint16_t LicenseeVersion; - uint32_t HeaderSize; - int32_t FolderNameLength; - std::string FolderName; - uint32_t PackageFlags; - uint32_t NameCount; - uint32_t NameOffset; - uint32_t ExportCount; - uint32_t ExportOffset; - uint32_t ImportCount; - uint32_t ImportOffset; - uint32_t DependsOffset; - uint32_t SerialOffset; - uint32_t Unknown2; - uint32_t Unknown3; - uint32_t Unknown4; - FGuid GUID; - uint32_t GenerationsCount; - std::vector Generations; - uint32_t EngineVersion; - uint32_t CookerVersion; - uint32_t CompressionFlags; - uint32_t NumCompressedChunks; - std::vector CompressedChunks; - std::vector UnknownDataChunk; - /// memory - size_t HeaderSizeOffset; +/// +/// This is the class to extract header information from UPK files: +/// summary, compressed chunks, name table, import table, export table and +/// depends table +/// +#ifndef UPKINFO_H +#define UPKINFO_H + +#include +#include + +#include "UFlags.h" + +enum class UPKReadErrors +{ + NoErrors = 0, + FileError, + BadSignature, + BadVersion, + IsCompressed +}; + +typedef int32_t UObjectReference; + +struct FGuid +{ + /// persistent + uint32_t GUID_A; + uint32_t GUID_B; + uint32_t GUID_C; + uint32_t GUID_D; +}; + +struct FGenerationInfo +{ + int32_t ExportCount; + int32_t NameCount; + int32_t NetObjectCount; +}; + +struct FCompressedChunkBlock +{ + uint32_t CompressedSize; + uint32_t UncompressedSize; +}; + +struct FCompressedChunkHeader +{ + uint32_t Signature; // equals to PACKAGE_FILE_TAG (0x9E2A83C1) + uint32_t BlockSize; // maximal size of uncompressed block, always the same + uint32_t CompressedSize; + uint32_t UncompressedSize; + uint32_t NumBlocks; + std::vector Blocks; +}; + +struct FCompressedChunk +{ + uint32_t UncompressedOffset; + uint32_t UncompressedSize; + uint32_t CompressedOffset; + uint32_t CompressedSize; +}; + +struct UNameIndex +{ + uint32_t NameTableIdx; + uint32_t Numeric; +}; + +struct FPackageFileSummary +{ + /// persistent + uint32_t Signature; + uint16_t Version; + uint16_t LicenseeVersion; + uint32_t HeaderSize; + int32_t FolderNameLength; + std::string FolderName; + uint32_t PackageFlags; + uint32_t NameCount; + uint32_t NameOffset; + uint32_t ExportCount; + uint32_t ExportOffset; + uint32_t ImportCount; + uint32_t ImportOffset; + uint32_t DependsOffset; + uint32_t SerialOffset; + uint32_t Unknown2; + uint32_t Unknown3; + uint32_t Unknown4; + FGuid GUID; + uint32_t GenerationsCount; + std::vector Generations; + uint32_t EngineVersion; + uint32_t CookerVersion; + uint32_t CompressionFlags; + uint32_t NumCompressedChunks; + std::vector CompressedChunks; + std::vector UnknownDataChunk; + /// memory + size_t HeaderSizeOffset; size_t NameCountOffset; - size_t UPKFileSize; -}; - -struct FNameEntry -{ - /// persistent - int32_t NameLength; - std::string Name; - uint32_t NameFlagsL; - uint32_t NameFlagsH; - /// memory - size_t EntryOffset; - size_t EntrySize; -}; - -struct FObjectImport -{ - /// persistent - UNameIndex PackageIdx; - UNameIndex TypeIdx; - UObjectReference OwnerRef; - UNameIndex NameIdx; - /// memory - size_t EntryOffset; - size_t EntrySize; - std::string Name; - std::string FullName; - std::string Type; -}; - -struct FObjectExport -{ - /// persistent - UObjectReference TypeRef; - UObjectReference ParentClassRef; - UObjectReference OwnerRef; - UNameIndex NameIdx; - UObjectReference ArchetypeRef; - uint32_t ObjectFlagsH; - uint32_t ObjectFlagsL; - uint32_t SerialSize; - uint32_t SerialOffset; - uint32_t ExportFlags; - uint32_t NetObjectCount; - FGuid GUID; - uint32_t Unknown1; - std::vector NetObjects; // 4 x NetObjectCount bytes of data - /// memory - size_t EntryOffset; - size_t EntrySize; - std::string Name; - std::string FullName; - std::string Type; -}; - -class UPKInfo -{ - public: - /// constructors - UPKInfo(): Summary(), NoneIdx(0), ReadError(UPKReadErrors::NoErrors), Compressed(false), CompressedChunk(false), LastAccessedExportObjIdx(0) {}; - UPKInfo(std::istream& stream); - /// destructor - ~UPKInfo() {}; - /// read package header - bool Read(std::istream& stream); - bool ReadCompressedHeader(std::istream& stream); - /// helpers - std::string IndexToName(UNameIndex idx); - std::string ObjRefToName(UObjectReference ObjRef); - std::string ResolveFullName(UObjectReference ObjRef); - UObjectReference GetOwnerRef(UObjectReference ObjRef); - int FindName(std::string name); - UObjectReference FindObject(std::string FullName, bool isExport = true); - UObjectReference FindObjectByName(std::string Name, bool isExport = true); - UObjectReference FindObjectByOffset(size_t offset); - bool IsNoneIdx(UNameIndex idx) { return (idx.NameTableIdx == NoneIdx); } - /// Getters - const FPackageFileSummary& GetSummary() { return Summary; } - const FObjectExport& GetExportEntry(uint32_t idx); - const std::vector& GetExportTable() { return ExportTable; } - const FObjectImport& GetImportEntry(uint32_t idx); - const FNameEntry& GetNameEntry(uint32_t idx); - FGuid GetGUID() { return Summary.GUID; } - bool IsCompressed() { return Compressed; } - bool IsFullyCompressed() { return (Compressed && CompressedChunk); } - UPKReadErrors GetError() { return ReadError; } - uint32_t GetCompressionFlags() { return Summary.CompressionFlags; } + size_t UPKFileSize; +}; + +struct FNameEntry +{ + /// persistent + int32_t NameLength; + std::string Name; + uint32_t NameFlagsL; + uint32_t NameFlagsH; + /// memory + size_t EntryOffset; + size_t EntrySize; +}; + +struct FObjectImport +{ + /// persistent + UNameIndex PackageIdx; + UNameIndex TypeIdx; + UObjectReference OwnerRef; + UNameIndex NameIdx; + /// memory + size_t EntryOffset; + size_t EntrySize; + std::string Name; + std::string FullName; + std::string Type; +}; + +struct FObjectExport +{ + /// persistent + UObjectReference TypeRef; + UObjectReference ParentClassRef; + UObjectReference OwnerRef; + UNameIndex NameIdx; + UObjectReference ArchetypeRef; + uint32_t ObjectFlagsH; + uint32_t ObjectFlagsL; + uint32_t SerialSize; + uint32_t SerialOffset; + uint32_t ExportFlags; + uint32_t NetObjectCount; + FGuid GUID; + uint32_t Unknown1; + std::vector NetObjects; // 4 x NetObjectCount bytes of data + /// memory + size_t EntryOffset; + size_t EntrySize; + std::string Name; + std::string FullName; + std::string Type; +}; + +class UPKInfo +{ + public: + /// constructors + UPKInfo(): Summary(), NoneIdx(0), ReadError(UPKReadErrors::NoErrors), Compressed(false), CompressedChunk(false), LastAccessedExportObjIdx(0) {}; + UPKInfo(std::istream& stream); + /// destructor + ~UPKInfo() {}; + /// read package header + bool Read(std::istream& stream); + bool ReadCompressedHeader(std::istream& stream); + /// helpers + std::string IndexToName(UNameIndex idx); + std::string ObjRefToName(UObjectReference ObjRef); + std::string ResolveFullName(UObjectReference ObjRef); + UObjectReference GetOwnerRef(UObjectReference ObjRef); + int FindName(std::string name); + UObjectReference FindObject(std::string FullName, bool isExport = true); + UObjectReference FindObjectByName(std::string Name, bool isExport = true); + UObjectReference FindObjectByOffset(size_t offset); + bool IsNoneIdx(UNameIndex idx) { return (idx.NameTableIdx == NoneIdx); } + /// Getters + const FPackageFileSummary& GetSummary() { return Summary; } + const FObjectExport& GetExportEntry(uint32_t idx); + const std::vector& GetExportTable() { return ExportTable; } + const FObjectImport& GetImportEntry(uint32_t idx); + const FNameEntry& GetNameEntry(uint32_t idx); + FGuid GetGUID() { return Summary.GUID; } + bool IsCompressed() { return Compressed; } + bool IsFullyCompressed() { return (Compressed && CompressedChunk); } + UPKReadErrors GetError() { return ReadError; } + uint32_t GetCompressionFlags() { return Summary.CompressionFlags; } UObjectReference GetLastAccessedExportObjIdx() { return LastAccessedExportObjIdx; } - /// format header to text string - std::string FormatCompressedHeader(); - std::string FormatSummary(); - std::string FormatNames(bool verbose = false); - std::string FormatImports(bool verbose = false); - std::string FormatExports(bool verbose = false); - std::string FormatName(uint32_t idx, bool verbose = false); - std::string FormatImport(uint32_t idx, bool verbose = false); - std::string FormatExport(uint32_t idx, bool verbose = false); - protected: - FPackageFileSummary Summary; - std::vector NameTable; - std::vector ImportTable; - std::vector ExportTable; - std::vector DependsBuf; - uint32_t NoneIdx; - UPKReadErrors ReadError; - bool Compressed; - bool CompressedChunk; - FCompressedChunkHeader CompressedHeader; - UObjectReference LastAccessedExportObjIdx; -}; - -/// helper functions -std::string FormatHEX(uint32_t val); -std::string FormatHEX(uint16_t val); -std::string FormatHEX(uint8_t val); -std::string FormatHEX(float val); -std::string FormatHEX(FGuid GUID); -std::string FormatHEX(UNameIndex NameIndex); -std::string FormatHEX(uint32_t L, uint32_t H); -std::string FormatHEX(std::vector DataChunk); -std::string FormatHEX(char* DataChunk, size_t size); -std::string FormatHEX(std::string DataString); -/// format flags -std::string FormatPackageFlags(uint32_t flags); -std::string FormatCompressionFlags(uint32_t flags); -std::string FormatObjectFlagsL(uint32_t flags); -std::string FormatObjectFlagsH(uint32_t flags); -std::string FormatExportFlags(uint32_t flags); -std::string FormatFunctionFlags(uint32_t flags); -std::string FormatStructFlags(uint32_t flags); -std::string FormatClassFlags(uint32_t flags); -std::string FormatStateFlags(uint32_t flags); -std::string FormatPropertyFlagsL(uint32_t flags); -std::string FormatPropertyFlagsH(uint32_t flags); -std::string FormatReadErrors(UPKReadErrors ReadError); - -#endif // UPKINFO_H + /// format header to text string + std::string FormatCompressedHeader(); + std::string FormatSummary(); + std::string FormatNames(bool verbose = false); + std::string FormatImports(bool verbose = false); + std::string FormatExports(bool verbose = false); + std::string FormatName(uint32_t idx, bool verbose = false); + std::string FormatImport(uint32_t idx, bool verbose = false); + std::string FormatExport(uint32_t idx, bool verbose = false); + protected: + FPackageFileSummary Summary; + std::vector NameTable; + std::vector ImportTable; + std::vector ExportTable; + std::vector DependsBuf; + uint32_t NoneIdx; + UPKReadErrors ReadError; + bool Compressed; + bool CompressedChunk; + FCompressedChunkHeader CompressedHeader; + UObjectReference LastAccessedExportObjIdx; +}; + +/// helper functions +std::string FormatHEX(uint32_t val); +std::string FormatHEX(uint16_t val); +std::string FormatHEX(uint8_t val); +std::string FormatHEX(float val); +std::string FormatHEX(FGuid GUID); +std::string FormatHEX(UNameIndex NameIndex); +std::string FormatHEX(uint32_t L, uint32_t H); +std::string FormatHEX(std::vector DataChunk); +std::string FormatHEX(char* DataChunk, size_t size); +std::string FormatHEX(std::string DataString); +/// format flags +std::string FormatPackageFlags(uint32_t flags); +std::string FormatCompressionFlags(uint32_t flags); +std::string FormatObjectFlagsL(uint32_t flags); +std::string FormatObjectFlagsH(uint32_t flags); +std::string FormatExportFlags(uint32_t flags); +std::string FormatFunctionFlags(uint32_t flags); +std::string FormatStructFlags(uint32_t flags); +std::string FormatClassFlags(uint32_t flags); +std::string FormatStateFlags(uint32_t flags); +std::string FormatPropertyFlagsL(uint32_t flags); +std::string FormatPropertyFlagsH(uint32_t flags); +std::string FormatReadErrors(UPKReadErrors ReadError); + +#endif // UPKINFO_H diff --git a/UPKUtils.cpp b/UPKUtils.cpp index 09fd93e..5c84501 100644 --- a/UPKUtils.cpp +++ b/UPKUtils.cpp @@ -1,878 +1,878 @@ -#include "UPKUtils.h" - -#include -#include - -uint8_t PatchUPKhash [] = {0x7A, 0xA0, 0x56, 0xC9, - 0x60, 0x5F, 0x7B, 0x31, - 0x72, 0x5D, 0x4B, 0xC4, - 0x7C, 0xD2, 0x4D, 0xD9 }; - -UPKUtils::UPKUtils(const char* filename) -{ - if (UPKUtils::Read(filename) == false && UPKFile.is_open()) - { - UPKFile.close(); - } -} - -bool UPKUtils::Read(const char* filename) -{ - UPKFileName = filename; - if (UPKFile.is_open()) - { - UPKFile.close(); - UPKFile.clear(); - } - UPKFile.open(UPKFileName, std::ios::binary | std::ios::in | std::ios::out); - if (!UPKFile.is_open()) - return false; - return UPKUtils::Reload(); -} - -bool UPKUtils::Reload() -{ - if (!IsLoaded()) - return false; - UPKFile.clear(); - UPKFile.seekg(0, std::ios::end); - UPKFileSize = UPKFile.tellg(); - UPKFile.seekg(0); - return UPKInfo::Read(UPKFile); -} - -std::vector UPKUtils::GetExportData(uint32_t idx) -{ - std::vector data; - if (idx < 1 || idx >= ExportTable.size()) - return data; - data.resize(ExportTable[idx].SerialSize); - UPKFile.seekg(ExportTable[idx].SerialOffset); - UPKFile.read(data.data(), data.size()); - LastAccessedExportObjIdx = idx; - return data; -} - -void UPKUtils::SaveExportData(uint32_t idx) -{ - if (idx < 1 || idx >= ExportTable.size()) - return; - std::string filename = ExportTable[idx].FullName + "." + ExportTable[idx].Type; - std::vector dataChunk = GetExportData(idx); - std::ofstream out(filename.c_str(), std::ios::binary); - out.write(dataChunk.data(), dataChunk.size()); -} - -/// relatively safe behavior (old realization) -bool UPKUtils::MoveExportData(uint32_t idx, uint32_t newObjectSize) -{ - if (idx < 1 || idx >= ExportTable.size()) - return false; - std::vector data = GetExportData(idx); - UPKFile.seekg(0, std::ios::end); - uint32_t newObjectOffset = UPKFile.tellg(); - bool isFunction = (ExportTable[idx].Type == "Function"); - if (newObjectSize > ExportTable[idx].SerialSize) - { - UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*8); - UPKFile.write(reinterpret_cast(&newObjectSize), sizeof(newObjectSize)); - unsigned int diffSize = newObjectSize - data.size(); - if (isFunction == false) - { - for (unsigned int i = 0; i < diffSize; ++i) - data.push_back(0x00); - } - else - { - uint32_t oldMemSize = 0; - uint32_t oldFileSize = 0; - memcpy(&oldMemSize, data.data() + 0x28, 0x4); /// copy function memory size - memcpy(&oldFileSize, data.data() + 0x2C, 0x4); /// and file size - uint32_t newMemSize = oldMemSize + diffSize; /// find new sizes - uint32_t newFileSize = oldFileSize + diffSize; - uint32_t headSize = 0x30 + oldFileSize - 1; /// head size (all data before 0x53) - uint32_t tailSize = ExportTable[idx].SerialSize - headSize; /// tail size (0x53 and all data after) - std::vector newData(newObjectSize); - memset(newData.data(), 0x0B, newObjectSize); /// fill new data with 0x0B - memcpy(newData.data(), data.data(), headSize); /// copy all data before 0x53 - memcpy(newData.data() + 0x28, &newMemSize, 0x4); /// set new memory size - memcpy(newData.data() + 0x2C, &newFileSize, 0x4);/// and file size - memcpy(newData.data() + headSize + diffSize, data.data() + headSize, tailSize); /// copy 0x53 and all data after - data = newData; - } - } - UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*9); - UPKFile.write(reinterpret_cast(&newObjectOffset), sizeof(newObjectOffset)); - UPKFile.seekp(newObjectOffset); - UPKFile.write(data.data(), data.size()); - /// write backup info - UPKFile.write(reinterpret_cast(&PatchUPKhash[0]), 16); - UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialSize), sizeof(ExportTable[idx].SerialSize)); - UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialOffset), sizeof(ExportTable[idx].SerialOffset)); - /// reload package - UPKUtils::Reload(); - return true; -} - -bool UPKUtils::UndoMoveExportData(uint32_t idx) -{ - if (idx < 1 || idx >= ExportTable.size()) - return false; - UPKFile.seekg(ExportTable[idx].SerialOffset + ExportTable[idx].SerialSize); - uint8_t readHash [16]; - UPKFile.read(reinterpret_cast(&readHash[0]), 16); - if (memcmp(readHash, PatchUPKhash, 16) != 0) - return false; - uint32_t oldObjectFileSize, oldObjectOffset; - UPKFile.read(reinterpret_cast(&oldObjectFileSize), sizeof(oldObjectFileSize)); - UPKFile.read(reinterpret_cast(&oldObjectOffset), sizeof(oldObjectOffset)); - UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*8); - UPKFile.write(reinterpret_cast(&oldObjectFileSize), sizeof(oldObjectFileSize)); - UPKFile.write(reinterpret_cast(&oldObjectOffset), sizeof(oldObjectOffset)); - /// reload package - UPKUtils::Reload(); - return true; -} - -std::vector UPKUtils::GetResizedDataChunk(uint32_t idx, int newObjectSize, int resizeAt) -{ - std::vector data; - if (idx < 1 || idx >= ExportTable.size()) - return data; - /// get export object serial data - data = GetExportData(idx); - /// if object needs resizing - if (newObjectSize > 0 && (unsigned)newObjectSize != data.size()) - { - /// if resizing occurs in the middle of an object - if (resizeAt > 0 && resizeAt < newObjectSize) - { - int diff = newObjectSize - data.size(); - std::vector newData(newObjectSize); - memset(newData.data(), 0, newObjectSize); /// fill with zeros - memcpy(newData.data(), data.data(), resizeAt); /// copy head - if (diff > 0) /// if expanding - memcpy(newData.data() + resizeAt + diff, data.data() + resizeAt, data.size() - resizeAt); - else /// if shrinking - memcpy(newData.data() + resizeAt, data.data() + resizeAt - diff, data.size() - (resizeAt - diff)); - data = newData; - } - else - { - data.resize(newObjectSize, 0); - } - } - return data; -} - -bool UPKUtils::MoveResizeObject(uint32_t idx, int newObjectSize, int resizeAt) -{ - if (idx < 1 || idx >= ExportTable.size()) - return false; - std::vector data = GetResizedDataChunk(idx, newObjectSize, resizeAt); - /// move write pointer to the end of file - UPKFile.seekg(0, std::ios::end); - uint32_t newObjectOffset = UPKFile.tellg(); - /// if object needs resizing - if (ExportTable[idx].SerialSize != data.size()) - { - /// write new SerialSize to ExportTable entry - UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*8); - UPKFile.write(reinterpret_cast(&newObjectSize), sizeof(newObjectSize)); - } - /// write new SerialOffset to ExportTable entry - UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*9); - UPKFile.write(reinterpret_cast(&newObjectOffset), sizeof(newObjectOffset)); - /// write new SerialData - if (data.size() > 0) - { - UPKFile.seekp(newObjectOffset); - UPKFile.write(data.data(), data.size()); - } - /// write backup info - UPKFile.write(reinterpret_cast(&PatchUPKhash[0]), 16); - UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialSize), sizeof(ExportTable[idx].SerialSize)); - UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialOffset), sizeof(ExportTable[idx].SerialOffset)); - /// reload package - UPKUtils::Reload(); - return true; -} - -bool UPKUtils::UndoMoveResizeObject(uint32_t idx) -{ - return UndoMoveExportData(idx); -} - -std::string UPKUtils::Deserialize(UObjectReference ObjRef, bool TryUnsafe, bool QuickMode) -{ - if (ObjRef < 1 || ObjRef >= (int)ExportTable.size()) - return "Bad object reference!\n"; - UObject* Obj; - if (ExportTable[ObjRef].ObjectFlagsH & (uint32_t)UObjectFlagsH::PropertiesObject) - { - Obj = UObjectFactory::Create(GlobalType::UObject); - } - else - { - Obj = UObjectFactory::Create(ExportTable[ObjRef].Type); - } - if (Obj == nullptr) - return "Can't create object of given type!\n"; - std::string res; - UPKFile.seekg(ExportTable[ObjRef].SerialOffset); - Obj->SetRef(ObjRef); - Obj->SetUnsafe(TryUnsafe); - Obj->SetQuickMode(QuickMode); - res = Obj->Deserialize(UPKFile, *dynamic_cast(this)); - delete Obj; - return res; -} - -bool UPKUtils::CheckValidFileOffset(size_t offset) -{ - if (IsLoaded() == false) - { - return false; - } - /// does not allow to change package signature and version - return (offset >= 8 && offset < UPKFileSize); -} - -bool UPKUtils::WriteExportData(uint32_t idx, std::vector data, std::vector *backupData) -{ - if (idx < 1 || idx >= ExportTable.size()) - return false; - if (!IsLoaded()) - return false; - if (ExportTable[idx].SerialSize != data.size()) - return false; - if (backupData != nullptr) - { - backupData->clear(); - backupData->resize(data.size()); - UPKFile.seekg(ExportTable[idx].SerialOffset); - UPKFile.read(backupData->data(), backupData->size()); - } - UPKFile.seekp(ExportTable[idx].SerialOffset); - UPKFile.write(data.data(), data.size()); - return true; -} - -bool UPKUtils::WriteNameTableName(uint32_t idx, std::string name) -{ - if (idx < 1 || idx >= NameTable.size()) - return false; - if (!IsLoaded()) - return false; - if ((unsigned)(NameTable[idx].NameLength - 1) != name.length()) - return false; - UPKFile.seekp(NameTable[idx].EntryOffset + sizeof(NameTable[idx].NameLength)); - UPKFile.write(name.c_str(), name.length()); - /// reload package - UPKUtils::Reload(); - return true; -} - -bool UPKUtils::WriteData(size_t offset, std::vector data, std::vector *backupData) -{ - if (!CheckValidFileOffset(offset)) - return false; - if (backupData != nullptr) - { - backupData->clear(); - backupData->resize(data.size()); - UPKFile.seekg(offset); - UPKFile.read(backupData->data(), backupData->size()); - } - UPKFile.seekp(offset); - UPKFile.write(data.data(), data.size()); - /// if changed header - if (offset < Summary.SerialOffset) - { - /// reload package - UPKUtils::Reload(); - } - return true; -} - -std::vector UPKUtils::GetBulkData(size_t offset, std::vector data) -{ - UBulkDataMirror DataMirror; - DataMirror.SetBulkData(data); - DataMirror.SetFileOffset(offset + DataMirror.GetBulkDataRelOffset()); - std::string mirrorStr = DataMirror.Serialize(); - std::vector mirrorVec(mirrorStr.size()); - memcpy(mirrorVec.data(), mirrorStr.data(), mirrorStr.size()); - return mirrorVec; -} - -size_t UPKUtils::FindDataChunk(std::vector data, size_t beg, size_t limit) -{ - if (limit != 0 && (limit - beg + 1 < data.size() || limit < beg)) - return 0; - size_t offset = 0, idx = beg; - bool found = false; - std::vector fileBuf((limit == 0 ? UPKFileSize : limit) - beg + 1); - - UPKFile.seekg(beg); - UPKFile.read(fileBuf.data(), fileBuf.size()); - - char* pFileBuf = fileBuf.data(); - char* pData = data.data(); - - for (char* p = pFileBuf; p != pFileBuf + fileBuf.size() - data.size() + 1; ++p) - { - if (memcmp(p, pData, data.size()) == 0) - { - found = true; - offset = idx; - break; - } - ++idx; - } - - if (found == false) - offset = 0; - - UPKFile.clear(); - UPKFile.seekg(0); - return offset; -} - -size_t UPKUtils::GetScriptSize(uint32_t idx) -{ - if (idx < 1 || idx >= ExportTable.size()) - return 0; - UObject* Obj; - Obj = UObjectFactory::Create(ExportTable[idx].Type); - if (Obj == nullptr) - return 0; - UPKFile.seekg(ExportTable[idx].SerialOffset); - Obj->SetRef(idx); - Obj->SetUnsafe(false); - Obj->SetQuickMode(true); - Obj->Deserialize(UPKFile, *dynamic_cast(this)); - if (Obj->IsStructure() == false) - { - delete Obj; - return 0; - } - UStruct* St = dynamic_cast(Obj); - if (St == nullptr) - { - delete Obj; - return 0; - } - size_t ScriptSize = St->GetScriptSerialSize(); - delete Obj; - return ScriptSize; -} - -size_t UPKUtils::GetScriptMemSize(uint32_t idx) -{ - if (idx < 1 || idx >= ExportTable.size()) - return 0; - UObject* Obj; - Obj = UObjectFactory::Create(ExportTable[idx].Type); - if (Obj == nullptr) - return 0; - UPKFile.seekg(ExportTable[idx].SerialOffset); - Obj->SetRef(idx); - Obj->SetUnsafe(false); - Obj->SetQuickMode(true); - Obj->Deserialize(UPKFile, *dynamic_cast(this)); - if (Obj->IsStructure() == false) - { - delete Obj; - return 0; - } - UStruct* St = dynamic_cast(Obj); - if (St == nullptr) - { - delete Obj; - return 0; - } - size_t ScriptMemSize = St->GetScriptMemorySize(); - delete Obj; - return ScriptMemSize; -} - -size_t UPKUtils::GetScriptRelOffset(uint32_t idx) -{ - if (idx < 1 || idx >= ExportTable.size()) - return 0; - UObject* Obj; - Obj = UObjectFactory::Create(ExportTable[idx].Type); - if (Obj == nullptr) - return 0; - UPKFile.seekg(ExportTable[idx].SerialOffset); - Obj->SetRef(idx); - Obj->SetUnsafe(false); - Obj->SetQuickMode(true); - Obj->Deserialize(UPKFile, *dynamic_cast(this)); - if (Obj->IsStructure() == false) - { - delete Obj; - return 0; - } - UStruct* St = dynamic_cast(Obj); - if (St == nullptr) - { - delete Obj; - return 0; - } - size_t ScriptRelOffset = St->GetScriptOffset() - ExportTable[idx].SerialOffset; - delete Obj; - return ScriptRelOffset; -} - -bool UPKUtils::ResizeInPlace(uint32_t idx, int newObjectSize, int resizeAt) -{ - if (!UPKFile.good()) - { - return false; - } - if (idx < 1 || idx >= ExportTable.size()) - return false; - std::vector data = GetResizedDataChunk(idx, newObjectSize, resizeAt); - int diffSize = data.size() - ExportTable[idx].SerialSize; - /// increase offsets - for (unsigned i = 1; i <= Summary.ExportCount; ++i) - { - if (i != idx && ExportTable[i].SerialOffset > ExportTable[idx].SerialOffset) - { - ExportTable[i].SerialOffset += diffSize; - } - } - /// backup serialized export data into memory - UPKFile.clear(); - UPKFile.seekg(Summary.SerialOffset); - std::vector serializedDataBeforeIdx(ExportTable[idx].SerialOffset - UPKFile.tellg()); - if (serializedDataBeforeIdx.size() > 0) - { - UPKFile.read(serializedDataBeforeIdx.data(), serializedDataBeforeIdx.size()); - } - UPKFile.seekg(ExportTable[idx].SerialOffset + ExportTable[idx].SerialSize); - std::vector serializedDataAfterIdx(UPKFileSize - UPKFile.tellg()); - if (serializedDataAfterIdx.size() > 0) - { - UPKFile.read(serializedDataAfterIdx.data(), serializedDataAfterIdx.size()); - } - /// save new serial size - ExportTable[idx].SerialSize = newObjectSize; - /// serialize header - std::vector serializedHeader = SerializeHeader(); - /// rewrite package - UPKFile.close(); - UPKFile.open(UPKFileName.c_str(), std::ios::binary | std::ios::out | std::ios::trunc); - /// write serialized header - UPKFile.write(serializedHeader.data(), serializedHeader.size()); - /// write serialized export data before resized object - if (serializedDataBeforeIdx.size() > 0) - { - UPKFile.write(serializedDataBeforeIdx.data(), serializedDataBeforeIdx.size()); - } - /// write resized export object data - UPKFile.write(data.data(), data.size()); - /// write serialized export data after resized object - if (serializedDataAfterIdx.size() > 0) - { - UPKFile.write(serializedDataAfterIdx.data(), serializedDataAfterIdx.size()); - } - /// reload package - UPKUtils::Read(UPKFileName.c_str()); - return true; -} - -bool UPKUtils::AddNameEntry(FNameEntry Entry) -{ - if (!UPKFile.good()) - { - return false; - } - size_t oldSerialOffset = Summary.SerialOffset; - /// increase header size - Summary.HeaderSize += Entry.EntrySize; - /// add entry - ++Summary.NameCount; - NameTable.push_back(Entry); - /// increase offsets - Summary.ImportOffset += Entry.EntrySize; - Summary.ExportOffset += Entry.EntrySize; - Summary.DependsOffset += Entry.EntrySize; - Summary.SerialOffset += Entry.EntrySize; - for (unsigned i = 1; i <= Summary.ExportCount; ++i) - { - ExportTable[i].SerialOffset += Entry.EntrySize; - } - /// backup serialized export data into memory - UPKFile.clear(); - UPKFile.seekg(oldSerialOffset); - std::vector serializedData(UPKFileSize - oldSerialOffset); - UPKFile.read(serializedData.data(), serializedData.size()); - /// rewrite package - UPKFile.seekp(0); - /// serialize header - std::vector serializedHeader = SerializeHeader(); - /// write serialized header - UPKFile.write(serializedHeader.data(), serializedHeader.size()); - /// write serialized export data - UPKFile.write(serializedData.data(), serializedData.size()); - /// reload package - UPKUtils::Reload(); - return true; -} - -bool UPKUtils::AddImportEntry(FObjectImport Entry) -{ - if (!UPKFile.good()) - { - return false; - } - size_t oldSerialOffset = Summary.SerialOffset; - /// increase header size - Summary.HeaderSize += Entry.EntrySize; - /// add entry - ++Summary.ImportCount; - ImportTable.push_back(Entry); - /// increase offsets - Summary.ExportOffset += Entry.EntrySize; - Summary.DependsOffset += Entry.EntrySize; - Summary.SerialOffset += Entry.EntrySize; - for (unsigned i = 1; i <= Summary.ExportCount; ++i) - { - ExportTable[i].SerialOffset += Entry.EntrySize; - } - /// backup serialized export data into memory - UPKFile.clear(); - UPKFile.seekg(oldSerialOffset); - std::vector serializedData(UPKFileSize - oldSerialOffset); - UPKFile.read(serializedData.data(), serializedData.size()); - /// rewrite package - UPKFile.seekp(0); - /// serialize header - std::vector serializedHeader = SerializeHeader(); - /// write serialized header - UPKFile.write(serializedHeader.data(), serializedHeader.size()); - /// write serialized export data - UPKFile.write(serializedData.data(), serializedData.size()); - /// reload package - UPKUtils::Reload(); - return true; -} - -bool UPKUtils::AddExportEntry(FObjectExport Entry) -{ - if (!UPKFile.good()) - { - return false; - } - unsigned oldExportCount = Summary.ExportCount; - size_t oldSerialOffset = Summary.SerialOffset; - /// increase header size - Summary.HeaderSize += Entry.EntrySize; - /// add entry - ++Summary.ExportCount; - if (Entry.SerialSize < 16) /// PrevObject + NoneIdx + NextRef - { - Entry.SerialSize = 16; - } - Entry.SerialOffset = UPKFileSize + Entry.EntrySize; - ExportTable.push_back(Entry); - /// increase offsets - Summary.DependsOffset += Entry.EntrySize; - Summary.SerialOffset += Entry.EntrySize; - for (unsigned i = 1; i <= oldExportCount; ++i) - { - ExportTable[i].SerialOffset += Entry.EntrySize; - } - /// backup serialized export data into memory - UPKFile.clear(); - UPKFile.seekg(oldSerialOffset); - std::vector serializedData(UPKFileSize - oldSerialOffset); - UPKFile.read(serializedData.data(), serializedData.size()); - /// rewrite package - UPKFile.seekp(0); - /// serialize header - std::vector serializedHeader = SerializeHeader(); - /// write serialized header - UPKFile.write(serializedHeader.data(), serializedHeader.size()); - /// write serialized export data - UPKFile.write(serializedData.data(), serializedData.size()); - /// write new export serialized data - std::vector serializedEntry(Entry.SerialSize); - UObjectReference PrevObjRef = oldExportCount; - memcpy(serializedEntry.data(), reinterpret_cast(&PrevObjRef), sizeof(PrevObjRef)); - memcpy(serializedEntry.data() + sizeof(PrevObjRef), reinterpret_cast(&NoneIdx), sizeof(NoneIdx)); - UPKFile.write(serializedEntry.data(), serializedEntry.size()); - /// reload package - UPKUtils::Reload(); - /// link export object to owner - LinkChild(Entry.OwnerRef, Summary.ExportCount); - return true; -} - -bool UPKUtils::LinkChild(UObjectReference OwnerRef, UObjectReference ChildRef) -{ - if (OwnerRef < 1 || OwnerRef >= (int)ExportTable.size()) - return false; - UObject* Obj; - /// deserialize owner object to get first child - Obj = UObjectFactory::Create(ExportTable[OwnerRef].Type); - if (Obj == nullptr || Obj->IsStructure() == false) - { - return false; - } - UPKFile.seekg(ExportTable[OwnerRef].SerialOffset); - Obj->SetRef(OwnerRef); - Obj->SetUnsafe(false); - Obj->SetQuickMode(true); - Obj->Deserialize(UPKFile, *dynamic_cast(this)); - UStruct* StructObj = dynamic_cast(Obj); - if (StructObj == nullptr) - { - delete Obj; - return false; - } - UObjectReference FirstChildRef = StructObj->GetFirstChildRef(); - /// owner has no children - if (FirstChildRef == 0) - { - /// link child to owner - UPKFile.seekg(StructObj->GetFirstChildRefOffset()); - UPKFile.write(reinterpret_cast(&ChildRef), sizeof(ChildRef)); - delete Obj; - return true; - } - delete Obj; - /// find last child - UObjectReference NextRef = FirstChildRef; - size_t LastRefOffset = 0; - while (NextRef != 0) - { - Obj = UObjectFactory::Create(GlobalType::UField); - if (Obj == nullptr) - { - return false; - } - UPKFile.seekg(ExportTable[NextRef].SerialOffset); - Obj->SetRef(NextRef); - Obj->SetUnsafe(false); - Obj->SetQuickMode(true); - Obj->Deserialize(UPKFile, *dynamic_cast(this)); - UField* FieldObj = dynamic_cast(Obj); - if (FieldObj == nullptr) - { - delete Obj; - return false; - } - NextRef = FieldObj->GetNextRef(); - LastRefOffset = FieldObj->GetNextRefOffset(); - delete Obj; - - } - /// link new child to last child - UPKFile.seekg(LastRefOffset); - UPKFile.write(reinterpret_cast(&ChildRef), sizeof(ChildRef)); - return true; -} - -bool UPKUtils::Deserialize(FNameEntry& entry, std::vector& data) -{ - if (data.size() < 12) - { - return false; - } - std::stringstream ss; - ss.write(data.data(), data.size()); - ss.read(reinterpret_cast(&entry.NameLength), 4); - if (data.size() != 12U + entry.NameLength) - { - return false; - } - if (entry.NameLength > 0) - { - getline(ss, entry.Name, '\0'); - } - else - { - entry.Name = ""; - } - ss.read(reinterpret_cast(&entry.NameFlagsL), 4); - ss.read(reinterpret_cast(&entry.NameFlagsH), 4); - /// memory variables - entry.EntrySize = data.size(); - return true; -} - -bool UPKUtils::Deserialize(FObjectImport& entry, std::vector& data) -{ - if (data.size() != 28) - { - return false; - } - std::stringstream ss; - ss.write(data.data(), data.size()); - ss.read(reinterpret_cast(&entry.PackageIdx), sizeof(entry.PackageIdx)); - ss.read(reinterpret_cast(&entry.TypeIdx), sizeof(entry.TypeIdx)); - ss.read(reinterpret_cast(&entry.OwnerRef), sizeof(entry.OwnerRef)); - ss.read(reinterpret_cast(&entry.NameIdx), sizeof(entry.NameIdx)); - /// memory variables - entry.EntrySize = data.size(); - entry.Name = IndexToName(entry.NameIdx); - entry.FullName = entry.Name; - if (entry.OwnerRef != 0) - { - entry.FullName = ResolveFullName(entry.OwnerRef) + "." + entry.Name; - } - entry.Type = IndexToName(entry.TypeIdx); - if (entry.Type == "") - { - entry.Type = "Class"; - } - return true; -} - -bool UPKUtils::Deserialize(FObjectExport& entry, std::vector& data) -{ - if (data.size() < 68) - { - return false; - } - std::stringstream ss; - ss.write(data.data(), data.size()); - ss.read(reinterpret_cast(&entry.TypeRef), sizeof(entry.TypeRef)); - ss.read(reinterpret_cast(&entry.ParentClassRef), sizeof(entry.ParentClassRef)); - ss.read(reinterpret_cast(&entry.OwnerRef), sizeof(entry.OwnerRef)); - ss.read(reinterpret_cast(&entry.NameIdx), sizeof(entry.NameIdx)); - ss.read(reinterpret_cast(&entry.ArchetypeRef), sizeof(entry.ArchetypeRef)); - ss.read(reinterpret_cast(&entry.ObjectFlagsH), sizeof(entry.ObjectFlagsH)); - ss.read(reinterpret_cast(&entry.ObjectFlagsL), sizeof(entry.ObjectFlagsL)); - ss.read(reinterpret_cast(&entry.SerialSize), sizeof(entry.SerialSize)); - ss.read(reinterpret_cast(&entry.SerialOffset), sizeof(entry.SerialOffset)); - ss.read(reinterpret_cast(&entry.ExportFlags), sizeof(entry.ExportFlags)); - ss.read(reinterpret_cast(&entry.NetObjectCount), sizeof(entry.NetObjectCount)); - ss.read(reinterpret_cast(&entry.GUID), sizeof(entry.GUID)); - ss.read(reinterpret_cast(&entry.Unknown1), sizeof(entry.Unknown1)); - entry.NetObjects.resize(entry.NetObjectCount); - if (data.size() != 68 + entry.NetObjectCount) - { - return false; - } - if (entry.NetObjectCount > 0) - { - ss.read(reinterpret_cast(entry.NetObjects.data()), entry.NetObjects.size()*4); - } - /// memory variables - entry.EntrySize = data.size(); - entry.Name = IndexToName(entry.NameIdx); - entry.FullName = entry.Name; - if (entry.OwnerRef != 0) - { - entry.FullName = ResolveFullName(entry.OwnerRef) + "." + entry.Name; - } - entry.Type = ObjRefToName(entry.TypeRef); - if (entry.Type == "") - { - entry.Type = "Class"; - } - return true; -} - -std::vector UPKUtils::SerializeHeader() -{ - std::stringstream ss; - ss.write(reinterpret_cast(&Summary.Signature), 4); - int32_t Ver = (Summary.LicenseeVersion << 16) + Summary.Version; - ss.write(reinterpret_cast(&Ver), 4); - ss.write(reinterpret_cast(&Summary.HeaderSize), 4); - ss.write(reinterpret_cast(&Summary.FolderNameLength), 4); - if (Summary.FolderNameLength > 0) - { - ss.write(Summary.FolderName.c_str(), Summary.FolderNameLength); - } - ss.write(reinterpret_cast(&Summary.PackageFlags), 4); - ss.write(reinterpret_cast(&Summary.NameCount), 4); - ss.write(reinterpret_cast(&Summary.NameOffset), 4); - ss.write(reinterpret_cast(&Summary.ExportCount), 4); - ss.write(reinterpret_cast(&Summary.ExportOffset), 4); - ss.write(reinterpret_cast(&Summary.ImportCount), 4); - ss.write(reinterpret_cast(&Summary.ImportOffset), 4); - ss.write(reinterpret_cast(&Summary.DependsOffset), 4); - ss.write(reinterpret_cast(&Summary.SerialOffset), 4); - ss.write(reinterpret_cast(&Summary.Unknown2), 4); - ss.write(reinterpret_cast(&Summary.Unknown3), 4); - ss.write(reinterpret_cast(&Summary.Unknown4), 4); - ss.write(reinterpret_cast(&Summary.GUID), sizeof(Summary.GUID)); - ss.write(reinterpret_cast(&Summary.GenerationsCount), 4); - for (unsigned i = 0; i < Summary.GenerationsCount; ++i) - { - FGenerationInfo Entry = Summary.Generations[i]; - ss.write(reinterpret_cast(&Entry.ExportCount), 4); - ss.write(reinterpret_cast(&Entry.NameCount), 4); - ss.write(reinterpret_cast(&Entry.NetObjectCount), 4); - } - ss.write(reinterpret_cast(&Summary.EngineVersion), 4); - ss.write(reinterpret_cast(&Summary.CookerVersion), 4); - ss.write(reinterpret_cast(&Summary.CompressionFlags), 4); - ss.write(reinterpret_cast(&Summary.NumCompressedChunks), 4); - for (unsigned i = 0; i < Summary.NumCompressedChunks; ++i) - { - FCompressedChunk CompressedChunk = Summary.CompressedChunks[i]; - ss.write(reinterpret_cast(&CompressedChunk.UncompressedOffset), 4); - ss.write(reinterpret_cast(&CompressedChunk.UncompressedSize), 4); - ss.write(reinterpret_cast(&CompressedChunk.CompressedOffset), 4); - ss.write(reinterpret_cast(&CompressedChunk.CompressedSize), 4); - } - if (Summary.UnknownDataChunk.size() > 0) - { - ss.write(Summary.UnknownDataChunk.data(), Summary.UnknownDataChunk.size()); - } - for (unsigned i = 0; i < Summary.NameCount; ++i) - { - FNameEntry Entry = NameTable[i]; - ss.write(reinterpret_cast(&Entry.NameLength), 4); - if (Entry.NameLength > 0) - { - ss.write(Entry.Name.c_str(), Entry.NameLength); - } - ss.write(reinterpret_cast(&Entry.NameFlagsL), 4); - ss.write(reinterpret_cast(&Entry.NameFlagsH), 4); - } - for (unsigned i = 1; i <= Summary.ImportCount; ++i) - { - FObjectImport Entry = ImportTable[i]; - ss.write(reinterpret_cast(&Entry.PackageIdx), sizeof(Entry.PackageIdx)); - ss.write(reinterpret_cast(&Entry.TypeIdx), sizeof(Entry.TypeIdx)); - ss.write(reinterpret_cast(&Entry.OwnerRef), sizeof(Entry.OwnerRef)); - ss.write(reinterpret_cast(&Entry.NameIdx), sizeof(Entry.NameIdx)); - } - for (unsigned i = 1; i <= Summary.ExportCount; ++i) - { - FObjectExport Entry = ExportTable[i]; - ss.write(reinterpret_cast(&Entry.TypeRef), sizeof(Entry.TypeRef)); - ss.write(reinterpret_cast(&Entry.ParentClassRef), sizeof(Entry.ParentClassRef)); - ss.write(reinterpret_cast(&Entry.OwnerRef), sizeof(Entry.OwnerRef)); - ss.write(reinterpret_cast(&Entry.NameIdx), sizeof(Entry.NameIdx)); - ss.write(reinterpret_cast(&Entry.ArchetypeRef), sizeof(Entry.ArchetypeRef)); - ss.write(reinterpret_cast(&Entry.ObjectFlagsH), sizeof(Entry.ObjectFlagsH)); - ss.write(reinterpret_cast(&Entry.ObjectFlagsL), sizeof(Entry.ObjectFlagsL)); - ss.write(reinterpret_cast(&Entry.SerialSize), sizeof(Entry.SerialSize)); - ss.write(reinterpret_cast(&Entry.SerialOffset), sizeof(Entry.SerialOffset)); - ss.write(reinterpret_cast(&Entry.ExportFlags), sizeof(Entry.ExportFlags)); - ss.write(reinterpret_cast(&Entry.NetObjectCount), sizeof(Entry.NetObjectCount)); - ss.write(reinterpret_cast(&Entry.GUID), sizeof(Entry.GUID)); - ss.write(reinterpret_cast(&Entry.Unknown1), sizeof(Entry.Unknown1)); - if (Entry.NetObjectCount > 0) - { - ss.write(reinterpret_cast(Entry.NetObjects.data()), Entry.NetObjects.size()*4); - } - } - if (DependsBuf.size() > 0) - { - ss.write(DependsBuf.data(), DependsBuf.size()); - } - std::vector ret(ss.tellp()); - ss.read(ret.data(), ret.size()); - return ret; -} +#include "UPKUtils.h" + +#include +#include + +uint8_t PatchUPKhash [] = {0x7A, 0xA0, 0x56, 0xC9, + 0x60, 0x5F, 0x7B, 0x31, + 0x72, 0x5D, 0x4B, 0xC4, + 0x7C, 0xD2, 0x4D, 0xD9 }; + +UPKUtils::UPKUtils(const char* filename) +{ + if (UPKUtils::Read(filename) == false && UPKFile.is_open()) + { + UPKFile.close(); + } +} + +bool UPKUtils::Read(const char* filename) +{ + UPKFileName = filename; + if (UPKFile.is_open()) + { + UPKFile.close(); + UPKFile.clear(); + } + UPKFile.open(UPKFileName, std::ios::binary | std::ios::in | std::ios::out); + if (!UPKFile.is_open()) + return false; + return UPKUtils::Reload(); +} + +bool UPKUtils::Reload() +{ + if (!IsLoaded()) + return false; + UPKFile.clear(); + UPKFile.seekg(0, std::ios::end); + UPKFileSize = UPKFile.tellg(); + UPKFile.seekg(0); + return UPKInfo::Read(UPKFile); +} + +std::vector UPKUtils::GetExportData(uint32_t idx) +{ + std::vector data; + if (idx < 1 || idx >= ExportTable.size()) + return data; + data.resize(ExportTable[idx].SerialSize); + UPKFile.seekg(ExportTable[idx].SerialOffset); + UPKFile.read(data.data(), data.size()); + LastAccessedExportObjIdx = idx; + return data; +} + +void UPKUtils::SaveExportData(uint32_t idx) +{ + if (idx < 1 || idx >= ExportTable.size()) + return; + std::string filename = ExportTable[idx].FullName + "." + ExportTable[idx].Type; + std::vector dataChunk = GetExportData(idx); + std::ofstream out(filename.c_str(), std::ios::binary); + out.write(dataChunk.data(), dataChunk.size()); +} + +/// relatively safe behavior (old realization) +bool UPKUtils::MoveExportData(uint32_t idx, uint32_t newObjectSize) +{ + if (idx < 1 || idx >= ExportTable.size()) + return false; + std::vector data = GetExportData(idx); + UPKFile.seekg(0, std::ios::end); + uint32_t newObjectOffset = UPKFile.tellg(); + bool isFunction = (ExportTable[idx].Type == "Function"); + if (newObjectSize > ExportTable[idx].SerialSize) + { + UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*8); + UPKFile.write(reinterpret_cast(&newObjectSize), sizeof(newObjectSize)); + unsigned int diffSize = newObjectSize - data.size(); + if (isFunction == false) + { + for (unsigned int i = 0; i < diffSize; ++i) + data.push_back(0x00); + } + else + { + uint32_t oldMemSize = 0; + uint32_t oldFileSize = 0; + memcpy(&oldMemSize, data.data() + 0x28, 0x4); /// copy function memory size + memcpy(&oldFileSize, data.data() + 0x2C, 0x4); /// and file size + uint32_t newMemSize = oldMemSize + diffSize; /// find new sizes + uint32_t newFileSize = oldFileSize + diffSize; + uint32_t headSize = 0x30 + oldFileSize - 1; /// head size (all data before 0x53) + uint32_t tailSize = ExportTable[idx].SerialSize - headSize; /// tail size (0x53 and all data after) + std::vector newData(newObjectSize); + memset(newData.data(), 0x0B, newObjectSize); /// fill new data with 0x0B + memcpy(newData.data(), data.data(), headSize); /// copy all data before 0x53 + memcpy(newData.data() + 0x28, &newMemSize, 0x4); /// set new memory size + memcpy(newData.data() + 0x2C, &newFileSize, 0x4);/// and file size + memcpy(newData.data() + headSize + diffSize, data.data() + headSize, tailSize); /// copy 0x53 and all data after + data = newData; + } + } + UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*9); + UPKFile.write(reinterpret_cast(&newObjectOffset), sizeof(newObjectOffset)); + UPKFile.seekp(newObjectOffset); + UPKFile.write(data.data(), data.size()); + /// write backup info + UPKFile.write(reinterpret_cast(&PatchUPKhash[0]), 16); + UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialSize), sizeof(ExportTable[idx].SerialSize)); + UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialOffset), sizeof(ExportTable[idx].SerialOffset)); + /// reload package + UPKUtils::Reload(); + return true; +} + +bool UPKUtils::UndoMoveExportData(uint32_t idx) +{ + if (idx < 1 || idx >= ExportTable.size()) + return false; + UPKFile.seekg(ExportTable[idx].SerialOffset + ExportTable[idx].SerialSize); + uint8_t readHash [16]; + UPKFile.read(reinterpret_cast(&readHash[0]), 16); + if (memcmp(readHash, PatchUPKhash, 16) != 0) + return false; + uint32_t oldObjectFileSize, oldObjectOffset; + UPKFile.read(reinterpret_cast(&oldObjectFileSize), sizeof(oldObjectFileSize)); + UPKFile.read(reinterpret_cast(&oldObjectOffset), sizeof(oldObjectOffset)); + UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*8); + UPKFile.write(reinterpret_cast(&oldObjectFileSize), sizeof(oldObjectFileSize)); + UPKFile.write(reinterpret_cast(&oldObjectOffset), sizeof(oldObjectOffset)); + /// reload package + UPKUtils::Reload(); + return true; +} + +std::vector UPKUtils::GetResizedDataChunk(uint32_t idx, int newObjectSize, int resizeAt) +{ + std::vector data; + if (idx < 1 || idx >= ExportTable.size()) + return data; + /// get export object serial data + data = GetExportData(idx); + /// if object needs resizing + if (newObjectSize > 0 && (unsigned)newObjectSize != data.size()) + { + /// if resizing occurs in the middle of an object + if (resizeAt > 0 && resizeAt < newObjectSize) + { + int diff = newObjectSize - data.size(); + std::vector newData(newObjectSize); + memset(newData.data(), 0, newObjectSize); /// fill with zeros + memcpy(newData.data(), data.data(), resizeAt); /// copy head + if (diff > 0) /// if expanding + memcpy(newData.data() + resizeAt + diff, data.data() + resizeAt, data.size() - resizeAt); + else /// if shrinking + memcpy(newData.data() + resizeAt, data.data() + resizeAt - diff, data.size() - (resizeAt - diff)); + data = newData; + } + else + { + data.resize(newObjectSize, 0); + } + } + return data; +} + +bool UPKUtils::MoveResizeObject(uint32_t idx, int newObjectSize, int resizeAt) +{ + if (idx < 1 || idx >= ExportTable.size()) + return false; + std::vector data = GetResizedDataChunk(idx, newObjectSize, resizeAt); + /// move write pointer to the end of file + UPKFile.seekg(0, std::ios::end); + uint32_t newObjectOffset = UPKFile.tellg(); + /// if object needs resizing + if (ExportTable[idx].SerialSize != data.size()) + { + /// write new SerialSize to ExportTable entry + UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*8); + UPKFile.write(reinterpret_cast(&newObjectSize), sizeof(newObjectSize)); + } + /// write new SerialOffset to ExportTable entry + UPKFile.seekp(ExportTable[idx].EntryOffset + sizeof(uint32_t)*9); + UPKFile.write(reinterpret_cast(&newObjectOffset), sizeof(newObjectOffset)); + /// write new SerialData + if (data.size() > 0) + { + UPKFile.seekp(newObjectOffset); + UPKFile.write(data.data(), data.size()); + } + /// write backup info + UPKFile.write(reinterpret_cast(&PatchUPKhash[0]), 16); + UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialSize), sizeof(ExportTable[idx].SerialSize)); + UPKFile.write(reinterpret_cast(&ExportTable[idx].SerialOffset), sizeof(ExportTable[idx].SerialOffset)); + /// reload package + UPKUtils::Reload(); + return true; +} + +bool UPKUtils::UndoMoveResizeObject(uint32_t idx) +{ + return UndoMoveExportData(idx); +} + +std::string UPKUtils::Deserialize(UObjectReference ObjRef, bool TryUnsafe, bool QuickMode) +{ + if (ObjRef < 1 || ObjRef >= (int)ExportTable.size()) + return "Bad object reference!\n"; + UObject* Obj; + if (ExportTable[ObjRef].ObjectFlagsH & (uint32_t)UObjectFlagsH::PropertiesObject) + { + Obj = UObjectFactory::Create(GlobalType::UObject); + } + else + { + Obj = UObjectFactory::Create(ExportTable[ObjRef].Type); + } + if (Obj == nullptr) + return "Can't create object of given type!\n"; + std::string res; + UPKFile.seekg(ExportTable[ObjRef].SerialOffset); + Obj->SetRef(ObjRef); + Obj->SetUnsafe(TryUnsafe); + Obj->SetQuickMode(QuickMode); + res = Obj->Deserialize(UPKFile, *dynamic_cast(this)); + delete Obj; + return res; +} + +bool UPKUtils::CheckValidFileOffset(size_t offset) +{ + if (IsLoaded() == false) + { + return false; + } + /// does not allow to change package signature and version + return (offset >= 8 && offset < UPKFileSize); +} + +bool UPKUtils::WriteExportData(uint32_t idx, std::vector data, std::vector *backupData) +{ + if (idx < 1 || idx >= ExportTable.size()) + return false; + if (!IsLoaded()) + return false; + if (ExportTable[idx].SerialSize != data.size()) + return false; + if (backupData != nullptr) + { + backupData->clear(); + backupData->resize(data.size()); + UPKFile.seekg(ExportTable[idx].SerialOffset); + UPKFile.read(backupData->data(), backupData->size()); + } + UPKFile.seekp(ExportTable[idx].SerialOffset); + UPKFile.write(data.data(), data.size()); + return true; +} + +bool UPKUtils::WriteNameTableName(uint32_t idx, std::string name) +{ + if (idx < 1 || idx >= NameTable.size()) + return false; + if (!IsLoaded()) + return false; + if ((unsigned)(NameTable[idx].NameLength - 1) != name.length()) + return false; + UPKFile.seekp(NameTable[idx].EntryOffset + sizeof(NameTable[idx].NameLength)); + UPKFile.write(name.c_str(), name.length()); + /// reload package + UPKUtils::Reload(); + return true; +} + +bool UPKUtils::WriteData(size_t offset, std::vector data, std::vector *backupData) +{ + if (!CheckValidFileOffset(offset)) + return false; + if (backupData != nullptr) + { + backupData->clear(); + backupData->resize(data.size()); + UPKFile.seekg(offset); + UPKFile.read(backupData->data(), backupData->size()); + } + UPKFile.seekp(offset); + UPKFile.write(data.data(), data.size()); + /// if changed header + if (offset < Summary.SerialOffset) + { + /// reload package + UPKUtils::Reload(); + } + return true; +} + +std::vector UPKUtils::GetBulkData(size_t offset, std::vector data) +{ + UBulkDataMirror DataMirror; + DataMirror.SetBulkData(data); + DataMirror.SetFileOffset(offset + DataMirror.GetBulkDataRelOffset()); + std::string mirrorStr = DataMirror.Serialize(); + std::vector mirrorVec(mirrorStr.size()); + memcpy(mirrorVec.data(), mirrorStr.data(), mirrorStr.size()); + return mirrorVec; +} + +size_t UPKUtils::FindDataChunk(std::vector data, size_t beg, size_t limit) +{ + if (limit != 0 && (limit - beg + 1 < data.size() || limit < beg)) + return 0; + size_t offset = 0, idx = beg; + bool found = false; + std::vector fileBuf((limit == 0 ? UPKFileSize : limit) - beg + 1); + + UPKFile.seekg(beg); + UPKFile.read(fileBuf.data(), fileBuf.size()); + + char* pFileBuf = fileBuf.data(); + char* pData = data.data(); + + for (char* p = pFileBuf; p != pFileBuf + fileBuf.size() - data.size() + 1; ++p) + { + if (memcmp(p, pData, data.size()) == 0) + { + found = true; + offset = idx; + break; + } + ++idx; + } + + if (found == false) + offset = 0; + + UPKFile.clear(); + UPKFile.seekg(0); + return offset; +} + +size_t UPKUtils::GetScriptSize(uint32_t idx) +{ + if (idx < 1 || idx >= ExportTable.size()) + return 0; + UObject* Obj; + Obj = UObjectFactory::Create(ExportTable[idx].Type); + if (Obj == nullptr) + return 0; + UPKFile.seekg(ExportTable[idx].SerialOffset); + Obj->SetRef(idx); + Obj->SetUnsafe(false); + Obj->SetQuickMode(true); + Obj->Deserialize(UPKFile, *dynamic_cast(this)); + if (Obj->IsStructure() == false) + { + delete Obj; + return 0; + } + UStruct* St = dynamic_cast(Obj); + if (St == nullptr) + { + delete Obj; + return 0; + } + size_t ScriptSize = St->GetScriptSerialSize(); + delete Obj; + return ScriptSize; +} + +size_t UPKUtils::GetScriptMemSize(uint32_t idx) +{ + if (idx < 1 || idx >= ExportTable.size()) + return 0; + UObject* Obj; + Obj = UObjectFactory::Create(ExportTable[idx].Type); + if (Obj == nullptr) + return 0; + UPKFile.seekg(ExportTable[idx].SerialOffset); + Obj->SetRef(idx); + Obj->SetUnsafe(false); + Obj->SetQuickMode(true); + Obj->Deserialize(UPKFile, *dynamic_cast(this)); + if (Obj->IsStructure() == false) + { + delete Obj; + return 0; + } + UStruct* St = dynamic_cast(Obj); + if (St == nullptr) + { + delete Obj; + return 0; + } + size_t ScriptMemSize = St->GetScriptMemorySize(); + delete Obj; + return ScriptMemSize; +} + +size_t UPKUtils::GetScriptRelOffset(uint32_t idx) +{ + if (idx < 1 || idx >= ExportTable.size()) + return 0; + UObject* Obj; + Obj = UObjectFactory::Create(ExportTable[idx].Type); + if (Obj == nullptr) + return 0; + UPKFile.seekg(ExportTable[idx].SerialOffset); + Obj->SetRef(idx); + Obj->SetUnsafe(false); + Obj->SetQuickMode(true); + Obj->Deserialize(UPKFile, *dynamic_cast(this)); + if (Obj->IsStructure() == false) + { + delete Obj; + return 0; + } + UStruct* St = dynamic_cast(Obj); + if (St == nullptr) + { + delete Obj; + return 0; + } + size_t ScriptRelOffset = St->GetScriptOffset() - ExportTable[idx].SerialOffset; + delete Obj; + return ScriptRelOffset; +} + +bool UPKUtils::ResizeInPlace(uint32_t idx, int newObjectSize, int resizeAt) +{ + if (!UPKFile.good()) + { + return false; + } + if (idx < 1 || idx >= ExportTable.size()) + return false; + std::vector data = GetResizedDataChunk(idx, newObjectSize, resizeAt); + int diffSize = data.size() - ExportTable[idx].SerialSize; + /// increase offsets + for (unsigned i = 1; i <= Summary.ExportCount; ++i) + { + if (i != idx && ExportTable[i].SerialOffset > ExportTable[idx].SerialOffset) + { + ExportTable[i].SerialOffset += diffSize; + } + } + /// backup serialized export data into memory + UPKFile.clear(); + UPKFile.seekg(Summary.SerialOffset); + std::vector serializedDataBeforeIdx(ExportTable[idx].SerialOffset - UPKFile.tellg()); + if (serializedDataBeforeIdx.size() > 0) + { + UPKFile.read(serializedDataBeforeIdx.data(), serializedDataBeforeIdx.size()); + } + UPKFile.seekg(ExportTable[idx].SerialOffset + ExportTable[idx].SerialSize); + std::vector serializedDataAfterIdx(UPKFileSize - UPKFile.tellg()); + if (serializedDataAfterIdx.size() > 0) + { + UPKFile.read(serializedDataAfterIdx.data(), serializedDataAfterIdx.size()); + } + /// save new serial size + ExportTable[idx].SerialSize = newObjectSize; + /// serialize header + std::vector serializedHeader = SerializeHeader(); + /// rewrite package + UPKFile.close(); + UPKFile.open(UPKFileName.c_str(), std::ios::binary | std::ios::out | std::ios::trunc); + /// write serialized header + UPKFile.write(serializedHeader.data(), serializedHeader.size()); + /// write serialized export data before resized object + if (serializedDataBeforeIdx.size() > 0) + { + UPKFile.write(serializedDataBeforeIdx.data(), serializedDataBeforeIdx.size()); + } + /// write resized export object data + UPKFile.write(data.data(), data.size()); + /// write serialized export data after resized object + if (serializedDataAfterIdx.size() > 0) + { + UPKFile.write(serializedDataAfterIdx.data(), serializedDataAfterIdx.size()); + } + /// reload package + UPKUtils::Read(UPKFileName.c_str()); + return true; +} + +bool UPKUtils::AddNameEntry(FNameEntry Entry) +{ + if (!UPKFile.good()) + { + return false; + } + size_t oldSerialOffset = Summary.SerialOffset; + /// increase header size + Summary.HeaderSize += Entry.EntrySize; + /// add entry + ++Summary.NameCount; + NameTable.push_back(Entry); + /// increase offsets + Summary.ImportOffset += Entry.EntrySize; + Summary.ExportOffset += Entry.EntrySize; + Summary.DependsOffset += Entry.EntrySize; + Summary.SerialOffset += Entry.EntrySize; + for (unsigned i = 1; i <= Summary.ExportCount; ++i) + { + ExportTable[i].SerialOffset += Entry.EntrySize; + } + /// backup serialized export data into memory + UPKFile.clear(); + UPKFile.seekg(oldSerialOffset); + std::vector serializedData(UPKFileSize - oldSerialOffset); + UPKFile.read(serializedData.data(), serializedData.size()); + /// rewrite package + UPKFile.seekp(0); + /// serialize header + std::vector serializedHeader = SerializeHeader(); + /// write serialized header + UPKFile.write(serializedHeader.data(), serializedHeader.size()); + /// write serialized export data + UPKFile.write(serializedData.data(), serializedData.size()); + /// reload package + UPKUtils::Reload(); + return true; +} + +bool UPKUtils::AddImportEntry(FObjectImport Entry) +{ + if (!UPKFile.good()) + { + return false; + } + size_t oldSerialOffset = Summary.SerialOffset; + /// increase header size + Summary.HeaderSize += Entry.EntrySize; + /// add entry + ++Summary.ImportCount; + ImportTable.push_back(Entry); + /// increase offsets + Summary.ExportOffset += Entry.EntrySize; + Summary.DependsOffset += Entry.EntrySize; + Summary.SerialOffset += Entry.EntrySize; + for (unsigned i = 1; i <= Summary.ExportCount; ++i) + { + ExportTable[i].SerialOffset += Entry.EntrySize; + } + /// backup serialized export data into memory + UPKFile.clear(); + UPKFile.seekg(oldSerialOffset); + std::vector serializedData(UPKFileSize - oldSerialOffset); + UPKFile.read(serializedData.data(), serializedData.size()); + /// rewrite package + UPKFile.seekp(0); + /// serialize header + std::vector serializedHeader = SerializeHeader(); + /// write serialized header + UPKFile.write(serializedHeader.data(), serializedHeader.size()); + /// write serialized export data + UPKFile.write(serializedData.data(), serializedData.size()); + /// reload package + UPKUtils::Reload(); + return true; +} + +bool UPKUtils::AddExportEntry(FObjectExport Entry) +{ + if (!UPKFile.good()) + { + return false; + } + unsigned oldExportCount = Summary.ExportCount; + size_t oldSerialOffset = Summary.SerialOffset; + /// increase header size + Summary.HeaderSize += Entry.EntrySize; + /// add entry + ++Summary.ExportCount; + if (Entry.SerialSize < 16) /// PrevObject + NoneIdx + NextRef + { + Entry.SerialSize = 16; + } + Entry.SerialOffset = UPKFileSize + Entry.EntrySize; + ExportTable.push_back(Entry); + /// increase offsets + Summary.DependsOffset += Entry.EntrySize; + Summary.SerialOffset += Entry.EntrySize; + for (unsigned i = 1; i <= oldExportCount; ++i) + { + ExportTable[i].SerialOffset += Entry.EntrySize; + } + /// backup serialized export data into memory + UPKFile.clear(); + UPKFile.seekg(oldSerialOffset); + std::vector serializedData(UPKFileSize - oldSerialOffset); + UPKFile.read(serializedData.data(), serializedData.size()); + /// rewrite package + UPKFile.seekp(0); + /// serialize header + std::vector serializedHeader = SerializeHeader(); + /// write serialized header + UPKFile.write(serializedHeader.data(), serializedHeader.size()); + /// write serialized export data + UPKFile.write(serializedData.data(), serializedData.size()); + /// write new export serialized data + std::vector serializedEntry(Entry.SerialSize); + UObjectReference PrevObjRef = oldExportCount; + memcpy(serializedEntry.data(), reinterpret_cast(&PrevObjRef), sizeof(PrevObjRef)); + memcpy(serializedEntry.data() + sizeof(PrevObjRef), reinterpret_cast(&NoneIdx), sizeof(NoneIdx)); + UPKFile.write(serializedEntry.data(), serializedEntry.size()); + /// reload package + UPKUtils::Reload(); + /// link export object to owner + LinkChild(Entry.OwnerRef, Summary.ExportCount); + return true; +} + +bool UPKUtils::LinkChild(UObjectReference OwnerRef, UObjectReference ChildRef) +{ + if (OwnerRef < 1 || OwnerRef >= (int)ExportTable.size()) + return false; + UObject* Obj; + /// deserialize owner object to get first child + Obj = UObjectFactory::Create(ExportTable[OwnerRef].Type); + if (Obj == nullptr || Obj->IsStructure() == false) + { + return false; + } + UPKFile.seekg(ExportTable[OwnerRef].SerialOffset); + Obj->SetRef(OwnerRef); + Obj->SetUnsafe(false); + Obj->SetQuickMode(true); + Obj->Deserialize(UPKFile, *dynamic_cast(this)); + UStruct* StructObj = dynamic_cast(Obj); + if (StructObj == nullptr) + { + delete Obj; + return false; + } + UObjectReference FirstChildRef = StructObj->GetFirstChildRef(); + /// owner has no children + if (FirstChildRef == 0) + { + /// link child to owner + UPKFile.seekg(StructObj->GetFirstChildRefOffset()); + UPKFile.write(reinterpret_cast(&ChildRef), sizeof(ChildRef)); + delete Obj; + return true; + } + delete Obj; + /// find last child + UObjectReference NextRef = FirstChildRef; + size_t LastRefOffset = 0; + while (NextRef != 0) + { + Obj = UObjectFactory::Create(GlobalType::UField); + if (Obj == nullptr) + { + return false; + } + UPKFile.seekg(ExportTable[NextRef].SerialOffset); + Obj->SetRef(NextRef); + Obj->SetUnsafe(false); + Obj->SetQuickMode(true); + Obj->Deserialize(UPKFile, *dynamic_cast(this)); + UField* FieldObj = dynamic_cast(Obj); + if (FieldObj == nullptr) + { + delete Obj; + return false; + } + NextRef = FieldObj->GetNextRef(); + LastRefOffset = FieldObj->GetNextRefOffset(); + delete Obj; + + } + /// link new child to last child + UPKFile.seekg(LastRefOffset); + UPKFile.write(reinterpret_cast(&ChildRef), sizeof(ChildRef)); + return true; +} + +bool UPKUtils::Deserialize(FNameEntry& entry, std::vector& data) +{ + if (data.size() < 12) + { + return false; + } + std::stringstream ss; + ss.write(data.data(), data.size()); + ss.read(reinterpret_cast(&entry.NameLength), 4); + if (data.size() != 12U + entry.NameLength) + { + return false; + } + if (entry.NameLength > 0) + { + getline(ss, entry.Name, '\0'); + } + else + { + entry.Name = ""; + } + ss.read(reinterpret_cast(&entry.NameFlagsL), 4); + ss.read(reinterpret_cast(&entry.NameFlagsH), 4); + /// memory variables + entry.EntrySize = data.size(); + return true; +} + +bool UPKUtils::Deserialize(FObjectImport& entry, std::vector& data) +{ + if (data.size() != 28) + { + return false; + } + std::stringstream ss; + ss.write(data.data(), data.size()); + ss.read(reinterpret_cast(&entry.PackageIdx), sizeof(entry.PackageIdx)); + ss.read(reinterpret_cast(&entry.TypeIdx), sizeof(entry.TypeIdx)); + ss.read(reinterpret_cast(&entry.OwnerRef), sizeof(entry.OwnerRef)); + ss.read(reinterpret_cast(&entry.NameIdx), sizeof(entry.NameIdx)); + /// memory variables + entry.EntrySize = data.size(); + entry.Name = IndexToName(entry.NameIdx); + entry.FullName = entry.Name; + if (entry.OwnerRef != 0) + { + entry.FullName = ResolveFullName(entry.OwnerRef) + "." + entry.Name; + } + entry.Type = IndexToName(entry.TypeIdx); + if (entry.Type == "") + { + entry.Type = "Class"; + } + return true; +} + +bool UPKUtils::Deserialize(FObjectExport& entry, std::vector& data) +{ + if (data.size() < 68) + { + return false; + } + std::stringstream ss; + ss.write(data.data(), data.size()); + ss.read(reinterpret_cast(&entry.TypeRef), sizeof(entry.TypeRef)); + ss.read(reinterpret_cast(&entry.ParentClassRef), sizeof(entry.ParentClassRef)); + ss.read(reinterpret_cast(&entry.OwnerRef), sizeof(entry.OwnerRef)); + ss.read(reinterpret_cast(&entry.NameIdx), sizeof(entry.NameIdx)); + ss.read(reinterpret_cast(&entry.ArchetypeRef), sizeof(entry.ArchetypeRef)); + ss.read(reinterpret_cast(&entry.ObjectFlagsH), sizeof(entry.ObjectFlagsH)); + ss.read(reinterpret_cast(&entry.ObjectFlagsL), sizeof(entry.ObjectFlagsL)); + ss.read(reinterpret_cast(&entry.SerialSize), sizeof(entry.SerialSize)); + ss.read(reinterpret_cast(&entry.SerialOffset), sizeof(entry.SerialOffset)); + ss.read(reinterpret_cast(&entry.ExportFlags), sizeof(entry.ExportFlags)); + ss.read(reinterpret_cast(&entry.NetObjectCount), sizeof(entry.NetObjectCount)); + ss.read(reinterpret_cast(&entry.GUID), sizeof(entry.GUID)); + ss.read(reinterpret_cast(&entry.Unknown1), sizeof(entry.Unknown1)); + entry.NetObjects.resize(entry.NetObjectCount); + if (data.size() != 68 + entry.NetObjectCount) + { + return false; + } + if (entry.NetObjectCount > 0) + { + ss.read(reinterpret_cast(entry.NetObjects.data()), entry.NetObjects.size()*4); + } + /// memory variables + entry.EntrySize = data.size(); + entry.Name = IndexToName(entry.NameIdx); + entry.FullName = entry.Name; + if (entry.OwnerRef != 0) + { + entry.FullName = ResolveFullName(entry.OwnerRef) + "." + entry.Name; + } + entry.Type = ObjRefToName(entry.TypeRef); + if (entry.Type == "") + { + entry.Type = "Class"; + } + return true; +} + +std::vector UPKUtils::SerializeHeader() +{ + std::stringstream ss; + ss.write(reinterpret_cast(&Summary.Signature), 4); + int32_t Ver = (Summary.LicenseeVersion << 16) + Summary.Version; + ss.write(reinterpret_cast(&Ver), 4); + ss.write(reinterpret_cast(&Summary.HeaderSize), 4); + ss.write(reinterpret_cast(&Summary.FolderNameLength), 4); + if (Summary.FolderNameLength > 0) + { + ss.write(Summary.FolderName.c_str(), Summary.FolderNameLength); + } + ss.write(reinterpret_cast(&Summary.PackageFlags), 4); + ss.write(reinterpret_cast(&Summary.NameCount), 4); + ss.write(reinterpret_cast(&Summary.NameOffset), 4); + ss.write(reinterpret_cast(&Summary.ExportCount), 4); + ss.write(reinterpret_cast(&Summary.ExportOffset), 4); + ss.write(reinterpret_cast(&Summary.ImportCount), 4); + ss.write(reinterpret_cast(&Summary.ImportOffset), 4); + ss.write(reinterpret_cast(&Summary.DependsOffset), 4); + ss.write(reinterpret_cast(&Summary.SerialOffset), 4); + ss.write(reinterpret_cast(&Summary.Unknown2), 4); + ss.write(reinterpret_cast(&Summary.Unknown3), 4); + ss.write(reinterpret_cast(&Summary.Unknown4), 4); + ss.write(reinterpret_cast(&Summary.GUID), sizeof(Summary.GUID)); + ss.write(reinterpret_cast(&Summary.GenerationsCount), 4); + for (unsigned i = 0; i < Summary.GenerationsCount; ++i) + { + FGenerationInfo Entry = Summary.Generations[i]; + ss.write(reinterpret_cast(&Entry.ExportCount), 4); + ss.write(reinterpret_cast(&Entry.NameCount), 4); + ss.write(reinterpret_cast(&Entry.NetObjectCount), 4); + } + ss.write(reinterpret_cast(&Summary.EngineVersion), 4); + ss.write(reinterpret_cast(&Summary.CookerVersion), 4); + ss.write(reinterpret_cast(&Summary.CompressionFlags), 4); + ss.write(reinterpret_cast(&Summary.NumCompressedChunks), 4); + for (unsigned i = 0; i < Summary.NumCompressedChunks; ++i) + { + FCompressedChunk CompressedChunk = Summary.CompressedChunks[i]; + ss.write(reinterpret_cast(&CompressedChunk.UncompressedOffset), 4); + ss.write(reinterpret_cast(&CompressedChunk.UncompressedSize), 4); + ss.write(reinterpret_cast(&CompressedChunk.CompressedOffset), 4); + ss.write(reinterpret_cast(&CompressedChunk.CompressedSize), 4); + } + if (Summary.UnknownDataChunk.size() > 0) + { + ss.write(Summary.UnknownDataChunk.data(), Summary.UnknownDataChunk.size()); + } + for (unsigned i = 0; i < Summary.NameCount; ++i) + { + FNameEntry Entry = NameTable[i]; + ss.write(reinterpret_cast(&Entry.NameLength), 4); + if (Entry.NameLength > 0) + { + ss.write(Entry.Name.c_str(), Entry.NameLength); + } + ss.write(reinterpret_cast(&Entry.NameFlagsL), 4); + ss.write(reinterpret_cast(&Entry.NameFlagsH), 4); + } + for (unsigned i = 1; i <= Summary.ImportCount; ++i) + { + FObjectImport Entry = ImportTable[i]; + ss.write(reinterpret_cast(&Entry.PackageIdx), sizeof(Entry.PackageIdx)); + ss.write(reinterpret_cast(&Entry.TypeIdx), sizeof(Entry.TypeIdx)); + ss.write(reinterpret_cast(&Entry.OwnerRef), sizeof(Entry.OwnerRef)); + ss.write(reinterpret_cast(&Entry.NameIdx), sizeof(Entry.NameIdx)); + } + for (unsigned i = 1; i <= Summary.ExportCount; ++i) + { + FObjectExport Entry = ExportTable[i]; + ss.write(reinterpret_cast(&Entry.TypeRef), sizeof(Entry.TypeRef)); + ss.write(reinterpret_cast(&Entry.ParentClassRef), sizeof(Entry.ParentClassRef)); + ss.write(reinterpret_cast(&Entry.OwnerRef), sizeof(Entry.OwnerRef)); + ss.write(reinterpret_cast(&Entry.NameIdx), sizeof(Entry.NameIdx)); + ss.write(reinterpret_cast(&Entry.ArchetypeRef), sizeof(Entry.ArchetypeRef)); + ss.write(reinterpret_cast(&Entry.ObjectFlagsH), sizeof(Entry.ObjectFlagsH)); + ss.write(reinterpret_cast(&Entry.ObjectFlagsL), sizeof(Entry.ObjectFlagsL)); + ss.write(reinterpret_cast(&Entry.SerialSize), sizeof(Entry.SerialSize)); + ss.write(reinterpret_cast(&Entry.SerialOffset), sizeof(Entry.SerialOffset)); + ss.write(reinterpret_cast(&Entry.ExportFlags), sizeof(Entry.ExportFlags)); + ss.write(reinterpret_cast(&Entry.NetObjectCount), sizeof(Entry.NetObjectCount)); + ss.write(reinterpret_cast(&Entry.GUID), sizeof(Entry.GUID)); + ss.write(reinterpret_cast(&Entry.Unknown1), sizeof(Entry.Unknown1)); + if (Entry.NetObjectCount > 0) + { + ss.write(reinterpret_cast(Entry.NetObjects.data()), Entry.NetObjects.size()*4); + } + } + if (DependsBuf.size() > 0) + { + ss.write(DependsBuf.data(), DependsBuf.size()); + } + std::vector ret(ss.tellp()); + ss.read(ret.data(), ret.size()); + return ret; +} diff --git a/UPKUtils.h b/UPKUtils.h index 379dcad..da20401 100644 --- a/UPKUtils.h +++ b/UPKUtils.h @@ -1,63 +1,63 @@ -#ifndef UPKUTILS_H -#define UPKUTILS_H - -#include "UPKInfo.h" -#include "UObjectFactory.h" -#include - -class UPKUtils: public UPKInfo -{ -public: - UPKUtils() {} - ~UPKUtils() {} - UPKUtils(const char* filename); - /// Read package header - bool Read(const char* filename); - bool Reload(); - bool IsLoaded() { return (UPKFile.is_open() && UPKFile.good()); }; - size_t GetFileSize() { return UPKFileSize; } - /// Extract serialized data - std::vector GetExportData(uint32_t idx); - void SaveExportData(uint32_t idx); - size_t GetScriptSize(uint32_t idx); - size_t GetScriptMemSize(uint32_t idx); - size_t GetScriptRelOffset(uint32_t idx); - /// Move/expand export object data (legacy functions for backward compatibility) - bool MoveExportData(uint32_t idx, uint32_t newObjectSize = 0); - bool UndoMoveExportData(uint32_t idx); - /// Move/resize export object data (new functions) - /// You cannot resize object without moving it first - /// You can move object without resizing it - std::vector GetResizedDataChunk(uint32_t idx, int newObjectSize = -1, int resizeAt = -1); - bool ResizeInPlace(uint32_t idx, int newObjectSize = -1, int resizeAt = -1); - bool MoveResizeObject(uint32_t idx, int newObjectSize = -1, int resizeAt = -1); - bool UndoMoveResizeObject(uint32_t idx); - /// Deserialize - std::string Deserialize(UObjectReference ObjRef, bool TryUnsafe = false, bool QuickMode = false); - bool Deserialize(FNameEntry& entry, std::vector& data); - bool Deserialize(FObjectImport& entry, std::vector& data); - bool Deserialize(FObjectExport& entry, std::vector& data); - /// Write data - bool CheckValidFileOffset(size_t offset); - bool WriteExportData(uint32_t idx, std::vector data, std::vector *backupData = nullptr); - bool WriteNameTableName(uint32_t idx, std::string name); - bool WriteData(size_t offset, std::vector data, std::vector *backupData = nullptr); - size_t FindDataChunk(std::vector data, size_t beg = 0, size_t limit = 0); - std::vector GetBulkData(size_t offset, std::vector data); - /// UPK serialization - std::vector SerializeHeader(); - /// Aggressive patching functions - bool AddNameEntry(FNameEntry Entry); - bool AddImportEntry(FObjectImport Entry); - bool AddExportEntry(FObjectExport Entry); - bool LinkChild(UObjectReference OwnerRef, UObjectReference ChildRef); - /* - bool ResizeInPlace(UObjectReference ObjRef, uint32_t newObjectSize); - */ -private: - std::string UPKFileName; - std::fstream UPKFile; - size_t UPKFileSize; -}; - -#endif // UPKUTILS_H +#ifndef UPKUTILS_H +#define UPKUTILS_H + +#include "UPKInfo.h" +#include "UObjectFactory.h" +#include + +class UPKUtils: public UPKInfo +{ +public: + UPKUtils() {} + ~UPKUtils() {} + UPKUtils(const char* filename); + /// Read package header + bool Read(const char* filename); + bool Reload(); + bool IsLoaded() { return (UPKFile.is_open() && UPKFile.good()); }; + size_t GetFileSize() { return UPKFileSize; } + /// Extract serialized data + std::vector GetExportData(uint32_t idx); + void SaveExportData(uint32_t idx); + size_t GetScriptSize(uint32_t idx); + size_t GetScriptMemSize(uint32_t idx); + size_t GetScriptRelOffset(uint32_t idx); + /// Move/expand export object data (legacy functions for backward compatibility) + bool MoveExportData(uint32_t idx, uint32_t newObjectSize = 0); + bool UndoMoveExportData(uint32_t idx); + /// Move/resize export object data (new functions) + /// You cannot resize object without moving it first + /// You can move object without resizing it + std::vector GetResizedDataChunk(uint32_t idx, int newObjectSize = -1, int resizeAt = -1); + bool ResizeInPlace(uint32_t idx, int newObjectSize = -1, int resizeAt = -1); + bool MoveResizeObject(uint32_t idx, int newObjectSize = -1, int resizeAt = -1); + bool UndoMoveResizeObject(uint32_t idx); + /// Deserialize + std::string Deserialize(UObjectReference ObjRef, bool TryUnsafe = false, bool QuickMode = false); + bool Deserialize(FNameEntry& entry, std::vector& data); + bool Deserialize(FObjectImport& entry, std::vector& data); + bool Deserialize(FObjectExport& entry, std::vector& data); + /// Write data + bool CheckValidFileOffset(size_t offset); + bool WriteExportData(uint32_t idx, std::vector data, std::vector *backupData = nullptr); + bool WriteNameTableName(uint32_t idx, std::string name); + bool WriteData(size_t offset, std::vector data, std::vector *backupData = nullptr); + size_t FindDataChunk(std::vector data, size_t beg = 0, size_t limit = 0); + std::vector GetBulkData(size_t offset, std::vector data); + /// UPK serialization + std::vector SerializeHeader(); + /// Aggressive patching functions + bool AddNameEntry(FNameEntry Entry); + bool AddImportEntry(FObjectImport Entry); + bool AddExportEntry(FObjectExport Entry); + bool LinkChild(UObjectReference OwnerRef, UObjectReference ChildRef); + /* + bool ResizeInPlace(UObjectReference ObjRef, uint32_t newObjectSize); + */ +private: + std::string UPKFileName; + std::fstream UPKFile; + size_t UPKFileSize; +}; + +#endif // UPKUTILS_H diff --git a/doc/PatchUPK_Mod_Example.txt b/doc/PatchUPK_Mod_Example.txt index 05209c8..a917269 100644 --- a/doc/PatchUPK_Mod_Example.txt +++ b/doc/PatchUPK_Mod_Example.txt @@ -1,141 +1,141 @@ -MOD_NAME=PatchUPK Mod Example -AUTHOR=wghost81 aka Wasteland Ghost -DESCRIPTION=This is sample mod file to demonstrate program features. Do not try to install this! - -For more info see PatchUPK_Readme.txt. - -Mod files have to be plain ASCII text files! Do not use Unicode! - -Compatible with ToolBoks Custom Mod format, can be used to install ToolBoks custom mods for EU/EW. - -Version: 3.0 - -Compatible with XCOM Enemy Unknown / Enemy Within version: - - versions list - - -// Next line was placed here to generate error and prevent accidental installation of the sample. -UPK_FILE=FileDoesNotExist.upk - -{ Curly brackets are used for comments. - Comments can be multi-line. - Do not forget to close opened brackets. } - -{ Do not use curly brackets inside comments!!! } - -// You can use C-style comments as well - -/* Multi-line - C-style - comment */ - -/* Open XComStrategyGame.upk - Do not add path to upk file if you're using it with PatcherGUI! - You need to open upk file only once to apply multiple changes, - but you still can use ToolBoks style and specify upk file name for every single change: - the file will not be reopened. */ -UPK_FILE=XComStrategyGame.upk - -// ToolBoks-style patching: absolute offsets followed by binary replacement data - -/* Offsets can be dec or hex values. Hex values should begin with 0x - Offset key sets patching scope to entire UPK file. You can change anything, except - first 8 bytes: package signature and version. */ -OFFSET=0x1234ABC -[MODDED_HEX] -// You can use comments here... -00 01 0A 0B FF /* ... and here... */ 00 AA /* ... and here... */ -01 23 -45 - -// Using external binary files - -/* If you're using PatcherGUI, path to binary file should be either absolute - or relative to mod file (Your_mod_file_name.txt) location. - File name should match Full.Object.Name.ext pattern. Allowed extensions are: - .NameEntry for NameTable entry - .ImportEntry for ImportTable entry - .ExportEntry for ExportTable entry */ -MODDED_FILE=Path-to-file/Full.Object.Name.ext - -// User-friendly way to set values - -// You can use BYTE key to set single byte value. Byte values are unsigned! -BYTE=0x0A { hex } -BYTE=10 { or dec } - -// You can use FLOAT key to set 4-byte floating point value. -FLOAT=123.4 - -/* You can use INTEGER key to set 4-byte integer value. - WARNING! No hex values are allowed! For hex representation use UNSIGNED key! */ -INTEGER=15 - -// You can use UNSIGNED key to set 4-byte unsigned integer value. -UNSIGNED=0xA { hex } -UNSIGNED=10 { or dec } - -// Object-oriented patching - -// You can use full object name (as it is shown in UE Explorer) to find it inside UPK file -OBJECT=XGBattleDesc.InitAlienLoadoutInfos -OBJECT=XGStrategyAI.GetAltWeapon:AUTO -OBJECT=XGStrategyAI.GetAltWeapon:INPL -OBJECT=XGStrategyAI.GetAltWeapon:MOVE -OBJECT=XGStrategyAI.GetAltWeapon:KEEP -/* Empty specifier is equal to KEEP. KEEP is the safest behaviour: all changes are applied - to current scope only and if data chunk length exceeds current scope, program will - output an error. - MOVE forces moving object's data to the end of UPK file before applying any changes. - AUTO allows program to auto-move/resize object when needed. - INPL works similar to AUTO, but performs object resize in place without moving. - Note that specifiers themselves do not make any changes: they work when you start to - write actual data. */ - -/* You can use relative offsets to patch data inside specified object. - If current scope is set to package, relative offset will be added to global offset. */ -REL_OFFSET=0x28 - -// Changing names and expanding functions - -// Rename GetAltWeapon to #GetPodProgs -RENAME=GetAltWeapon:#GetPodProgs - -/* You can expand function by specifying its new ObjectSize - (not file size from script header, but full data chunk length!) - If new size is equal to old size, function will not be expanded. - EXPAND_FUNCTION sets current offset value to new data offset, - so you don't need to specify offset after using it. */ -EXPAND_FUNCTION=XGStrategyAI.GetAltWeapon:300 -// You can undo function expand. -EXPAND_UNDO=XGStrategyAI.GetAltWeapon - -// Before-after style patching - -[BEFORE_HEX] -01 AA BC 23 -[/BEFORE_HEX] - -[AFTER_HEX] -BB AC 22 13 -[/AFTER_HEX] - -// Using pseudo-code - -// You can use pseudo-code instead of hex references. For more info see PatchUPK_Readme.txt. -OBJECT=XGStrategyAI.GetNumOutsiders -[REPLACEMENT_CODE] -// if(Game().GetDifficulty() >= 2) -07 [@label1] 99 19 1B 16 0A 00 00 1B 16 2C <%b2> 16 - // return 2 - 04 2C 02 -// goto (else) -06 [@label2] - [#label1] - // return 1 - 04 26 -[#label2] -// return ReturnValue -04 3A <.ReturnValue> -// EOS -53 +MOD_NAME=PatchUPK Mod Example +AUTHOR=wghost81 aka Wasteland Ghost +DESCRIPTION=This is sample mod file to demonstrate program features. Do not try to install this! + +For more info see PatchUPK_Readme.txt. + +Mod files have to be plain ASCII text files! Do not use Unicode! + +Compatible with ToolBoks Custom Mod format, can be used to install ToolBoks custom mods for EU/EW. + +Version: 3.0 + +Compatible with XCOM Enemy Unknown / Enemy Within version: + - versions list + + +// Next line was placed here to generate error and prevent accidental installation of the sample. +UPK_FILE=FileDoesNotExist.upk + +{ Curly brackets are used for comments. + Comments can be multi-line. + Do not forget to close opened brackets. } + +{ Do not use curly brackets inside comments!!! } + +// You can use C-style comments as well + +/* Multi-line + C-style + comment */ + +/* Open XComStrategyGame.upk + Do not add path to upk file if you're using it with PatcherGUI! + You need to open upk file only once to apply multiple changes, + but you still can use ToolBoks style and specify upk file name for every single change: + the file will not be reopened. */ +UPK_FILE=XComStrategyGame.upk + +// ToolBoks-style patching: absolute offsets followed by binary replacement data + +/* Offsets can be dec or hex values. Hex values should begin with 0x + Offset key sets patching scope to entire UPK file. You can change anything, except + first 8 bytes: package signature and version. */ +OFFSET=0x1234ABC +[MODDED_HEX] +// You can use comments here... +00 01 0A 0B FF /* ... and here... */ 00 AA /* ... and here... */ +01 23 +45 + +// Using external binary files + +/* If you're using PatcherGUI, path to binary file should be either absolute + or relative to mod file (Your_mod_file_name.txt) location. + File name should match Full.Object.Name.ext pattern. Allowed extensions are: + .NameEntry for NameTable entry + .ImportEntry for ImportTable entry + .ExportEntry for ExportTable entry */ +MODDED_FILE=Path-to-file/Full.Object.Name.ext + +// User-friendly way to set values + +// You can use BYTE key to set single byte value. Byte values are unsigned! +BYTE=0x0A { hex } +BYTE=10 { or dec } + +// You can use FLOAT key to set 4-byte floating point value. +FLOAT=123.4 + +/* You can use INTEGER key to set 4-byte integer value. + WARNING! No hex values are allowed! For hex representation use UNSIGNED key! */ +INTEGER=15 + +// You can use UNSIGNED key to set 4-byte unsigned integer value. +UNSIGNED=0xA { hex } +UNSIGNED=10 { or dec } + +// Object-oriented patching + +// You can use full object name (as it is shown in UE Explorer) to find it inside UPK file +OBJECT=XGBattleDesc.InitAlienLoadoutInfos +OBJECT=XGStrategyAI.GetAltWeapon:AUTO +OBJECT=XGStrategyAI.GetAltWeapon:INPL +OBJECT=XGStrategyAI.GetAltWeapon:MOVE +OBJECT=XGStrategyAI.GetAltWeapon:KEEP +/* Empty specifier is equal to KEEP. KEEP is the safest behaviour: all changes are applied + to current scope only and if data chunk length exceeds current scope, program will + output an error. + MOVE forces moving object's data to the end of UPK file before applying any changes. + AUTO allows program to auto-move/resize object when needed. + INPL works similar to AUTO, but performs object resize in place without moving. + Note that specifiers themselves do not make any changes: they work when you start to + write actual data. */ + +/* You can use relative offsets to patch data inside specified object. + If current scope is set to package, relative offset will be added to global offset. */ +REL_OFFSET=0x28 + +// Changing names and expanding functions + +// Rename GetAltWeapon to #GetPodProgs +RENAME=GetAltWeapon:#GetPodProgs + +/* You can expand function by specifying its new ObjectSize + (not file size from script header, but full data chunk length!) + If new size is equal to old size, function will not be expanded. + EXPAND_FUNCTION sets current offset value to new data offset, + so you don't need to specify offset after using it. */ +EXPAND_FUNCTION=XGStrategyAI.GetAltWeapon:300 +// You can undo function expand. +EXPAND_UNDO=XGStrategyAI.GetAltWeapon + +// Before-after style patching + +[BEFORE_HEX] +01 AA BC 23 +[/BEFORE_HEX] + +[AFTER_HEX] +BB AC 22 13 +[/AFTER_HEX] + +// Using pseudo-code + +// You can use pseudo-code instead of hex references. For more info see PatchUPK_Readme.txt. +OBJECT=XGStrategyAI.GetNumOutsiders +[REPLACEMENT_CODE] +// if(Game().GetDifficulty() >= 2) +07 [@label1] 99 19 1B 16 0A 00 00 1B 16 2C <%b2> 16 + // return 2 + 04 2C 02 +// goto (else) +06 [@label2] + [#label1] + // return 1 + 04 26 +[#label2] +// return ReturnValue +04 3A <.ReturnValue> +// EOS +53 diff --git a/doc/PatchUPK_Readme.txt b/doc/PatchUPK_Readme.txt index f7bf3c2..1f5f4e2 100644 --- a/doc/PatchUPK_Readme.txt +++ b/doc/PatchUPK_Readme.txt @@ -1,485 +1,485 @@ ------------------------------------------------------------------------------------------------------------------ - PatchUPK documentation file ------------------------------------------------------------------------------------------------------------------ - -PatchUPK is a console application for patching XCOM packages (upk files). Packages must be decompressed first -(using DecompressLZO or Gildor's decompress.exe). - -PatchUPK operates on special script files and applies subsequent changes to packages. - ------------------------------------------------------------------------------------------------------------------ - Script commands basics ------------------------------------------------------------------------------------------------------------------ - -There are two types of commands: keys and sections. Keys are followed by '=' character to separate them from -values and sections are enclosed into brackets '[' and ']'. - -Keys can have additional specifiers separated by ':' character. - ------------------------------------------------------------------------------------------------------------------ - Comments ------------------------------------------------------------------------------------------------------------------ - -PatchUPK supports two types of comments: curly brackets (text enclosed in '{' and '}') and C-style comments. -You can use comments anywhere inside a file. Examples: -/* Multi-line - comment */ -// Line comment -{ Curly brackets comment } - ------------------------------------------------------------------------------------------------------------------ - Controlling patcher behavior ------------------------------------------------------------------------------------------------------------------ - -There are currently two keys to modify Patcher behavior. - -UNINSTALL key is used to switch uninstall feature on and off: UNINSTALL=FALSE will switch uninstall feature off -and UNINSTALL=TRUE will switch it back on. By default uninstall feature is on. You can use this key in the middle -of the file to select what parts of your mod can be uninstalled safely and what parts can not. - -UPDATE_REL key allows to switch relative offset auto-update on and off. Default behavior is off. UPDATE_REL=TRUE -forces Patcher to update relative offset after each write operation. UPDATE_REL=FALSE tells Patcher to keep -relative offset unchanged. - ------------------------------------------------------------------------------------------------------------------ - Optional keys ------------------------------------------------------------------------------------------------------------------ - -MOD_NAME, AUTHOR and DESCRIPTION keys are used to provide author and mod information and can be used as mod's -readme. - ------------------------------------------------------------------------------------------------------------------ - Loading a package ------------------------------------------------------------------------------------------------------------------ - -GUID key adds the GUID to list of allowed GUIDs. Example: -GUID=3F3B9C3140E45D9C8E92AFABF2746525:XComStrategyGame.upk -GUID value should correspond with the output of ExtractNameLists program! You can add multiple GUIDs for the same -package and multiple GUIDs for multiple packages. Declare allowed GUIDs before you start loading a package! - -UPK_FILE key loads a package. Example: -UPK_FILE=XComGame.upk -Patcher opens the package, reads its header info, reconstructs full object names and additional info for "smart" -patching operations, so this command may take a while to complete. If the package is already opened, patcher -will not try to re-open it. - ------------------------------------------------------------------------------------------------------------------ - Setting a scope ------------------------------------------------------------------------------------------------------------------ - -There currently are five different scopes: -- Package -- Name Table Entry -- Import Table Entry -- Export Table Entry -- Object: serialized data referenced in the Export Table Entry - -OFFSET key sets the scope to an entire package (excluding first 8 bytes as changing package signature and version -is not allowed). The key sets a stream position to an ABSOLUTE file offset (i.e. from the very beginning - byte 0). -You can use dec and hex value representation. -OFFSET=0xA1B2 -OFFSET=12345 - -NAME_ENTRY, IMPORT_ENTRY and EXPORT_ENTRY keys set the scope to a table entry: name entry for NAME_ENTRY -and object entry for IMPORT_ENTRY and EXPORT_ENTRY: -NAME_ENTRY=GetAltWeapon // use unique name here -IMPORT_ENTRY=XComGame.XComGameInfo.PerkContents // use full object name here -EXPORT_ENTRY=XGStrategyAI.GetAltWeapon // use full object name here - -OBJECT key sets the scope to an export object: -OBJECT=XGStrategyAI.GetAltWeapon -This key has specifiers: -OBJECT=XGStrategyAI.GetAltWeapon:KEEP -OBJECT=XGStrategyAI.GetAltWeapon:MOVE -OBJECT=XGStrategyAI.GetAltWeapon:AUTO -OBJECT=XGStrategyAI.GetAltWeapon:INPL -If specifier is empty, KEEP is assumed. KEEP provides the most safest behavior: write operations will be performed -inside the scope only. It means, if a data chunk exceeds object's size, write operation will fail. -MOVE specifier forces moving object's data before applying any changes. -AUTO specifier allows program to auto-move/resize object when needed. -INPL specifier works similar to AUTO, but resizes object in-place instead of moving it. -Note that specifiers themselves do not make any changes: they work when you start to write actual data. - ------------------------------------------------------------------------------------------------------------------ - Setting a relative offset ------------------------------------------------------------------------------------------------------------------ - -REL_OFFSET key is used to set a relative offset inside the scope. Example: -REL_OFFSET=0x30 -REL_OFFSET=48 - ------------------------------------------------------------------------------------------------------------------ - Finding a binary data ------------------------------------------------------------------------------------------------------------------ - -FIND_HEX key is used to find the data and set a relative offset to the beginning of that data: -FIND_HEX=0A 0B 12 34 -The value of FIND_HEX is a space-separated hex-represented string of bytes. You can use comments and multiline -strings inside FIND_HEX value. - -FIND_HEX has two specifiers: BEG and END. Examples: -FIND_HEX=0A 0B 12 34:BEG -FIND_HEX=0A 0B 12 34:END -Using BEG will set the offset to the beginning of the search string and using END will set the offset to the -end of it. If no specifier is present, BEG is assumed. - ------------------------------------------------------------------------------------------------------------------ - Writing a binary data ------------------------------------------------------------------------------------------------------------------ - -MODDED_HEX key is used to write the data at relative offset inside current scope. The value is the same as for -FIND_HEX: space-separated hex-represented string of bytes, can be multiline, can use comments. -MODDED_HEX=0A 0B 12 34 -If AUTO or MOVE object specifier is used, program will attempt to auto-expand current object to fit MODDED_HEX -data. Auto-expand works for export object's serial data only, table entries won't be expanded. - -MODDED_FILE key is used to read modded binary data from separate file. -MODDED_FILE=path-to-file.ext - -MODDED_FILE is considered a total replacement for current scope: i.e. it should contain full object data. It is -also required to use Full.Object.Name.ext pattern for file name, so program could auto-set scope, based on file -name. Object data files should use .Type as their extension and table entries should use .NameEntry, -.ImportEntry and .ExportEntry respectively. Examples: -MODDED_FILE=XGStrategyAI.GetAltWeapon.Function -MODDED_FILE=GetAltWeapon.NameEntry -MODDED_FILE=XComGame.XComGameInfo.PerkContents.ImportEntry -MODDED_FILE=XGStrategyAI.GetAltWeapon.ExportEntry -You can use the same specifiers as for OBJECT key with MODDED_FILE key, although they will work for objects only -and will be ignored for table entries. After successful patching current scope will be set to the object. Since -modded file contains an object replacement code, export object data will be resized (expanded or shrunk) -accordingly if AUTO or MOVE specifier is set. - -BYTE, FLOAT, INTEGER and UNSIGNED keys are used to set corresponding values. BYTE value is unsigned dec or hex -represented 1-byte value. FLOAT and INTEGER are signed dec-represented 4-byte values. UNSIGNED is unsigned dec -or hex represented 4-byte value. - -NAMEIDX key is used to write UNameIndex 8-byte value determined by the name. Example: -NAMEIDX=GetAltWeapon - -OBJIDX key is used to write UObjectReference 4-byte signed value determined by the full name. Example: -OBJIDX=XGStrategyAI.GetAltWeapon - ------------------------------------------------------------------------------------------------------------------ - Modifying object table entry ------------------------------------------------------------------------------------------------------------------ - -RENAME key is used to rename an entry in the Name Table: -RENAME=GetAltWeapon:#GetPodProgs -Changing entry size is not allowed, so new name length should be equal to old name length. After successful -renaming current scope will be set to new Name Table name. - -If entry is already renamed program will just set current scope to it and continue patching. - ------------------------------------------------------------------------------------------------------------------ - Expanding a function ------------------------------------------------------------------------------------------------------------------ - -EXPAND_FUNCTION key is used to expand functions: -EXPAND_FUNCTION=XGStrategyAI.GetPossibleAliens:5828 -It will expand function code by adding 0B (nop) tokens and modifying serial and memory size accordingly and set -current scope to the object after successful patching. If function already is of requested size, it will do -nothing. -EXPAND_FUNCTION will also expand other objects by simply appending zeroes to the end of object data (for backward- -compatibility reasons). - -EXPAND_UNDO key is used to undo move/expand operation: -EXPAND_UNDO=XGStrategyAI.GetAltWeapon -As move/expand operation keeps original data in place, EXPAND_UNDO simply restores ExportTable references. -No other changes are made and no "garbage" (expanded object binary) is collected. Used mostly for uninstall -purposes. - ------------------------------------------------------------------------------------------------------------------ - Section-style patching ------------------------------------------------------------------------------------------------------------------ - -All the keys can also be written as sections (for compatibility reasons and because IDIC matters). - -[MODDED_HEX] section is an equivalent of MODDED_HEX key. Example: -[MODDED_HEX] -AB CD 12 34 -You can use [/MODDED_HEX] to visually mark section end, but it is purely optional. - -[FIND_HEX] (with optional [/FIND_HEX]) is an equivalent of FIND_HEX key. - ------------------------------------------------------------------------------------------------------------------ - Find-and-replace (FNR) style patching (UPKModder compatibility) ------------------------------------------------------------------------------------------------------------------ - -BEFORE_HEX and AFTER_HEX are unique commands that behave similar to those of UPKModder. - -[BEFORE_HEX] (with optional [/BEFORE_HEX]) sets current scope to search string. - -[AFTER_HEX] (with optional [/AFTER_HEX]) string is considered a replacement for BEFORE_HEX string and can not be -used without calling BEFORE_HEX first. - -If FNR operation is performed within export object scope it will respect OBJECT key specifiers: for KEEP it will -require AFTER_HEX data length be equal to BEFORE_HEX data length and for AUTO and MOVE it will auto-expand -(or shrink) BEFORE_HEX scope to fit AFTER_HEX data. Program will auto-adjust script serial size for objects with -scripts (functions and states), but it won't do anything about script memory size as it doesn't know anything -about it (see "Using pseudo-code" section below for pseudo-code version which does calculate and rewrite both -serial and memory sizes). - -If FNR operation is performed within global scope or name/import/export tables, it will require AFTER_HEX data -length to match with BEFORE_HEX data length. - -See "Using pseudo-code" section for BEFORE_CODE/AFTER_CODE combination, which can auto-update both serial and -memory sizes. - -REPLACE_HEX/REPLACE_CODE keys are used to batch-replace specified data. They perform subsequent BEFORE/AFTER -patching inside current scope until all the blocks of before data are found and replaced. - -Example: -REPLACE_CODE=<.LocalVarA>:<.LocalVarB> - ------------------------------------------------------------------------------------------------------------------ - Writing bulk data ------------------------------------------------------------------------------------------------------------------ - -BULK_DATA/BULK_FILE keys allow to write BulkDataMirror objects which use absolute file offset to mark raw data -file position. BulkDataMirror objects are used to store textures and sounds inside cooked packages. - -Examples: -BULK_DATA=01 02 03 04 -BULK_FILE=path/to/binaryfile.ext - ------------------------------------------------------------------------------------------------------------------ - Using pseudo-code ------------------------------------------------------------------------------------------------------------------ - -As PatchUPK can auto-convert object/name references to hex code, you can write scripts (and plain references) in -pseudo-code. - -Pseudo-code is enclosed in '<' and '>' brackets. You can use any type of white-spaces and comments inside brackets. -Example: -<%b -// user-changeable section: set sniper class probability -25 -// user-changeable section end -> - -------- -Syntax: -------- -1. Numeric constants: <%c Value>, where c is a single-character format specifier: - f - float (4 bytes) - i - signed integer (4 bytes) - u - unsigned integer (4 bytes) - s - short unsigned integer (2 bytes) - b - byte (1 byte) - t - text (variable-size string) - Examples: - <%f 1.0> - <%i -1> - <%s 1> - <%u 1> - <%b 1> - <%t "text"> - <%t "text with spaces "and inner quotes""> -2. Object references: - - class reference, should begin with "Class."! - - object reference (full object name). - <.LocalObjName> - local object reference, should begin with ".". Full name is reconstructed by prepending - local name with full name of current export object. Can not be used outside of export object scope. - <@MemberObjName> - member object reference, full object name is reconstructed by prepending member name - with class name determined from current export object name. Can not be used outside of export object scope. - - alias reference (see ALIAS key description below). - - null object reference (0x00000000) with 4 serial and 8 memory size. -3. Name reference: - - plain simple name. -4. [@label_name] and [#label_name], [@] and () - labels and references for auto-calculating memory offsets - (see below). - ------------------- -Keys and sections: ------------------- -MODDED_CODE (key and section) - used to write a modded code at current scope, equivalent of MODDED_HEX. -FIND_CODE (key and section) - used to find the code, equivalent of FIND_HEX. -INSERT_CODE (key and section) - used to insert a code at current position. Requires AUTO behavior, as it will -resize current object to insert new data. Works with export object data only. Does not change memory/file size -of a script! Useful for inserting additional default properties into default property list. Warning! Using -this function for mod distribution is not recommended, as if applied twice it will add the code twice! -BEFORE_CODE and AFTER_CODE (sections) - behave like BEFORE_HEX and AFTER_HEX, but calculate and write correct -serial and memory sizes in case of expanding/shrinking the object. - -Pseudo-code example: -[MODDED_CODE] -0F 00 <.arrRemove> 1B 00 <.arrRemove> 1E <%f0.75> 16 - -Adding default property example: -OBJECT=Default__XComMeldContainerSpawnPoint:AUTO -FIND_CODE= -[INSERT_CODE] - - -<%u0> -<%u0> -<%b1> - -FNR example (from Drakous79 "Default Helmet and Armor Tint" mod). This will correctly expand a function, -calculate and write script serial and memory sizes: -[BEFORE_CODE] -04 00 <.kSoldier> -[AFTER_CODE] -0F 35 00 00 -35 00 01 00 -<.kSoldier> 1D <%u305> -0F 35 00 00 -35 00 01 00 -<.kSoldier> 93 2C <%b10> 26 16 -04 00 <.kSoldier> - --------- -Aliases: --------- - -ALIAS (key) - used to define alias name for a portion of code. Syntax: - ALIAS=NAME:replacement code - Example: - ALIAS=GameDiff:19 1B 16 0A 00 00 1B 16 - Usage: - [MODDED_CODE] - // if(Game().GetDifficulty() >= 2) - 07 00 00 99 2C <%b2> 16 - -Aliases are searched scope-wise: if alias is defined inside object scope it is considered local to that -scope. While searching for alias replacement code program first looks in the local scope and then in -the global scope. This means you can have different aliases with the same names inside different scopes. - -Example of locally defined alias: -OBJECT=XGStrategyAI.GetNumOutsiders:AUTO -ALIAS=GameDiff:19 1B 16 0A 00 00 1B 16 -[MODDED_CODE] -// if(Game().GetDifficulty() >= 2) -07 00 00 99 2C <%b2> 16 - -Example of globally defined alias: -ALIAS=GameDiff:19 1B 16 0A 00 00 1B 16 -OBJECT=XGStrategyAI.GetNumOutsiders:AUTO -[MODDED_CODE] -// if(Game().GetDifficulty() >= 2) -07 00 00 99 2C <%b2> 16 - ------------------ -Replacement code: ------------------ - -REPLACEMENT_CODE (key and section) is used to completely replace export object script with the new one. Works -only with objects which have scripts (like functions and states). Will expand/shrink object if necessary and -if current behavior is set to "AUTO" or "MOVE". Will auto-calculate and re-write memory and serial script size. - -You can use CODE keys/sections with pure/partial hex data, but in this case the memory size will be wrong and -you will need to set it manually. If you choose to do this, you should do it after REPLACEMENT_CODE call. - ----------------------- -Labels and references: ----------------------- - -You can label a token inside a code with [#label] and reference it with [@label]. Label names must be unique -(see example below). Don't use labels if you mix pure hex with pseudo-code, as references will be wrong! - -Example: -OBJECT=XGStrategyAI.GetNumOutsiders:AUTO -[REPLACEMENT_CODE] -// if(Game().GetDifficulty() >= 2) -07 [@label1] 99 19 1B 16 0A 00 00 1B 16 2C <%b2> 16 - // return 2 - 04 2C 02 -// goto (else) -06 [@label2] - [#label1] - // return 1 - 04 26 -[#label2] -// return ReturnValue -04 3A <.ReturnValue> -// EOS -53 - -You can use empty reference [@] and a pair of parentheses to auto-calculate memory size/skip size. Example: -// if(Game().GetDifficulty() >= 2) -07 [@label1] 99 19 1B 16 [@] 00 ( 1B 16 ) 2C <%b2> 16 - -[@] marks the place of memory size to calculate and parentheses mark the beginning and the end of the code -which size needs to be auto-calculated. - ------------------------------------------------------------------------------------------------------------------ - Adding new names and objects ------------------------------------------------------------------------------------------------------------------ - -Warning! This feature is highly experimental and can completely mess up your packages! Use with caution! - -There are three main keys/sections for adding new objects: -1. ADD_NAME_ENTRY - adds a name entry into the name table. -2. ADD_IMPORT_ENTRY - adds an import entry into the import table. -3. ADD_EXPORT_ENTRY - adds an export entry into the export table and links the new object to specified owner. - Always writes at least 16 bytes long serialized data: - PrevObjRef + Empty DefaultProperties List + NextObjRef - -Example: adding a new WGhost81 local variable to XGStrategyAI.GetNumOutsiders function - -[ADD_NAME_ENTRY] -<%u9> // string length (including terminating null) -<%t"WGhost81"> // ASCII null-terminated string -<%u0x00000000> // flags L (always the same) -<%u0x00070010> // flags H (always the same) - -[ADD_EXPORT_ENTRY] - // Type - // ParentClassRef - // OwnerRef - // NameIdx - // ArchetypeRef -<%u0x00000000> // flags H -<%u0x00070004> // flags L -<%u40> // serial size -<%u0> // serial offset -<%u0> // export flags -<%u0> // net objects count -<%u0> // GUID1, zero if net objects count == 0 -<%u0> // GUID2, zero if net objects count == 0 -<%u0> // GUID3, zero if net objects count == 0 -<%u0> // GUID4, zero if net objects count == 0 -<%u0> // unknown, zero if net objects count == 0 - -OBJECT=XGStrategyAI.GetNumOutsiders.WGhost81 -REL_OFFSET=16 // skip PrevObjRef + Empty DefaultProperties List + NextObjRef -[MODDED_CODE] -<%s1> // ArrayDim -<%s0> // ElementSize -<%u0x00000000> // flags L -<%u0x00000000> // flags H - // CategoryIndex - // ArrayEnumRef - -Note that all entries are actually raw data written in PatchUPK pseudo-code. Patcher will get it as pure HEX -code, deserialize it and link to owner if necessary. Patcher will not construct proper serial data for you, -you should do it yourself after adding an object! - ------------------------------------------------------------------------------------------------------------------ - Legacy support: deprecated/renamed keys ------------------------------------------------------------------------------------------------------------------ - -You can still use those, but it is recommended to move on to new commands. - -FUNCTION key is an alias to OBJECT key. Renamed for obvious reasons: not all objects are functions. - -FUNCTION_FILE key is an alias to MODDED_FILE key. - -NAMELIST_NAME key is an alias to much shorter RENAME key. - ------------------------------------------------------------------------------------------------------------------ - Uninstall scripts ------------------------------------------------------------------------------------------------------------------ - -When installing mod PatchUPK automatically generates uninstall script and writes it to -your_mod_file_name.uninstall.txt. "Installing" .uninstall.txt "mod" will not generate another uninstall file. -In case your_mod_file_name.uninstall.txt already exists, program will generate your_mod_file_name.uninstall1.txt -and so on. - -Uninstall data are taken directly from existing package before rewriting them. - ------------------------------------------------------------------------------------------------------------------ - TO DO ------------------------------------------------------------------------------------------------------------------ - -Script compiler? :) +----------------------------------------------------------------------------------------------------------------- + PatchUPK documentation file +----------------------------------------------------------------------------------------------------------------- + +PatchUPK is a console application for patching XCOM packages (upk files). Packages must be decompressed first +(using DecompressLZO or Gildor's decompress.exe). + +PatchUPK operates on special script files and applies subsequent changes to packages. + +----------------------------------------------------------------------------------------------------------------- + Script commands basics +----------------------------------------------------------------------------------------------------------------- + +There are two types of commands: keys and sections. Keys are followed by '=' character to separate them from +values and sections are enclosed into brackets '[' and ']'. + +Keys can have additional specifiers separated by ':' character. + +----------------------------------------------------------------------------------------------------------------- + Comments +----------------------------------------------------------------------------------------------------------------- + +PatchUPK supports two types of comments: curly brackets (text enclosed in '{' and '}') and C-style comments. +You can use comments anywhere inside a file. Examples: +/* Multi-line + comment */ +// Line comment +{ Curly brackets comment } + +----------------------------------------------------------------------------------------------------------------- + Controlling patcher behavior +----------------------------------------------------------------------------------------------------------------- + +There are currently two keys to modify Patcher behavior. + +UNINSTALL key is used to switch uninstall feature on and off: UNINSTALL=FALSE will switch uninstall feature off +and UNINSTALL=TRUE will switch it back on. By default uninstall feature is on. You can use this key in the middle +of the file to select what parts of your mod can be uninstalled safely and what parts can not. + +UPDATE_REL key allows to switch relative offset auto-update on and off. Default behavior is off. UPDATE_REL=TRUE +forces Patcher to update relative offset after each write operation. UPDATE_REL=FALSE tells Patcher to keep +relative offset unchanged. + +----------------------------------------------------------------------------------------------------------------- + Optional keys +----------------------------------------------------------------------------------------------------------------- + +MOD_NAME, AUTHOR and DESCRIPTION keys are used to provide author and mod information and can be used as mod's +readme. + +----------------------------------------------------------------------------------------------------------------- + Loading a package +----------------------------------------------------------------------------------------------------------------- + +GUID key adds the GUID to list of allowed GUIDs. Example: +GUID=3F3B9C3140E45D9C8E92AFABF2746525:XComStrategyGame.upk +GUID value should correspond with the output of ExtractNameLists program! You can add multiple GUIDs for the same +package and multiple GUIDs for multiple packages. Declare allowed GUIDs before you start loading a package! + +UPK_FILE key loads a package. Example: +UPK_FILE=XComGame.upk +Patcher opens the package, reads its header info, reconstructs full object names and additional info for "smart" +patching operations, so this command may take a while to complete. If the package is already opened, patcher +will not try to re-open it. + +----------------------------------------------------------------------------------------------------------------- + Setting a scope +----------------------------------------------------------------------------------------------------------------- + +There currently are five different scopes: +- Package +- Name Table Entry +- Import Table Entry +- Export Table Entry +- Object: serialized data referenced in the Export Table Entry + +OFFSET key sets the scope to an entire package (excluding first 8 bytes as changing package signature and version +is not allowed). The key sets a stream position to an ABSOLUTE file offset (i.e. from the very beginning - byte 0). +You can use dec and hex value representation. +OFFSET=0xA1B2 +OFFSET=12345 + +NAME_ENTRY, IMPORT_ENTRY and EXPORT_ENTRY keys set the scope to a table entry: name entry for NAME_ENTRY +and object entry for IMPORT_ENTRY and EXPORT_ENTRY: +NAME_ENTRY=GetAltWeapon // use unique name here +IMPORT_ENTRY=XComGame.XComGameInfo.PerkContents // use full object name here +EXPORT_ENTRY=XGStrategyAI.GetAltWeapon // use full object name here + +OBJECT key sets the scope to an export object: +OBJECT=XGStrategyAI.GetAltWeapon +This key has specifiers: +OBJECT=XGStrategyAI.GetAltWeapon:KEEP +OBJECT=XGStrategyAI.GetAltWeapon:MOVE +OBJECT=XGStrategyAI.GetAltWeapon:AUTO +OBJECT=XGStrategyAI.GetAltWeapon:INPL +If specifier is empty, KEEP is assumed. KEEP provides the most safest behavior: write operations will be performed +inside the scope only. It means, if a data chunk exceeds object's size, write operation will fail. +MOVE specifier forces moving object's data before applying any changes. +AUTO specifier allows program to auto-move/resize object when needed. +INPL specifier works similar to AUTO, but resizes object in-place instead of moving it. +Note that specifiers themselves do not make any changes: they work when you start to write actual data. + +----------------------------------------------------------------------------------------------------------------- + Setting a relative offset +----------------------------------------------------------------------------------------------------------------- + +REL_OFFSET key is used to set a relative offset inside the scope. Example: +REL_OFFSET=0x30 +REL_OFFSET=48 + +----------------------------------------------------------------------------------------------------------------- + Finding a binary data +----------------------------------------------------------------------------------------------------------------- + +FIND_HEX key is used to find the data and set a relative offset to the beginning of that data: +FIND_HEX=0A 0B 12 34 +The value of FIND_HEX is a space-separated hex-represented string of bytes. You can use comments and multiline +strings inside FIND_HEX value. + +FIND_HEX has two specifiers: BEG and END. Examples: +FIND_HEX=0A 0B 12 34:BEG +FIND_HEX=0A 0B 12 34:END +Using BEG will set the offset to the beginning of the search string and using END will set the offset to the +end of it. If no specifier is present, BEG is assumed. + +----------------------------------------------------------------------------------------------------------------- + Writing a binary data +----------------------------------------------------------------------------------------------------------------- + +MODDED_HEX key is used to write the data at relative offset inside current scope. The value is the same as for +FIND_HEX: space-separated hex-represented string of bytes, can be multiline, can use comments. +MODDED_HEX=0A 0B 12 34 +If AUTO or MOVE object specifier is used, program will attempt to auto-expand current object to fit MODDED_HEX +data. Auto-expand works for export object's serial data only, table entries won't be expanded. + +MODDED_FILE key is used to read modded binary data from separate file. +MODDED_FILE=path-to-file.ext + +MODDED_FILE is considered a total replacement for current scope: i.e. it should contain full object data. It is +also required to use Full.Object.Name.ext pattern for file name, so program could auto-set scope, based on file +name. Object data files should use .Type as their extension and table entries should use .NameEntry, +.ImportEntry and .ExportEntry respectively. Examples: +MODDED_FILE=XGStrategyAI.GetAltWeapon.Function +MODDED_FILE=GetAltWeapon.NameEntry +MODDED_FILE=XComGame.XComGameInfo.PerkContents.ImportEntry +MODDED_FILE=XGStrategyAI.GetAltWeapon.ExportEntry +You can use the same specifiers as for OBJECT key with MODDED_FILE key, although they will work for objects only +and will be ignored for table entries. After successful patching current scope will be set to the object. Since +modded file contains an object replacement code, export object data will be resized (expanded or shrunk) +accordingly if AUTO or MOVE specifier is set. + +BYTE, FLOAT, INTEGER and UNSIGNED keys are used to set corresponding values. BYTE value is unsigned dec or hex +represented 1-byte value. FLOAT and INTEGER are signed dec-represented 4-byte values. UNSIGNED is unsigned dec +or hex represented 4-byte value. + +NAMEIDX key is used to write UNameIndex 8-byte value determined by the name. Example: +NAMEIDX=GetAltWeapon + +OBJIDX key is used to write UObjectReference 4-byte signed value determined by the full name. Example: +OBJIDX=XGStrategyAI.GetAltWeapon + +----------------------------------------------------------------------------------------------------------------- + Modifying object table entry +----------------------------------------------------------------------------------------------------------------- + +RENAME key is used to rename an entry in the Name Table: +RENAME=GetAltWeapon:#GetPodProgs +Changing entry size is not allowed, so new name length should be equal to old name length. After successful +renaming current scope will be set to new Name Table name. + +If entry is already renamed program will just set current scope to it and continue patching. + +----------------------------------------------------------------------------------------------------------------- + Expanding a function +----------------------------------------------------------------------------------------------------------------- + +EXPAND_FUNCTION key is used to expand functions: +EXPAND_FUNCTION=XGStrategyAI.GetPossibleAliens:5828 +It will expand function code by adding 0B (nop) tokens and modifying serial and memory size accordingly and set +current scope to the object after successful patching. If function already is of requested size, it will do +nothing. +EXPAND_FUNCTION will also expand other objects by simply appending zeroes to the end of object data (for backward- +compatibility reasons). + +EXPAND_UNDO key is used to undo move/expand operation: +EXPAND_UNDO=XGStrategyAI.GetAltWeapon +As move/expand operation keeps original data in place, EXPAND_UNDO simply restores ExportTable references. +No other changes are made and no "garbage" (expanded object binary) is collected. Used mostly for uninstall +purposes. + +----------------------------------------------------------------------------------------------------------------- + Section-style patching +----------------------------------------------------------------------------------------------------------------- + +All the keys can also be written as sections (for compatibility reasons and because IDIC matters). + +[MODDED_HEX] section is an equivalent of MODDED_HEX key. Example: +[MODDED_HEX] +AB CD 12 34 +You can use [/MODDED_HEX] to visually mark section end, but it is purely optional. + +[FIND_HEX] (with optional [/FIND_HEX]) is an equivalent of FIND_HEX key. + +----------------------------------------------------------------------------------------------------------------- + Find-and-replace (FNR) style patching (UPKModder compatibility) +----------------------------------------------------------------------------------------------------------------- + +BEFORE_HEX and AFTER_HEX are unique commands that behave similar to those of UPKModder. + +[BEFORE_HEX] (with optional [/BEFORE_HEX]) sets current scope to search string. + +[AFTER_HEX] (with optional [/AFTER_HEX]) string is considered a replacement for BEFORE_HEX string and can not be +used without calling BEFORE_HEX first. + +If FNR operation is performed within export object scope it will respect OBJECT key specifiers: for KEEP it will +require AFTER_HEX data length be equal to BEFORE_HEX data length and for AUTO and MOVE it will auto-expand +(or shrink) BEFORE_HEX scope to fit AFTER_HEX data. Program will auto-adjust script serial size for objects with +scripts (functions and states), but it won't do anything about script memory size as it doesn't know anything +about it (see "Using pseudo-code" section below for pseudo-code version which does calculate and rewrite both +serial and memory sizes). + +If FNR operation is performed within global scope or name/import/export tables, it will require AFTER_HEX data +length to match with BEFORE_HEX data length. + +See "Using pseudo-code" section for BEFORE_CODE/AFTER_CODE combination, which can auto-update both serial and +memory sizes. + +REPLACE_HEX/REPLACE_CODE keys are used to batch-replace specified data. They perform subsequent BEFORE/AFTER +patching inside current scope until all the blocks of before data are found and replaced. + +Example: +REPLACE_CODE=<.LocalVarA>:<.LocalVarB> + +----------------------------------------------------------------------------------------------------------------- + Writing bulk data +----------------------------------------------------------------------------------------------------------------- + +BULK_DATA/BULK_FILE keys allow to write BulkDataMirror objects which use absolute file offset to mark raw data +file position. BulkDataMirror objects are used to store textures and sounds inside cooked packages. + +Examples: +BULK_DATA=01 02 03 04 +BULK_FILE=path/to/binaryfile.ext + +----------------------------------------------------------------------------------------------------------------- + Using pseudo-code +----------------------------------------------------------------------------------------------------------------- + +As PatchUPK can auto-convert object/name references to hex code, you can write scripts (and plain references) in +pseudo-code. + +Pseudo-code is enclosed in '<' and '>' brackets. You can use any type of white-spaces and comments inside brackets. +Example: +<%b +// user-changeable section: set sniper class probability +25 +// user-changeable section end +> + +------- +Syntax: +------- +1. Numeric constants: <%c Value>, where c is a single-character format specifier: + f - float (4 bytes) + i - signed integer (4 bytes) + u - unsigned integer (4 bytes) + s - short unsigned integer (2 bytes) + b - byte (1 byte) + t - text (variable-size string) + Examples: + <%f 1.0> + <%i -1> + <%s 1> + <%u 1> + <%b 1> + <%t "text"> + <%t "text with spaces "and inner quotes""> +2. Object references: + - class reference, should begin with "Class."! + - object reference (full object name). + <.LocalObjName> - local object reference, should begin with ".". Full name is reconstructed by prepending + local name with full name of current export object. Can not be used outside of export object scope. + <@MemberObjName> - member object reference, full object name is reconstructed by prepending member name + with class name determined from current export object name. Can not be used outside of export object scope. + - alias reference (see ALIAS key description below). + - null object reference (0x00000000) with 4 serial and 8 memory size. +3. Name reference: + - plain simple name. +4. [@label_name] and [#label_name], [@] and () - labels and references for auto-calculating memory offsets + (see below). + +------------------ +Keys and sections: +------------------ +MODDED_CODE (key and section) - used to write a modded code at current scope, equivalent of MODDED_HEX. +FIND_CODE (key and section) - used to find the code, equivalent of FIND_HEX. +INSERT_CODE (key and section) - used to insert a code at current position. Requires AUTO behavior, as it will +resize current object to insert new data. Works with export object data only. Does not change memory/file size +of a script! Useful for inserting additional default properties into default property list. Warning! Using +this function for mod distribution is not recommended, as if applied twice it will add the code twice! +BEFORE_CODE and AFTER_CODE (sections) - behave like BEFORE_HEX and AFTER_HEX, but calculate and write correct +serial and memory sizes in case of expanding/shrinking the object. + +Pseudo-code example: +[MODDED_CODE] +0F 00 <.arrRemove> 1B 00 <.arrRemove> 1E <%f0.75> 16 + +Adding default property example: +OBJECT=Default__XComMeldContainerSpawnPoint:AUTO +FIND_CODE= +[INSERT_CODE] + + +<%u0> +<%u0> +<%b1> + +FNR example (from Drakous79 "Default Helmet and Armor Tint" mod). This will correctly expand a function, +calculate and write script serial and memory sizes: +[BEFORE_CODE] +04 00 <.kSoldier> +[AFTER_CODE] +0F 35 00 00 +35 00 01 00 +<.kSoldier> 1D <%u305> +0F 35 00 00 +35 00 01 00 +<.kSoldier> 93 2C <%b10> 26 16 +04 00 <.kSoldier> + +-------- +Aliases: +-------- + +ALIAS (key) - used to define alias name for a portion of code. Syntax: + ALIAS=NAME:replacement code + Example: + ALIAS=GameDiff:19 1B 16 0A 00 00 1B 16 + Usage: + [MODDED_CODE] + // if(Game().GetDifficulty() >= 2) + 07 00 00 99 2C <%b2> 16 + +Aliases are searched scope-wise: if alias is defined inside object scope it is considered local to that +scope. While searching for alias replacement code program first looks in the local scope and then in +the global scope. This means you can have different aliases with the same names inside different scopes. + +Example of locally defined alias: +OBJECT=XGStrategyAI.GetNumOutsiders:AUTO +ALIAS=GameDiff:19 1B 16 0A 00 00 1B 16 +[MODDED_CODE] +// if(Game().GetDifficulty() >= 2) +07 00 00 99 2C <%b2> 16 + +Example of globally defined alias: +ALIAS=GameDiff:19 1B 16 0A 00 00 1B 16 +OBJECT=XGStrategyAI.GetNumOutsiders:AUTO +[MODDED_CODE] +// if(Game().GetDifficulty() >= 2) +07 00 00 99 2C <%b2> 16 + +----------------- +Replacement code: +----------------- + +REPLACEMENT_CODE (key and section) is used to completely replace export object script with the new one. Works +only with objects which have scripts (like functions and states). Will expand/shrink object if necessary and +if current behavior is set to "AUTO" or "MOVE". Will auto-calculate and re-write memory and serial script size. + +You can use CODE keys/sections with pure/partial hex data, but in this case the memory size will be wrong and +you will need to set it manually. If you choose to do this, you should do it after REPLACEMENT_CODE call. + +---------------------- +Labels and references: +---------------------- + +You can label a token inside a code with [#label] and reference it with [@label]. Label names must be unique +(see example below). Don't use labels if you mix pure hex with pseudo-code, as references will be wrong! + +Example: +OBJECT=XGStrategyAI.GetNumOutsiders:AUTO +[REPLACEMENT_CODE] +// if(Game().GetDifficulty() >= 2) +07 [@label1] 99 19 1B 16 0A 00 00 1B 16 2C <%b2> 16 + // return 2 + 04 2C 02 +// goto (else) +06 [@label2] + [#label1] + // return 1 + 04 26 +[#label2] +// return ReturnValue +04 3A <.ReturnValue> +// EOS +53 + +You can use empty reference [@] and a pair of parentheses to auto-calculate memory size/skip size. Example: +// if(Game().GetDifficulty() >= 2) +07 [@label1] 99 19 1B 16 [@] 00 ( 1B 16 ) 2C <%b2> 16 + +[@] marks the place of memory size to calculate and parentheses mark the beginning and the end of the code +which size needs to be auto-calculated. + +----------------------------------------------------------------------------------------------------------------- + Adding new names and objects +----------------------------------------------------------------------------------------------------------------- + +Warning! This feature is highly experimental and can completely mess up your packages! Use with caution! + +There are three main keys/sections for adding new objects: +1. ADD_NAME_ENTRY - adds a name entry into the name table. +2. ADD_IMPORT_ENTRY - adds an import entry into the import table. +3. ADD_EXPORT_ENTRY - adds an export entry into the export table and links the new object to specified owner. + Always writes at least 16 bytes long serialized data: + PrevObjRef + Empty DefaultProperties List + NextObjRef + +Example: adding a new WGhost81 local variable to XGStrategyAI.GetNumOutsiders function + +[ADD_NAME_ENTRY] +<%u9> // string length (including terminating null) +<%t"WGhost81"> // ASCII null-terminated string +<%u0x00000000> // flags L (always the same) +<%u0x00070010> // flags H (always the same) + +[ADD_EXPORT_ENTRY] + // Type + // ParentClassRef + // OwnerRef + // NameIdx + // ArchetypeRef +<%u0x00000000> // flags H +<%u0x00070004> // flags L +<%u40> // serial size +<%u0> // serial offset +<%u0> // export flags +<%u0> // net objects count +<%u0> // GUID1, zero if net objects count == 0 +<%u0> // GUID2, zero if net objects count == 0 +<%u0> // GUID3, zero if net objects count == 0 +<%u0> // GUID4, zero if net objects count == 0 +<%u0> // unknown, zero if net objects count == 0 + +OBJECT=XGStrategyAI.GetNumOutsiders.WGhost81 +REL_OFFSET=16 // skip PrevObjRef + Empty DefaultProperties List + NextObjRef +[MODDED_CODE] +<%s1> // ArrayDim +<%s0> // ElementSize +<%u0x00000000> // flags L +<%u0x00000000> // flags H + // CategoryIndex + // ArrayEnumRef + +Note that all entries are actually raw data written in PatchUPK pseudo-code. Patcher will get it as pure HEX +code, deserialize it and link to owner if necessary. Patcher will not construct proper serial data for you, +you should do it yourself after adding an object! + +----------------------------------------------------------------------------------------------------------------- + Legacy support: deprecated/renamed keys +----------------------------------------------------------------------------------------------------------------- + +You can still use those, but it is recommended to move on to new commands. + +FUNCTION key is an alias to OBJECT key. Renamed for obvious reasons: not all objects are functions. + +FUNCTION_FILE key is an alias to MODDED_FILE key. + +NAMELIST_NAME key is an alias to much shorter RENAME key. + +----------------------------------------------------------------------------------------------------------------- + Uninstall scripts +----------------------------------------------------------------------------------------------------------------- + +When installing mod PatchUPK automatically generates uninstall script and writes it to +your_mod_file_name.uninstall.txt. "Installing" .uninstall.txt "mod" will not generate another uninstall file. +In case your_mod_file_name.uninstall.txt already exists, program will generate your_mod_file_name.uninstall1.txt +and so on. + +Uninstall data are taken directly from existing package before rewriting them. + +----------------------------------------------------------------------------------------------------------------- + TO DO +----------------------------------------------------------------------------------------------------------------- + +Script compiler? :) diff --git a/doc/UPKUtils_Readme.txt b/doc/UPKUtils_Readme.txt index a416a7e..ed3d945 100644 --- a/doc/UPKUtils_Readme.txt +++ b/doc/UPKUtils_Readme.txt @@ -1,169 +1,169 @@ ------------------------------------------------------------------------------------------------------------------ - UPK Utils documentation file ------------------------------------------------------------------------------------------------------------------ - -UPK Utils are a set of console applications for analysing and patching XCOM packages (.upk). - ------------------------------------------------------------------------------------------------------------------ - DecompressLZO ------------------------------------------------------------------------------------------------------------------ - -An utility to decompress upk files using LZO compression algorithm. - -Usage: DecompressLZO CompressedResourceFile.upk [DecompressedCompressedResourceFile.upk] - -If DecompressedCompressedResourceFile.upk is not specified, decompressed package is saved to -CompressedResourceFile.upk.uncompr file. - -Works with compressed and fully compressed packages. - ------------------------------------------------------------------------------------------------------------------ - ExtractNameLists ------------------------------------------------------------------------------------------------------------------ - -The program reads package header (package information, name, import and export tables) and outputs the data -to the console. - -Usage: -ExtractNameLists UnpackedResourceFile.upk [/v] - /v — verbose mode (optional parameter) -Examples: -ExtractNameLists XComStrategyGame.upk -ExtractNameLists XComStrategyGame.upk /v - -To redirect output from the console to a text file type: -ExtractNameLists UnpackedResourceFile.upk > file_name.txt -Examples: -ExtractNameLists XComStrategyGame.upk > XComStrategyGame.txt -ExtractNameLists XComStrategyGame.upk /v > XComStrategyGame.txt - -This will redirect output to file_name.txt. Error messages will still be printed into the console. - -If package is compressed, program will try to read and print compression info. - ------------------------------------------------------------------------------------------------------------------ - FindObjectEntry ------------------------------------------------------------------------------------------------------------------ - -The program finds a specified object in a specified package and outputs the information to the console. -Program will also try to deserialize object's data and print all the data available. - -Usage: -FindObjectEntry UnpackedResourceFile.upk ObjectName [/d] - ObjectName is a full object name: Owner.Owner...Name - /d — dump object serialized data into binary file. File name will have Owner.Owner...Name.Type format. - (optional parameter) -Example: -FindObjectEntry XComStrategyGame.upk XGFacility_PsiLabs.TPsiTrainee - -You can redirect output to a file: -FindObjectEntry XComStrategyGame.upk XGFacility_PsiLabs.TPsiTrainee > XGFacility_PsiLabs.TPsiTrainee.txt - -FindObjectEntry will try to deserialize unknown objects. It shouldn't crash in case of wrong guess about -object's data, but may give wrong or incomplete data sometimes. It will give a warning about object's -type being unknown. - ------------------------------------------------------------------------------------------------------------------ - HexToPseudoCode ------------------------------------------------------------------------------------------------------------------ - -Converts hex bytecode to PatchUPK/PatcherGUI pseudo-code. Works with Functions and States. Decompiled script is -directly usable by PatchUPK/PatcherGUI. - -Usage: -HexToPseudoCode UnpackedResourceFile.upk ObjectName [/d] - ObjectName is a full object name: Owner.Owner...Name - /d — dump object serialized data into binary file. File name will have Owner.Owner...Name.Type format. - (optional parameter) -Example: -HexToPseudoCode XComStrategyGame.upk XGFundingCouncil.UpdateSlingshotMission > XGFundingCouncil.UpdateSlingshotMission.txt - -This will create a file XGFundingCouncil.UpdateSlingshotMission.txt with decompiled code inside. - ------------------------------------------------------------------------------------------------------------------ - FindObjectByOffset ------------------------------------------------------------------------------------------------------------------ - -The program finds an export object full name by specified offset. - -Usage: -FindObjectByOffset UnpackedResourceFile.upk offset - offset — file offset in bytes (dec or hex) - ------------------------------------------------------------------------------------------------------------------ - DeserializeAll ------------------------------------------------------------------------------------------------------------------ - -The program performs batch-deserialization of all Export Objects inside a specified package. - -Usage: -DeserializeAll UnpackedResourceFile.upk [NameMask] - NameMask - not yet real mask, but a substring in full name string (optional parameter) - -Example: -DeserializeAll URB_PierA.upk -Will create a file URB_PierA.txt with package info and folder URB_PierA with subfolders for each owner and it's -objects. - -DeserializeAll URB_PierA.upk TheWorld.PersistentLevel -Will deserialize TheWorld.PersistentLevel and it's objects. - -The program is unstable and can sometimes crash, especially with map packages, as there is no info on most of the -map objects. But it is helpful in analysing TheWorld.PersistentLevel objects, as they are mostly archetypes and -contain Default Properties only. - ------------------------------------------------------------------------------------------------------------------ - PatchUPK ------------------------------------------------------------------------------------------------------------------ - -An utility to apply UPK patches. For more information see PatchUPK_Readme.txt. - -Usage: -PatchUPK modfile.txt [PATH_TO_UPK] - modfile.txt — mod script (see PatchUPK_Readme.txt and PatchUPK_Mod_Example.txt) - PATH_TO_UPK — path to folder where packages are located (optional parameter) - - ------------------------------------------------------------------------------------------------------------------ - MoveExpandFunction (Deprecated) ------------------------------------------------------------------------------------------------------------------ - -The program performs move/expand operations for a specified function. Can undo previous move/expand operation. - -Usage: -MoveExpandFunction UnpackedResourceFile.upk FunctionName [NewFunctionSize or /u] - FunctionName is a full object name: Owner.Owner...Name - NewFunctionSize is a new function size in bytes (optional parameter) - /u switches the program into undo mode (optional parameter) - -Examples: -MoveExpandFunction XComStrategyGame.upk XGStrategyAI.GetAltWeapon 300 -MoveExpandFunction XComStrategyGame.upk XGStrategyAI.GetAltWeapon /u - ------------------------------------------------------------------------------------------------------------------ - CompareUPK ------------------------------------------------------------------------------------------------------------------ - -An utility to compare packages. Useful for analysing patches. Slow. Really slow. :) - -Usage: -CompareUPK OldPackage.upk NewPackage.upk - ------------------------------------------------------------------------------------------------------------------ - XComLZO ------------------------------------------------------------------------------------------------------------------ - -An utility to pack/unpack raw data using LZO compression algorithm. - -Usage: XComLZO p inputFileName - or XComLZO u inputFileName -where p = pack and u = unpack - -Useful to re-packing graphics and creating your very own tfc packages. - ------------------------------------------------------------------------------------------------------------------ - Acknowledgments ------------------------------------------------------------------------------------------------------------------ - -Gildor, Antonio Cordero Balcazar, Eliot van Uytfanghe and the other Unreal Engine researchers for helpful -utilities and format info. +----------------------------------------------------------------------------------------------------------------- + UPK Utils documentation file +----------------------------------------------------------------------------------------------------------------- + +UPK Utils are a set of console applications for analysing and patching XCOM packages (.upk). + +----------------------------------------------------------------------------------------------------------------- + DecompressLZO +----------------------------------------------------------------------------------------------------------------- + +An utility to decompress upk files using LZO compression algorithm. + +Usage: DecompressLZO CompressedResourceFile.upk [DecompressedCompressedResourceFile.upk] + +If DecompressedCompressedResourceFile.upk is not specified, decompressed package is saved to +CompressedResourceFile.upk.uncompr file. + +Works with compressed and fully compressed packages. + +----------------------------------------------------------------------------------------------------------------- + ExtractNameLists +----------------------------------------------------------------------------------------------------------------- + +The program reads package header (package information, name, import and export tables) and outputs the data +to the console. + +Usage: +ExtractNameLists UnpackedResourceFile.upk [/v] + /v — verbose mode (optional parameter) +Examples: +ExtractNameLists XComStrategyGame.upk +ExtractNameLists XComStrategyGame.upk /v + +To redirect output from the console to a text file type: +ExtractNameLists UnpackedResourceFile.upk > file_name.txt +Examples: +ExtractNameLists XComStrategyGame.upk > XComStrategyGame.txt +ExtractNameLists XComStrategyGame.upk /v > XComStrategyGame.txt + +This will redirect output to file_name.txt. Error messages will still be printed into the console. + +If package is compressed, program will try to read and print compression info. + +----------------------------------------------------------------------------------------------------------------- + FindObjectEntry +----------------------------------------------------------------------------------------------------------------- + +The program finds a specified object in a specified package and outputs the information to the console. +Program will also try to deserialize object's data and print all the data available. + +Usage: +FindObjectEntry UnpackedResourceFile.upk ObjectName [/d] + ObjectName is a full object name: Owner.Owner...Name + /d — dump object serialized data into binary file. File name will have Owner.Owner...Name.Type format. + (optional parameter) +Example: +FindObjectEntry XComStrategyGame.upk XGFacility_PsiLabs.TPsiTrainee + +You can redirect output to a file: +FindObjectEntry XComStrategyGame.upk XGFacility_PsiLabs.TPsiTrainee > XGFacility_PsiLabs.TPsiTrainee.txt + +FindObjectEntry will try to deserialize unknown objects. It shouldn't crash in case of wrong guess about +object's data, but may give wrong or incomplete data sometimes. It will give a warning about object's +type being unknown. + +----------------------------------------------------------------------------------------------------------------- + HexToPseudoCode +----------------------------------------------------------------------------------------------------------------- + +Converts hex bytecode to PatchUPK/PatcherGUI pseudo-code. Works with Functions and States. Decompiled script is +directly usable by PatchUPK/PatcherGUI. + +Usage: +HexToPseudoCode UnpackedResourceFile.upk ObjectName [/d] + ObjectName is a full object name: Owner.Owner...Name + /d — dump object serialized data into binary file. File name will have Owner.Owner...Name.Type format. + (optional parameter) +Example: +HexToPseudoCode XComStrategyGame.upk XGFundingCouncil.UpdateSlingshotMission > XGFundingCouncil.UpdateSlingshotMission.txt + +This will create a file XGFundingCouncil.UpdateSlingshotMission.txt with decompiled code inside. + +----------------------------------------------------------------------------------------------------------------- + FindObjectByOffset +----------------------------------------------------------------------------------------------------------------- + +The program finds an export object full name by specified offset. + +Usage: +FindObjectByOffset UnpackedResourceFile.upk offset + offset — file offset in bytes (dec or hex) + +----------------------------------------------------------------------------------------------------------------- + DeserializeAll +----------------------------------------------------------------------------------------------------------------- + +The program performs batch-deserialization of all Export Objects inside a specified package. + +Usage: +DeserializeAll UnpackedResourceFile.upk [NameMask] + NameMask - not yet real mask, but a substring in full name string (optional parameter) + +Example: +DeserializeAll URB_PierA.upk +Will create a file URB_PierA.txt with package info and folder URB_PierA with subfolders for each owner and it's +objects. + +DeserializeAll URB_PierA.upk TheWorld.PersistentLevel +Will deserialize TheWorld.PersistentLevel and it's objects. + +The program is unstable and can sometimes crash, especially with map packages, as there is no info on most of the +map objects. But it is helpful in analysing TheWorld.PersistentLevel objects, as they are mostly archetypes and +contain Default Properties only. + +----------------------------------------------------------------------------------------------------------------- + PatchUPK +----------------------------------------------------------------------------------------------------------------- + +An utility to apply UPK patches. For more information see PatchUPK_Readme.txt. + +Usage: +PatchUPK modfile.txt [PATH_TO_UPK] + modfile.txt — mod script (see PatchUPK_Readme.txt and PatchUPK_Mod_Example.txt) + PATH_TO_UPK — path to folder where packages are located (optional parameter) + + +----------------------------------------------------------------------------------------------------------------- + MoveExpandFunction (Deprecated) +----------------------------------------------------------------------------------------------------------------- + +The program performs move/expand operations for a specified function. Can undo previous move/expand operation. + +Usage: +MoveExpandFunction UnpackedResourceFile.upk FunctionName [NewFunctionSize or /u] + FunctionName is a full object name: Owner.Owner...Name + NewFunctionSize is a new function size in bytes (optional parameter) + /u switches the program into undo mode (optional parameter) + +Examples: +MoveExpandFunction XComStrategyGame.upk XGStrategyAI.GetAltWeapon 300 +MoveExpandFunction XComStrategyGame.upk XGStrategyAI.GetAltWeapon /u + +----------------------------------------------------------------------------------------------------------------- + CompareUPK +----------------------------------------------------------------------------------------------------------------- + +An utility to compare packages. Useful for analysing patches. Slow. Really slow. :) + +Usage: +CompareUPK OldPackage.upk NewPackage.upk + +----------------------------------------------------------------------------------------------------------------- + XComLZO +----------------------------------------------------------------------------------------------------------------- + +An utility to pack/unpack raw data using LZO compression algorithm. + +Usage: XComLZO p inputFileName + or XComLZO u inputFileName +where p = pack and u = unpack + +Useful to re-packing graphics and creating your very own tfc packages. + +----------------------------------------------------------------------------------------------------------------- + Acknowledgments +----------------------------------------------------------------------------------------------------------------- + +Gildor, Antonio Cordero Balcazar, Eliot van Uytfanghe and the other Unreal Engine researchers for helpful +utilities and format info.