diff --git a/CustomTFC.cpp b/CustomTFC.cpp index 187f86d..644c79b 100644 --- a/CustomTFC.cpp +++ b/CustomTFC.cpp @@ -79,7 +79,7 @@ bool CustomTFC::Reload() return false; } BlockHeaders.push_back(header); - size_t maxOffset = header.BlockOffset + header.BlockSize; + uint32_t maxOffset = header.BlockOffset + header.BlockSize; while (TFCFile.tellg() < maxOffset) { TFCInventoryEntry entry; @@ -152,7 +152,7 @@ bool CustomTFC::InternalWriteData(TFCInventoryEntry& DataDescr, std::vector add a new one if (newEntryOffset + newEntrySize >= LastHeader.BlockOffset + ALIGN_TO_BLOCK_SIZE) @@ -214,7 +214,7 @@ bool CustomTFC::InternalWriteData(TFCInventoryEntry& DataDescr, std::vector not a custom tfc, won't do anything! if (BlockHeaders.size() == 0 && newBlockOffset != 0) { diff --git a/CustomTFC.h b/CustomTFC.h index 5569697..696d149 100644 --- a/CustomTFC.h +++ b/CustomTFC.h @@ -47,7 +47,7 @@ class CustomTFC std::string TFCFileName; //std::iostream TFCFile; std::fstream TFCFile; - size_t LastEntryEndOffset = 0; + uint32_t LastEntryEndOffset = 0; ///internal operations bool InternalWriteData(TFCInventoryEntry& DataDescr, std::vector DataToWrite); bool InternalWriteNewBlock(); diff --git a/ExportTexturesToDDS.cpp b/ExportTexturesToDDS.cpp index 5148c3c..d2e4dd4 100644 --- a/ExportTexturesToDDS.cpp +++ b/ExportTexturesToDDS.cpp @@ -124,7 +124,7 @@ int main(int argN, char* argV[]) UPKReadErrors err = Package.GetError(); if (err != UPKReadErrors::NoErrors && !Package.IsCompressed()) { - std::cerr << "Error reading package:\n" << FormatReadErrors(err); + std::cerr << "Error reading package: " << nextFileName << "\n" << FormatReadErrors(err); failedToProcess.Add(nextFileName); continue; } @@ -133,7 +133,7 @@ int main(int argN, char* argV[]) std::cout << "Package is compressed, decompressing...\n"; if (!Package.DecompressPackage()) { - std::cerr << "Error decompressing package!\n"; + std::cerr << "Error decompressing package: " << nextFileName << "!\n"; failedToProcess.Add(nextFileName); continue; } @@ -144,6 +144,8 @@ int main(int argN, char* argV[]) { if (ExportTable[j].Type != "Texture2D" || Package.IsPropertiesObject(j)) continue; + if (ExportTable[j].FullName == "Default__Texture2D" && ExportTable[j].SerialSize == 14) /// BatmanAC def prop object with no def prop flag (???) + continue; if (wxIsWild(objectName) && !wxMatchWild(objectName, ExportTable[j].FullName, false)) continue; if (!wxIsWild(objectName) && ExportTable[j].FullName != objectName) @@ -198,13 +200,15 @@ bool SaveDDS(UObjectReference ObjRef, UPKUtils& package, std::string filename) UTexture2D* texture = dynamic_cast(package.DeserializeObjectByRef(ObjRef)); if (texture == nullptr) { - std::cerr << "Error deserializing Texture2D object!\n"; + std::cerr << "Error deserializing Texture2D object: " << package.ResolveFullName(ObjRef) << "!\n"; return false; } ///make dds header DDSHeader header = MakeDDSHeader(texture->GetPixelFormat()); + std::cout << "Pixel format for " << package.ResolveFullName(ObjRef) << ": " << texture->GetPixelFormat() << std::endl; + /// adjust header header.Height = texture->GetHeight(); header.Width = texture->GetWidth(); @@ -225,7 +229,7 @@ bool SaveDDS(UObjectReference ObjRef, UPKUtils& package, std::string filename) if (mipMapsToSave.size() == 0) { - std::cerr << "No mipmas to save to dds!\n"; + std::cerr << "No mipmas to save to dds: " << filename << "!\n"; delete texture; return false; } diff --git a/ImportTexturesFromDDS.cpp b/ImportTexturesFromDDS.cpp index 7843e7e..634b5c2 100644 --- a/ImportTexturesFromDDS.cpp +++ b/ImportTexturesFromDDS.cpp @@ -9,7 +9,6 @@ #include #define CUSTOM_TFC_NAME "Texture2D" -#define LOD_BIAS_BAD 100500 //using namespace std; @@ -36,8 +35,8 @@ int main(int argN, char* argV[]) { wxCMD_LINE_OPTION, "o", "output", "set output dir (output packages)" }, { wxCMD_LINE_OPTION, "d", "dds", "set dds dir (input dds, inventory.csv)" }, { wxCMD_LINE_OPTION, "t", "tfc", "set tfc name and dir (by default [output dir]/Texture2D.tfc is used)" }, - { wxCMD_LINE_OPTION, NULL, "LODBias", "new LODBias value", wxCMD_LINE_VAL_NUMBER }, - { wxCMD_LINE_SWITCH, NULL, "resetLOD", "reset LODBias" }, + { wxCMD_LINE_OPTION, NULL, "LODBias", "new LODBias value (ignored for Batman AC)", wxCMD_LINE_VAL_NUMBER }, + { wxCMD_LINE_SWITCH, NULL, "resetLOD", "reset LODBias (ignored for Batman AC)" }, { wxCMD_LINE_SWITCH, "x", "exit", "exit on error" }, { wxCMD_LINE_SWITCH, "w", "overwrite", "overwrite existing files without backup" }, { wxCMD_LINE_NONE } @@ -71,7 +70,7 @@ int main(int argN, char* argV[]) outputDirName = wxFileName(outputDirName).GetFullPath(); if (!wxDirExists(outputDirName) && !wxMkdir(outputDirName)) { - std::cerr << "Output directory does not exist: " + outputDirName << std::endl; + std::cerr << "Error: output directory does not exist: " + outputDirName << std::endl; return 1; } std::cout << "Output dir name (output packages): " << outputDirName << std::endl; @@ -185,7 +184,7 @@ int main(int argN, char* argV[]) nextFileName = found; else { - std::cerr << "Cannot find package file: " << nextFileName << std::endl; + std::cerr << "Error: cannot find package file: " << nextFileName << std::endl; failedToProcess.Add(nextFileName); if (exitOnError) return 1; @@ -198,7 +197,7 @@ int main(int argN, char* argV[]) UPKReadErrors err = Package.GetError(); if (err != UPKReadErrors::NoErrors && !Package.IsCompressed()) { - std::cerr << "Error reading package:\n" << FormatReadErrors(err); + std::cerr << "Error reading package: " << nextFileName << "\n" << FormatReadErrors(err); failedToProcess.Add(nextFileName); if (exitOnError) return 1; @@ -209,7 +208,7 @@ int main(int argN, char* argV[]) std::cout << "Package is compressed, decompressing...\n"; if (!Package.DecompressPackage()) { - std::cerr << "Error decompressing package!\n"; + std::cerr << "Error decompressing package: " << nextFileName << "\n"; failedToProcess.Add(nextFileName); if (exitOnError) return 1; @@ -220,7 +219,7 @@ int main(int argN, char* argV[]) /// make sure the specified tfc name exists in nametable if (Package.FindName(wxFileName(T2DFile.GetFilename()).GetName().ToStdString()) == -1) { - std::cerr << "The tfc name specified does not exist for this package!\n"; + std::cerr << "Error: the tfc name specified does not exist for this package!\n"; failedToProcess.Add(nextFileName); if (exitOnError) return 1; @@ -238,7 +237,7 @@ int main(int argN, char* argV[]) nextDDSFileName = found; else { - std::cerr << "Cannot find dds file: " << nextDDSFileName << std::endl; + std::cerr << "Error: cannot find dds file: " << nextDDSFileName << std::endl; failedToExportObjs.Add(nextDDSFileName); if (exitOnError) return 1; @@ -259,7 +258,7 @@ int main(int argN, char* argV[]) UObjectReference objRef = Package.FindObjectOfType(fullObjName, "Texture2D", true); if (objRef == 0) { - std::cerr << "Export object not found: " << fullObjName << " (in " << nextFileName << ")" << std::endl; + std::cerr << "Error: export object not found: " << fullObjName << " (in " << nextFileName << ")" << std::endl; failedToExportObjs.Add(fullObjName + "(in " + nextFileName + ")"); if (exitOnError) return 1; @@ -332,7 +331,7 @@ int main(int argN, char* argV[]) } if (failedToExportObjs.size() > 0) { - std::cout << "Failed to export textures (" << failedToExportObjs.size() << "):\n"; + std::cout << "Failed to import textures (" << failedToExportObjs.size() << "):\n"; for (unsigned i = 0; i < failedToExportObjs.size(); ++i) std::cout << failedToExportObjs[i] << std::endl; } @@ -389,7 +388,7 @@ bool ReadInventory(wxString filename, std::map& inv) std::ifstream invFile(filename.ToStdString()); if (!invFile.is_open()) { - std::cerr << "Cannot open " << filename << std::endl; + std::cerr << "Error: cannot open " << filename << std::endl; return false; } inv.clear(); @@ -431,13 +430,13 @@ bool CatalogueFilesToProcess(wxString ddsDir, wxString ddsMask, wxString upkDir, wxArrayString ddsToProcess = TraverseDir(ddsDir, ddsMask); if (ddsToProcess.size() == 0) { - std::cerr << "Missing dds file(s) to process!" << std::endl; + std::cerr << "Error: missing dds file(s) to process!" << std::endl; return false; } wxArrayString upkToProcess = TraverseDir(upkDir, upkMask); if (upkToProcess.size() == 0) { - std::cerr << "Missing package(s) to process!" << std::endl; + std::cerr << "Error: missing package(s) to process!" << std::endl; return false; } upkInv.clear(); @@ -453,7 +452,7 @@ bool CatalogueFilesToProcess(wxString ddsDir, wxString ddsMask, wxString upkDir, } if (ddsInv.size() == 0) { - std::cerr << "Bad inventory!" << std::endl; + std::cerr << "Error: bad inventory!" << std::endl; return false; } std::map upkPathInv; @@ -469,7 +468,7 @@ bool CatalogueFilesToProcess(wxString ddsDir, wxString ddsMask, wxString upkDir, wxString objName = wxFileName(ddsToProcess[i]).GetName(); if (ddsInv.count(objName) == 0) { - std::cerr << "Bad texture object name or bad inventory: " << objName << std::endl; + std::cerr << "Error: bad texture object name or bad inventory: " << objName << std::endl; return false; } /// extract package names from the inventory @@ -489,7 +488,7 @@ bool CatalogueFilesToProcess(wxString ddsDir, wxString ddsMask, wxString upkDir, } if (upkInv.size() == 0) { - std::cerr << "Cannot match textures to packages!" << std::endl; + std::cerr << "Error: cannot match textures to packages!" << std::endl; return false; } return true; @@ -509,7 +508,7 @@ bool ReadDDSFile(wxString nextDDSFileName, DDSHeader& header, std::vector(&ddsMagic), 4); if (ddsMagic != 0x20534444) { - std::cerr << "Missing DDS magic!" << std::endl; + std::cerr << "Error: missing DDS magic!" << std::endl; return false; } @@ -581,7 +580,7 @@ bool WriteTextureObject(UObjectReference ObjRef, UPKUtils& package, CustomTFC& t UTexture2D* texture = dynamic_cast(package.DeserializeObjectByRef(ObjRef, true)); if (texture == nullptr) { - std::cerr << "Error deserializing Texture2D object!\n"; + std::cerr << "Error deserializing Texture2D object: " << package.ResolveFullName(ObjRef) << "!\n"; return false; } /// make a new texture object @@ -597,9 +596,10 @@ bool WriteTextureObject(UObjectReference ObjRef, UPKUtils& package, CustomTFC& t ///adjust texture params bool texturesAreIdentical = true; newTexture->SetPixelFormat(GetPixelFormatStringFromDDSHeader(header)); + std::cout << "Pixel format for " << package.ResolveFullName(ObjRef) << ": " << texture->GetPixelFormat() << std::endl; if (texture->GetPixelFormat() != newTexture->GetPixelFormat()) { - std::cerr << "PixelFormat mismatch! Old PixelFormat = " << texture->GetPixelFormat() << std::endl; + std::cerr << "Error: PixelFormat mismatch! Old PixelFormat = " << texture->GetPixelFormat() << ". New PixelFormat = " << newTexture->GetPixelFormat() << std::endl; delete texture; delete newTexture; return false; @@ -624,38 +624,68 @@ bool WriteTextureObject(UObjectReference ObjRef, UPKUtils& package, CustomTFC& t ///adjust defaultproperties if (!texturesAreIdentical) { + ///for BatmanAC def props are serialized by offset, so searching for a def prop entry by type + offset (4 bytes) is unsafe (not unique enough combination) + ///this is why for that one full prop is reconstructed and replaced old -> new if (texture->GetWidth() != newTexture->GetWidth()) { - UDefaultProperty prop; - prop.MakeIntProperty("SizeX", newTexture->GetWidth(), package); - package.ReplacePropertyValue(prop, ObjRef, newExportDataStr); + UDefaultProperty newProp; + newProp.MakeIntProperty("SizeX", newTexture->GetWidth(), package); + if (package.GetVersion() == VER_BATMAN_CITY) + { + UDefaultProperty oldProp; + oldProp.MakeIntProperty("SizeX", texture->GetWidth(), package); + package.ReplaceProperty(oldProp, newProp, ObjRef, newExportDataStr); + } + else + package.ReplacePropertyValue(newProp, ObjRef, newExportDataStr); } if (texture->GetHeight() != newTexture->GetHeight()) { - UDefaultProperty prop; - prop.MakeIntProperty("SizeY", newTexture->GetHeight(), package); - package.ReplacePropertyValue(prop, ObjRef, newExportDataStr); + UDefaultProperty newProp; + newProp.MakeIntProperty("SizeY", newTexture->GetHeight(), package); + if (package.GetVersion() == VER_BATMAN_CITY) + { + UDefaultProperty oldProp; + oldProp.MakeIntProperty("SizeY", texture->GetHeight(), package); + package.ReplaceProperty(oldProp, newProp, ObjRef, newExportDataStr); + } + else + package.ReplacePropertyValue(newProp, ObjRef, newExportDataStr); } if (texture->GetMipMapCount() != newTexture->GetMipMapCount()) { - UDefaultProperty prop; - prop.MakeIntProperty("MipTailBaseIdx", newTexture->GetMipMapCount() - 1, package); - package.ReplacePropertyValue(prop, ObjRef, newExportDataStr); + UDefaultProperty newProp; + newProp.MakeIntProperty("MipTailBaseIdx", newTexture->GetMipMapCount() - 1, package); + if (package.GetVersion() == VER_BATMAN_CITY) + { + UDefaultProperty oldProp; + oldProp.MakeIntProperty("MipTailBaseIdx", texture->GetMipMapCount() - 1, package); + package.ReplaceProperty(oldProp, newProp, ObjRef, newExportDataStr); + } + else + package.ReplacePropertyValue(newProp, ObjRef, newExportDataStr); } } ///adjust LODBias - if (resetLOD) + if (package.GetVersion() == VER_BATMAN_CITY) { - UDefaultProperty prop; - prop.MakeIntProperty("LODBias", LODBias, package); - package.RemoveProperty(prop, ObjRef, newExportDataStr); + ///Doesn't seem to have LODBias var } - else if (LODBias != LOD_BIAS_BAD) + else { - UDefaultProperty prop; - prop.MakeIntProperty("LODBias", LODBias, package); - if (!package.ReplacePropertyValue(prop, ObjRef, newExportDataStr)) - package.InsertProperty(prop, ObjRef, newExportDataStr); + if (resetLOD) + { + UDefaultProperty prop; + prop.MakeIntProperty("LODBias", LODBias, package); + package.RemoveProperty(prop, ObjRef, newExportDataStr); + } + else if (LODBias != LOD_BIAS_BAD) + { + UDefaultProperty prop; + prop.MakeIntProperty("LODBias", LODBias, package); + if (!package.ReplacePropertyValue(prop, ObjRef, newExportDataStr)) + package.InsertProperty(prop, ObjRef, newExportDataStr); + } } ///mipmaps were in an external tfc originally if (texture->GetTextureFileCacheName() != "") @@ -663,20 +693,48 @@ bool WriteTextureObject(UObjectReference ObjRef, UPKUtils& package, CustomTFC& t ///set new tfc name newTexture->SetTextureFileCacheName(wxFileName(tfc.GetFilename()).GetName().ToStdString()); ///adjust default properties - UDefaultProperty prop1; - prop1.MakeNameProperty("TextureFileCacheName", newTexture->GetTextureFileCacheName(), package); - package.ReplacePropertyValue(prop1, ObjRef, newExportDataStr); + UDefaultProperty newProp; + newProp.MakeNameProperty("TextureFileCacheName", newTexture->GetTextureFileCacheName(), package); + if (package.GetVersion() == VER_BATMAN_CITY) + { + UDefaultProperty oldProp; + oldProp.MakeNameProperty("TextureFileCacheName", texture->GetTextureFileCacheName(), package); + package.ReplaceProperty(oldProp, newProp, ObjRef, newExportDataStr); + } + else + package.ReplacePropertyValue(newProp, ObjRef, newExportDataStr); ///try compression - if (newTexture->TryLzoCompression(256)) + if (texture->GetHasCompressedMipMaps()) { - ///try exporting compressed textures to the new tfc - if (!newTexture->ExportToExternalFile(tfc, package, true)) + int minResForCompression = newTexture->GetMinObservedResForCompression(); + std::cout << "Min observed res for mipmap compression for " << package.ResolveFullName(ObjRef) << ": " << minResForCompression << std::endl; + if (minResForCompression > -1) + { + if (newTexture->TryLzoCompression(minResForCompression)) + { + ///try exporting compressed textures to the new tfc + if (!newTexture->ExportToExternalFile(tfc, package, true)) + { + std::cerr << "Error writing external texture file " << tfc.GetFilename() << std::endl; + delete texture; + delete newTexture; + return false; + } + } + } + } + if (package.GetVersion() == VER_BATMAN_CITY && texture->GetNumTFCMipMaps() > -1) + { + UDefaultProperty newProp, oldProp; + if (newTexture->GetNumTFCMipMaps() > -1) + newProp.MakeIntProperty("NumTFCMipMaps", newTexture->GetNumTFCMipMaps(), package); + else { - std::cerr << "Error writing external texture file " << tfc.GetFilename() << std::endl; - delete texture; - delete newTexture; - return false; + newProp.MakeIntProperty("NumTFCMipMaps", 0, package); + std::cerr << "Warning: potential issues with texture compression and/or tfc! Texture2D object: " << package.ResolveFullName(ObjRef) << std::endl; } + oldProp.MakeIntProperty("NumTFCMipMaps", texture->GetNumTFCMipMaps(), package); + package.ReplaceProperty(oldProp, newProp, ObjRef, newExportDataStr); } } ///check if export object resize is needed @@ -693,7 +751,7 @@ bool WriteTextureObject(UObjectReference ObjRef, UPKUtils& package, CustomTFC& t std::vector dataToWrite(t2dData.begin(), t2dData.end()); if (!package.WriteExportData(ObjRef, dataToWrite)) { - std::cerr << "Error writing texture data!" << std::endl; + std::cerr << "Error writing texture data for " << package.ResolveFullName(ObjRef) << "!" << std::endl; delete texture; delete newTexture; return false; diff --git a/README.md b/README.md index f8dec9d..0c801c1 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,18 @@ UPKUtils A set of utilities to work with UE3 cooked packages (u, upk, umap). -The utilities were originally made to work with XCOM:EU/EW games. Now they also support Batman: Arkham Asylum and partially support Batman: Arkham City (WIP). +The utilities were originally made to work with XCOM:EU/EW games. Now they also support Batman: Arkham Asylum and Batman: Arkham City. XComLZO is an old helper tool and can be considered deprecated now. ExportTexturesToDDS and ImportTexturesFromDDS are the new tools added to find and modify textures inside packages and the Textures.tfc file. They allow to permanently inject modified textures into an UE3 game, thus eliminating the need for TexMod. +For more information on how to export/import textures see this doc: +https://docs.google.com/document/d/1aANCeqO_n3c4jFaXmDadvZTxB3xj_JAbtjjF9kXKjCs + The tools are locked to work with certain versions of the engine only. Simply unlocking the version is useless in most cases: due to the differences in how data are organized inside the packages using tools on an unsupported game will most probably lead to a crash. -The updated versions of the tools might not be working properly with PatcherGUI. Use the v7.3 sources released on Apr 10, 2015 for that purpose. +The updated versions of the tools won't work properly with PatcherGUI. Use the v7.3 sources released on Apr 10, 2015 for that purpose. How to compile under Linux: diff --git a/UObject.cpp b/UObject.cpp index 5d4a4b2..27e58a6 100644 --- a/UObject.cpp +++ b/UObject.cpp @@ -87,7 +87,7 @@ std::string UBulkDataMirror::Deserialize(std::istream& stream, UPKInfo& info, UO else if (GetDataFlag(UBulkDataFlags::StoredAsSeparateData)) { ToggleDataFlag(UBulkDataFlags::StoredAsSeparateData); - size_t currOffset = stream.tellg(); ///save curr offset + uint32_t currOffset = stream.tellg(); ///save curr offset stream.seekg(SavedBulkDataOffsetInFile); BulkData.resize(SavedBulkDataSizeOnDisk); stream.read(BulkData.data(), BulkData.size()); @@ -102,7 +102,7 @@ std::string UBulkDataMirror::Deserialize(std::istream& stream, UPKInfo& info, UO } else { - size_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; + uint32_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; if (SavedBulkDataOffsetInFile + SavedBulkDataSizeOnDisk > maxOffset) { ss << "Error deserializing bulk data: bad offset or data size!\n"; @@ -213,7 +213,7 @@ std::string UBulkDataMirror::Serialize() return ss.str(); } -std::string UBulkDataMirror::Serialize(size_t offset) +std::string UBulkDataMirror::Serialize(uint32_t offset) { if (!LockFileOffset) SavedBulkDataOffsetInFile = offset + GetBulkDataRelOffset(); @@ -309,7 +309,7 @@ std::string UTexture2DMipMap::Deserialize(std::istream& stream, UPKInfo& info, U return ss.str(); } -std::string UTexture2DMipMap::Serialize(size_t offset) +std::string UTexture2DMipMap::Serialize(uint32_t offset) { std::ostringstream ss; ss << UBulkDataMirror::Serialize(offset); @@ -329,31 +329,164 @@ std::string UDefaultPropertiesList::Deserialize(std::istream& stream, UPKInfo& i ss << "UDefaultPropertiesList:\n"; PropertyOffset = stream.tellg(); DefaultProperties.clear(); - size_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; + uint32_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; UDefaultProperty Property; do { Property = UDefaultProperty{}; - ss << Property.Deserialize(stream, info, owner, unsafe, quick); + if (info.GetVersion() == VER_BATMAN_CITY) + ss << Property.DeserializeBatmanAC(stream, info, owner, unsafe, quick); + else + ss << Property.Deserialize(stream, info, owner, unsafe, quick); DefaultProperties.push_back(Property); - } while (Property.GetName() != "None" && stream.good() && (size_t)stream.tellg() < maxOffset); + } while (Property.GetName() != "None" && stream.good() && stream.tellg() < maxOffset); PropertySize = (unsigned)stream.tellg() - (unsigned)PropertyOffset; return ss.str(); } -std::string BCArrayOfNames::Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner) +std::string UDefaultProperty::DeserializeBatmanAC(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe, bool quick) { + Init(owner, unsafe, quick); + //uint32_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; std::ostringstream ss; - ss << "BCArrayOfNames:\n"; - stream.read(reinterpret_cast(&NamesNum), sizeof(NamesNum)); - ss << "\tNamesNum = " << NamesNum << std::endl; - Names.resize(NamesNum); - for (unsigned i = 0; i < NamesNum; ++i) - { - UNameIndex nameIdx; - stream.read(reinterpret_cast(&nameIdx), sizeof(nameIdx)); - Names[i] = nameIdx; - ss << "\tNameIdx: " << FormatHEX(nameIdx) << " -> " << info.IndexToName(nameIdx) << std::endl; + ss << "UDefaultProperty (Batman AC):\n"; + uint16_t type, offset; + stream.read(reinterpret_cast(&type), sizeof(type)); + switch (type) + { + case 0: ///end of properties list + Name = "None"; + Type = "None"; + ss << "\tType: 0 -> Empty property, end of list." << std::endl; + break; + case 1: ///ByteProperty + Type = "ByteProperty"; + ss << "\tType: " << type << " -> " << Type << std::endl; + stream.read(reinterpret_cast(&offset), sizeof(offset)); + ss << "\tLocal offset: " << FormatHEX(offset) << std::endl; + stream.read(reinterpret_cast(&NameIdx), sizeof(NameIdx)); + Name = info.IndexToName(NameIdx); + ss << "\tNameIdx: " << FormatHEX(NameIdx) << " -> " << info.IndexToName(NameIdx) << std::endl; + stream.read(reinterpret_cast(&PropertySize), sizeof(PropertySize)); + stream.read(reinterpret_cast(&ArrayIdx), sizeof(ArrayIdx)); + stream.read(reinterpret_cast(&valueNameIdx), sizeof(valueNameIdx)); + ss << "\tInnerNameIdx: " << FormatHEX(valueNameIdx) << " -> " << info.IndexToName(valueNameIdx) << std::endl; + break; + case 2: ///IntProperty + Type = "IntProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + stream.read(reinterpret_cast(&offset), sizeof(offset)); + ss << "\tLocal offset: " << FormatHEX(offset) << std::endl; + stream.read(reinterpret_cast(&valueInt), sizeof(valueInt)); + ss << "\tValue: " << FormatHEX((uint32_t)valueInt) << " -> " << valueInt << std::endl; + if (offset == 0xE0) + Name = "SizeX"; + else if (offset == 0xE4) + Name = "SizeY"; + else if (offset == 0x0138) + Name = "MipTailBaseIdx"; + else if (offset == 0x0140) + Name = "NumTFCMipMaps"; + break; + case 3: ///BoolProperty + Type = "BoolProperty"; + ss << "\tType: " << type << " -> " << Type << std::endl; + stream.read(reinterpret_cast(&offset), sizeof(offset)); + ss << "\tLocal offset: " << FormatHEX(offset) << std::endl; + stream.read(reinterpret_cast(&NameIdx), sizeof(NameIdx)); + Name = info.IndexToName(NameIdx); + ss << "\tNameIdx: " << FormatHEX(NameIdx) << " -> " << info.IndexToName(NameIdx) << std::endl; + stream.read(reinterpret_cast(&PropertySize), sizeof(PropertySize)); + stream.read(reinterpret_cast(&ArrayIdx), sizeof(ArrayIdx)); + stream.read(reinterpret_cast(&BoolValue), sizeof(BoolValue)); + ss << "\tValue: " << FormatHEX(BoolValue) << " -> " << (int)BoolValue << std::endl; + break; + case 4: ///FloatProperty + Type = "FloatProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + stream.read(reinterpret_cast(&offset), sizeof(offset)); + ss << "\tLocal offset: " << FormatHEX(offset) << std::endl; + stream.read(reinterpret_cast(&valueFloat), sizeof(valueFloat)); + ss << "\tValue: " << valueFloat << std::endl; + break; + case 5: ///ObjectProperty + Type = "ObjectProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 6: ///NameProperty + Type = "NameProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + stream.read(reinterpret_cast(&offset), sizeof(offset)); + ss << "\tLocal offset: " << FormatHEX(offset) << std::endl; + stream.read(reinterpret_cast(&valueNameIdx), sizeof(valueNameIdx)); + ss << "\tNameIdx: " << FormatHEX(valueNameIdx) << " -> " << info.IndexToName(valueNameIdx) << std::endl; + if (offset == 0x104) + Name = "TextureFileCacheName"; + break; + case 7: ///DelegateProperty + Type = "DelegateProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 8: ///ClassProperty + Type = "ClassProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 9: ///ArrayProperty + Type = "ArrayProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 10: ///StructProperty + Type = "StructProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 11: ///VectorProperty + Type = "VectorProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 12: ///RotatorProperty + Type = "RotatorProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 13: ///StrProperty + Type = "StrProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 14: ///MapProperty + Type = "MapProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + case 15: ///InterfaceProperty + Type = "InterfaceProperty"; + Name = Type; //temp + ss << "\tType: " << type << " -> " << Type << std::endl; + ss << "ToDo!" << std::endl; + break; + default: + Name = "None"; + Type = "None"; + ss << "\tType: " << type << " -> unknown property, can't deserialize." << std::endl; + break; } return ss.str(); } @@ -361,7 +494,7 @@ std::string BCArrayOfNames::Deserialize(std::istream& stream, UPKInfo& info, UOb 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; + uint32_t maxOffset = info.GetExportEntry(owner).SerialOffset + info.GetExportEntry(owner).SerialSize; std::ostringstream ss; ss << "UDefaultProperty:\n"; stream.read(reinterpret_cast(&NameIdx), sizeof(NameIdx)); @@ -405,10 +538,10 @@ std::string UDefaultProperty::Deserialize(std::istream& stream, UPKInfo& info, U } if (PropertySize > 0) { - size_t offset = stream.tellg(); + uint32_t offset = stream.tellg(); InnerValue.resize(PropertySize); stream.read(InnerValue.data(), InnerValue.size()); - size_t offset2 = stream.tellg(); + uint32_t offset2 = stream.tellg(); if (QuickMode == false) { stream.seekg(offset); @@ -655,7 +788,7 @@ std::string UDefaultProperty::FindArrayType(std::string ArrName, std::istream& s if (ArrayEntry.Type == "ArrayProperty") { UArrayProperty ArrProperty; - size_t StreamPos = stream.tellg(); + uint32_t StreamPos = stream.tellg(); stream.seekg(ArrayEntry.SerialOffset); /// quick-deserialize property, as we need only it's inner type info ArrProperty.SetRef(ObjRef); @@ -788,44 +921,75 @@ std::string UDefaultProperty::Serialize(UPKInfo& info) return ss.str(); } -size_t UDefaultProperty::GetInnerValueOffset(uint16_t ver) +std::string UDefaultProperty::SerializeBatmanAC(UPKInfo& info) +{ + ///not all the properties are yet added! + std::ostringstream ss; + if (Name != "None") + { + if (Type == "IntProperty") + { + uint16_t type = 2, offset = 0; + if (Name == "SizeX") + offset = 0xE0; + else if (Name == "SizeY") + offset = 0xE4; + else if (Name == "MipTailBaseIdx") + offset = 0x0138; + else if (Name == "NumTFCMipMaps") + offset = 0x0140; + ss.write(reinterpret_cast(&type), sizeof(type)); + ss.write(reinterpret_cast(&offset), sizeof(offset)); + if (InnerValue.size() > 0) + ss.write(InnerValue.data(), InnerValue.size()); + } + else if (Type == "NameProperty") + { + uint16_t type = 6, offset = 0; + if (Name == "TextureFileCacheName") + offset = 0x104; + ss.write(reinterpret_cast(&type), sizeof(type)); + ss.write(reinterpret_cast(&offset), sizeof(offset)); + if (InnerValue.size() > 0) + ss.write(InnerValue.data(), InnerValue.size()); + } + } + return ss.str(); +} + +uint32_t UDefaultProperty::GetInnerValueOffset(uint16_t ver) { if (Name == "None") { - return 8; + if (ver == VER_BATMAN_CITY) + return 2; + else + return 8; } else if (Type == "ByteProperty") { - if (ver >= VER_XCOM) + if (ver == VER_BATMAN_CITY) + return 20; ///2+2+8+4+4 + else if (ver >= VER_XCOM) return 32; ///8+8+4+4+8 return 24; } else { - return 24; ///8+8+4+4 + if (ver == VER_BATMAN_CITY) + return 4; ///2+2 - cautious: might not be true for all of them!!! + else + return 24; ///8+8+4+4 } } std::string UObject::Deserialize(std::istream& stream, UPKInfo& info) { + uint32_t beginOffset = stream.tellg(); std::ostringstream ss; ss << "UObject:\n"; - if (info.GetVersion() == VER_BATMAN_CITY) - { - stream.read(reinterpret_cast(&BCUnknown1), sizeof(BCUnknown1)); - ss << "\tUnknown1 = " << FormatHEX((uint32_t)BCUnknown1) << std::endl; - stream.read(reinterpret_cast(&BCUnknown2), sizeof(BCUnknown2)); - ss << "\tUnknown2 = " << FormatHEX((uint32_t)BCUnknown2) << std::endl; - } stream.read(reinterpret_cast(&ObjRef), sizeof(ObjRef)); ss << "\tPrevObjRef = " << FormatHEX((uint32_t)ObjRef) << " -> " << info.ObjRefToName(ObjRef) << std::endl; - if (info.GetVersion() == VER_BATMAN_CITY) - { - ss << BCUnkNames.Deserialize(stream, info, ThisRef); - uint32_t pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); - if (pos == info.GetExportEntry(ThisRef).SerialSize) - return ss.str(); - } if (Type != GlobalType::UClass) { FObjectExport ThisTableEntry = info.GetExportEntry(ThisRef); @@ -836,6 +1000,8 @@ std::string UObject::Deserialize(std::istream& stream, UPKInfo& info) } ss << DefaultProperties.Deserialize(stream, info, ThisRef, TryUnsafe, QuickMode); } + uint32_t endOffset = stream.tellg(); + UObjExportEntrySize = endOffset - beginOffset; return ss.str(); } @@ -1304,6 +1470,9 @@ std::string ULevel::Deserialize(std::istream& stream, UPKInfo& info) UObjectReference A; uint32_t NumActors; ss << UObject::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() > info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); uint32_t pos = ((unsigned)stream.tellg() - info.GetExportEntry(ThisRef).SerialOffset); ss << "ULevel:\n"; stream.read(reinterpret_cast(&A), sizeof(A)); @@ -1343,6 +1512,9 @@ std::string UTexture2D::Deserialize(std::istream& stream, UPKInfo& info) { std::ostringstream ss; ss << UTexture::Deserialize(stream, info); + /// check for bad data + if ((unsigned)stream.tellg() >= info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize) + return ss.str(); ///attempt to init vars from default property list std::vector props = DefaultProperties.GetDefaultProperties(); @@ -1360,7 +1532,7 @@ std::string UTexture2D::Deserialize(std::istream& stream, UPKInfo& info) stream.read(reinterpret_cast(&Unknown3), sizeof(Unknown3)); ss << "\tUnknown3: " << FormatHEX(Unknown3) << std::endl; - size_t maxOffset = info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize; + uint32_t maxOffset = info.GetExportEntry(ThisRef).SerialOffset + info.GetExportEntry(ThisRef).SerialSize; uint32_t absOffset; stream.read(reinterpret_cast(&absOffset), sizeof(absOffset)); @@ -1376,6 +1548,7 @@ std::string UTexture2D::Deserialize(std::istream& stream, UPKInfo& info) MipMaps.clear(); MipMaps.reserve(MipMapCount); + NumEmptyMipMaps = 0; for (unsigned i = 0; i < MipMapCount; ++i) { UTexture2DMipMap nextData; @@ -1389,7 +1562,20 @@ std::string UTexture2D::Deserialize(std::istream& stream, UPKInfo& info) return ss.str(); } MipMaps.push_back(nextData); + if (nextData.GetWasCompressed()) + { + HasCompressedMipMaps = true; + int maxRes = nextData.GetSizeX() > nextData.GetSizeY() ? nextData.GetSizeX() : nextData.GetSizeY(); + if (MinObservedResForCompression < 0 || MinObservedResForCompression > maxRes) + MinObservedResForCompression = maxRes; + } + if (nextData.IsDataEmpty()) + ++NumEmptyMipMaps; } + if (NumEmptyMipMaps) + ss << "\tNum of empty mipmaps: " << NumEmptyMipMaps << std::endl; + if (info.GetVersion() == VER_BATMAN_CITY && NumTFCMipMaps != -1) + ss << "\tNumTFCMipMaps: " << NumTFCMipMaps << std::endl; MipMapCount = MipMaps.size(); if (MipMaps.size() > 0) { @@ -1410,7 +1596,7 @@ std::string UTexture2D::Deserialize(std::istream& stream, UPKInfo& info) return ss.str(); } -std::string UTexture2D::SerializeTexture2DData(size_t offset) +std::string UTexture2D::SerializeTexture2DData(uint32_t offset) { std::stringstream ss; @@ -1424,7 +1610,7 @@ std::string UTexture2D::SerializeTexture2DData(size_t offset) ss.write(reinterpret_cast(&Unknown2), 4); ss.write(reinterpret_cast(&Unknown3), 4); ///absolute file offset for the data following this absolute file offset - size_t nextDataOffset = offset + ss.tellp() + 4; + uint32_t nextDataOffset = offset + ss.tellp() + 4; ss.write(reinterpret_cast(&nextDataOffset), 4); ss.write(reinterpret_cast(&MipMapCount), 4); @@ -1444,12 +1630,17 @@ std::string UTexture2D::SerializeTexture2DData(size_t offset) bool UTexture2D::ExportToExternalFile(CustomTFC& T2DFile, UPKInfo& info, bool compressedOnly) { bool success = false; + NumTFCMipMaps = 0; for (unsigned i = 0; i < MipMapCount; ++i) { if (MipMaps[i].IsDataCompressed() || !compressedOnly) { MipMaps[i].SetExternalFileName(TextureFileCacheName + ".tfc"); - success |= MipMaps[i].ExportToExternalFile(T2DFile, info.GetExportEntry(ThisRef).FullName); + if (MipMaps[i].ExportToExternalFile(T2DFile, info.GetExportEntry(ThisRef).FullName)) + { + success = true; + ++NumTFCMipMaps; + } } } return success; @@ -1492,6 +1683,8 @@ void UTexture2D::SetClassVarFromProperty(UDefaultProperty& property, UPKInfo& in Height = property.GetValueInt(); else if (property.GetName() == "MipTailBaseIdx" && property.GetType() == "IntProperty") MipTailBaseIdx = property.GetValueInt(); + else if (property.GetName() == "NumTFCMipMaps" && property.GetType() == "IntProperty") + NumTFCMipMaps = property.GetValueInt(); else if (property.GetName() == "LODBias" && property.GetType() == "IntProperty") LODBias = property.GetValueInt(); else if (property.GetName() == "NeverStream" && property.GetType() == "BoolProperty") diff --git a/UObject.h b/UObject.h index 0afaad0..e3d7b54 100644 --- a/UObject.h +++ b/UObject.h @@ -9,6 +9,8 @@ #include "UPKInfo.h" #include "CustomTFC.h" +#define LOD_BIAS_BAD 100500 + /// global type enumeration enum class GlobalType { @@ -80,7 +82,7 @@ class UBulkDataMirror UBulkDataMirror() {} std::string Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner); std::string Serialize(); - std::string Serialize(size_t offset); + std::string Serialize(uint32_t offset); bool ExportToExternalFile(CustomTFC& T2DFile, std::string ObjName); bool TryLzoCompression(); uint32_t CalculateSerializedSize(); @@ -95,13 +97,13 @@ class UBulkDataMirror void SetEmpty(); void SetBulkDataRaw(std::vector Data); void SetBulkData(std::vector Data) { BulkData = Data; } - void SetFileOffset(size_t offset) { SavedBulkDataOffsetInFile = offset; } + void SetFileOffset(uint32_t offset) { SavedBulkDataOffsetInFile = offset; } void SetSavedBulkDataFlags(uint32_t flags) { SavedBulkDataFlags = flags; } void SetSavedElementCount(uint32_t elementCount) { SavedElementCount = elementCount; } void SetExternalFileName(std::string filename) { externalFileName = filename; } ///getters std::vector GetBulkData() { return BulkData; } - size_t GetBulkDataRelOffset() { return 16; } + uint32_t GetBulkDataRelOffset() { return 16; } uint32_t GetSavedBulkDataFlags() { return SavedBulkDataFlags; } uint32_t GetSavedElementCount() { return SavedElementCount; } uint32_t GetSavedBulkDataSizeOnDisk() { return SavedBulkDataSizeOnDisk; } @@ -131,7 +133,7 @@ class UTexture2DMipMap : public UBulkDataMirror public: UTexture2DMipMap() {} std::string Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner); - std::string Serialize(size_t offset); + std::string Serialize(uint32_t offset); uint32_t CalculateSerializedSize(); uint32_t GetSizeX() { return SizeX; } uint32_t GetSizeY() { return SizeY; } @@ -160,8 +162,11 @@ class UDefaultProperty std::string FindArrayType(std::string ArrName, std::istream& stream, UPKInfo& info); std::string GuessArrayType(std::string ArrName); std::string Serialize(UPKInfo& info); + std::string SerializeBatmanAC(UPKInfo& info); + ///BatmanAC specific + std::string DeserializeBatmanAC(std::istream& stream, UPKInfo& info, UObjectReference owner, bool unsafe = false, bool quick = false); /// getters - size_t GetInnerValueOffset(uint16_t ver = VER_XCOM); + uint32_t GetInnerValueOffset(uint16_t ver = VER_XCOM); std::string GetName() { return Name; } std::string GetType() { return Type; } UNameIndex GetValueNameIdx() { return valueNameIdx; } @@ -200,19 +205,8 @@ class UDefaultPropertiesList std::vector GetDefaultProperties() { return DefaultProperties; } protected: std::vector DefaultProperties; - size_t PropertyOffset; - size_t PropertySize; -}; - -class BCArrayOfNames -{ -public: - BCArrayOfNames() {} - ~BCArrayOfNames() {} - std::string Deserialize(std::istream& stream, UPKInfo& info, UObjectReference owner); -protected: - std::vector Names; - uint32_t NamesNum; + uint32_t PropertyOffset; + uint32_t PropertySize; }; /// parent class of all Unreal objects @@ -229,19 +223,18 @@ class UObject virtual bool IsProperty() { return false; } virtual bool IsState() { return false; } void SetClassVarFromProperty(UDefaultProperty& property, UPKInfo& info) {} + uint32_t GetUObjExportEntrySize() { return UObjExportEntrySize; } protected: /// persistent - uint16_t BCUnknown1; /// batman city unknown var - uint32_t BCUnknown2; /// batman city unknown var UObjectReference ObjRef; /// Next object (Linker-related) UDefaultPropertiesList DefaultProperties; /// for non-Class objects only - BCArrayOfNames BCUnkNames; /// batman city /// memory GlobalType Type; UObjectReference ThisRef; - size_t FlagsOffset; + uint32_t FlagsOffset; bool TryUnsafe; bool QuickMode; + uint32_t UObjExportEntrySize = -1; }; class UObjectNone: public UObject @@ -259,15 +252,15 @@ class UField: public UObject ~UField() {} std::string Deserialize(std::istream& stream, UPKInfo& info); UObjectReference GetNextRef() { return NextRef; } - size_t GetNextRefOffset() { return NextRefOffset; } + uint32_t GetNextRefOffset() { return NextRefOffset; } protected: /// persistent UObjectReference NextRef; UObjectReference ParentRef; /// for Struct objects only /// memory - size_t FieldOffset; - size_t FieldSize; - size_t NextRefOffset; + uint32_t FieldOffset; + uint32_t FieldSize; + uint32_t NextRefOffset; }; class UStruct: public UField @@ -280,8 +273,8 @@ class UStruct: public UField UObjectReference GetFirstChildRef() { return FirstChildRef; } uint32_t GetScriptSerialSize() { return ScriptSerialSize; } uint32_t GetScriptMemorySize() { return ScriptMemorySize; } - size_t GetScriptOffset() { return ScriptOffset; } - size_t GetFirstChildRefOffset() { return FirstChildRefOffset; } + uint32_t GetScriptOffset() { return ScriptOffset; } + uint32_t GetFirstChildRefOffset() { return FirstChildRefOffset; } protected: /// persistent UObjectReference ScriptTextRef; @@ -293,10 +286,10 @@ class UStruct: public UField uint32_t ScriptSerialSize; std::vector DataScript; /// memory - size_t StructOffset; - size_t StructSize; - size_t ScriptOffset; - size_t FirstChildRefOffset; + uint32_t StructOffset; + uint32_t StructSize; + uint32_t ScriptOffset; + uint32_t FirstChildRefOffset; }; class UFunction: public UStruct @@ -313,8 +306,8 @@ class UFunction: public UStruct uint16_t RepOffset; UNameIndex NameIdx; /// memory - size_t FunctionOffset; - size_t FunctionSize; + uint32_t FunctionOffset; + uint32_t FunctionSize; }; class UScriptStruct: public UStruct @@ -328,8 +321,8 @@ class UScriptStruct: public UStruct uint32_t StructFlags; UDefaultPropertiesList StructDefaultProperties; /// memory - size_t ScriptStructOffset; - size_t ScriptStructSize; + uint32_t ScriptStructOffset; + uint32_t ScriptStructSize; }; class UState: public UStruct @@ -347,8 +340,8 @@ class UState: public UStruct uint32_t StateMapSize; std::vector > StateMap; /// memory - size_t StateOffset; - size_t StateSize; + uint32_t StateOffset; + uint32_t StateSize; }; class UClass: public UState @@ -613,7 +606,7 @@ class UTexture2D: public UTexture ~UTexture2D() {} ///serialization std::string Deserialize(std::istream& stream, UPKInfo& info); - std::string SerializeTexture2DData(size_t offset); + std::string SerializeTexture2DData(uint32_t offset); bool TryLzoCompression(int minResolution = -1); bool ExportToExternalFile(CustomTFC& T2DFile, UPKInfo& info, bool compressedOnly = true); uint32_t CalculateTexture2DDataSize(); @@ -627,6 +620,10 @@ class UTexture2D: public UTexture std::string GetTextureFileCacheName() { return TextureFileCacheName; } bool GetNeverStream() { return NeverStream; } bool GetDoNotReadTFC() { return DoNotReadTFC; } + bool GetHasCompressedMipMaps() { return HasCompressedMipMaps; } + int GetMinObservedResForCompression() { return MinObservedResForCompression; } + int GetNumEmptyMipMaps() { return NumEmptyMipMaps; } + int GetNumTFCMipMaps() { return NumTFCMipMaps; } ///setters void SetMipMaps(std::vector mmaps) { MipMaps = mmaps; } void SetHeight(uint32_t h) { Height = h; } @@ -656,6 +653,10 @@ class UTexture2D: public UTexture std::vector UnknownData; ///memory vars bool DoNotReadTFC = false; + bool HasCompressedMipMaps = false; + int MinObservedResForCompression = -1; + int NumEmptyMipMaps = 0; + int NumTFCMipMaps = -1; ///BAC: number of high-res mipmaps stored in external tfc ///internal methods void SetClassVarFromProperty(UDefaultProperty& property, UPKInfo& info); }; diff --git a/UPKInfo.cpp b/UPKInfo.cpp index f8faeb2..fb92b64 100644 --- a/UPKInfo.cpp +++ b/UPKInfo.cpp @@ -6,7 +6,8 @@ UPKInfo::UPKInfo(std::istream& stream): Summary(), NoneIdx(0), ReadError(UPKReadErrors::NoErrors), Compressed(false), CompressedChunk(false) { - if (!Read(stream)) + ///on read errors assume the package might be fully compressed and try reading compressed header + if (!Read(stream) && ReadError != UPKReadErrors::IsCompressed) ReadCompressedHeader(stream); } @@ -18,7 +19,7 @@ bool UPKInfo::ReadCompressedHeader(std::istream& stream) return false; } stream.seekg(0, std::ios::end); - size_t Size = stream.tellg(); + uint32_t Size = stream.tellg(); stream.seekg(0); stream.read(reinterpret_cast(&CompressedHeader.Signature), 4); if (CompressedHeader.Signature != 0x9E2A83C1) @@ -55,7 +56,7 @@ bool UPKInfo::ReadCompressedHeader(std::istream& stream) bool UPKInfo::Read(std::istream& stream) { - size_t headerEndOffset = 4; + uint32_t headerEndOffset = 4; CompressedHeader = FCompressedChunkHeader{}; if (!stream.good()) { @@ -213,7 +214,9 @@ bool UPKInfo::Read(std::istream& stream) stream.read(reinterpret_cast(&EntryToRead.NameIdx), sizeof(EntryToRead.NameIdx)); stream.read(reinterpret_cast(&EntryToRead.ArchetypeRef), sizeof(EntryToRead.ArchetypeRef)); if (Summary.Version == VER_BATMAN_CITY) + { stream.read(reinterpret_cast(&EntryToRead.Unknown2), sizeof(EntryToRead.Unknown2)); + } stream.read(reinterpret_cast(&EntryToRead.ObjectFlagsH), sizeof(EntryToRead.ObjectFlagsH)); stream.read(reinterpret_cast(&EntryToRead.ObjectFlagsL), sizeof(EntryToRead.ObjectFlagsL)); stream.read(reinterpret_cast(&EntryToRead.SerialSize), sizeof(EntryToRead.SerialSize)); @@ -443,7 +446,7 @@ UObjectReference UPKInfo::FindObjectByName(std::string Name, bool isExport) return 0; } -UObjectReference UPKInfo::FindObjectByOffset(size_t offset) +UObjectReference UPKInfo::FindObjectByOffset(uint32_t offset) { for (unsigned i = 1; i < ExportTable.size(); ++i) { @@ -631,7 +634,7 @@ std::string UPKInfo::FormatExport(uint32_t idx, bool verbose) << "\tArchetypeRef: " << FormatHEX((uint32_t)Entry.ArchetypeRef) << " -> " << ObjRefToName(Entry.ArchetypeRef) << std::endl; if (Summary.Version == VER_BATMAN_CITY) ss << "\tUnknown2: " << FormatHEX(Entry.Unknown2) << std::endl; - ss << "\tObjectFlagsH: " << FormatHEX(Entry.ObjectFlagsH) << std::endl + ss << "\tObjectFlagsH: " << FormatHEX(Entry.ObjectFlagsH) << std::endl << FormatObjectFlagsH(Entry.ObjectFlagsH) << "\tObjectFlagsL: " << FormatHEX(Entry.ObjectFlagsL) << std::endl << FormatObjectFlagsL(Entry.ObjectFlagsL) diff --git a/UPKInfo.h b/UPKInfo.h index a8f0997..222d57d 100644 --- a/UPKInfo.h +++ b/UPKInfo.h @@ -103,9 +103,9 @@ struct FPackageFileSummary std::vector CompressedChunks; std::vector UnknownDataChunk; /// memory - size_t HeaderSizeOffset; - size_t NameCountOffset; - size_t UPKFileSize; + uint32_t HeaderSizeOffset; + uint32_t NameCountOffset; + uint32_t UPKFileSize; }; struct FNameEntry @@ -116,8 +116,8 @@ struct FNameEntry uint32_t NameFlagsL; uint32_t NameFlagsH; /// memory - size_t EntryOffset; - size_t EntrySize; + uint32_t EntryOffset; + uint32_t EntrySize; }; struct FObjectImport @@ -128,8 +128,8 @@ struct FObjectImport UObjectReference OwnerRef; UNameIndex NameIdx; /// memory - size_t EntryOffset; - size_t EntrySize; + uint32_t EntryOffset; + uint32_t EntrySize; std::string Name; std::string FullName; std::string Type; @@ -143,6 +143,7 @@ struct FObjectExport UObjectReference OwnerRef; UNameIndex NameIdx; UObjectReference ArchetypeRef; + uint32_t Unknown2; /// batman city uint32_t ObjectFlagsH; uint32_t ObjectFlagsL; uint32_t SerialSize; @@ -151,11 +152,10 @@ struct FObjectExport uint32_t NetObjectCount; FGuid GUID; uint32_t Unknown1; - uint32_t Unknown2; /// batman city std::vector NetObjects; /// 4 x NetObjectCount bytes of data /// memory - size_t EntryOffset; - size_t EntrySize; + uint32_t EntryOffset; + uint32_t EntrySize; std::string Name; std::string FullName; std::string Type; @@ -184,7 +184,7 @@ class UPKInfo UObjectReference FindObject(std::string FullName, bool isExport = true); UObjectReference FindObjectOfType(std::string FullName, std::string Type, bool isExport = true); UObjectReference FindObjectByName(std::string Name, bool isExport = true); - UObjectReference FindObjectByOffset(size_t offset); + UObjectReference FindObjectByOffset(uint32_t offset); bool IsNoneIdx(UNameIndex idx) { return (idx.NameTableIdx == NoneIdx); } /// Getters const FPackageFileSummary& GetSummary() { return Summary; } diff --git a/UPKUtils.cbp b/UPKUtils.cbp index 98e6057..860607b 100644 --- a/UPKUtils.cbp +++ b/UPKUtils.cbp @@ -75,6 +75,12 @@