Advertisement
malice936

Group.cpp

Apr 13th, 2025
258
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 11.62 KB | Gaming | 0 0
  1. #include "Group.h"
  2. #include "Record.h"
  3. #include "HAL/FileManager.h"
  4. #include "Serialization/MemoryReader.h"
  5. #include "Serialization/MemoryWriter.h"
  6.  
  7. // GRUP type identifier as a 4-byte constant ('GRUP').
  8. static const uint32 GroupType = 'G' | ('R' << 8) | ('U' << 16) | ('P' << 24);
  9.  
  10. // Returns the header length to skip based on game-specific format (12 for Oblivion, 16 for others).
  11. static int32 GetHeaderLengthToSkip(EGameID GameID)
  12. {
  13.     return (GameID == EGameID::Oblivion) ? 12 : 16;
  14. }
  15.  
  16. // Internal function to read a group from an archive, handling recursive subgroups.
  17. // Parameters: OutGroup (output group), Archive (data source), GameID (game format), bSkipSubrecords (skip record subrecords), Depth (recursion depth).
  18. // Returns true on success, false on failure (e.g., invalid type, archive error).
  19. static bool ReadGroupInternal(FGroup& OutGroup, FArchive& Archive, EGameID GameID, bool bSkipSubrecords, int32 Depth = 0)
  20. {
  21.     // Limit recursion depth to prevent stack overflow on malformed files.
  22.     const int32 MaxDepth = 100;
  23.     if (Depth >= MaxDepth)
  24.     {
  25.         UE_LOG(LogTemp, Warning, TEXT("Maximum group recursion depth reached."));
  26.         return false;
  27.     }
  28.  
  29.     // Clear existing FormIDs and initialize a set for deduplication.
  30.     OutGroup.FormIds.Empty();
  31.     OutGroup.Label = TEXT(""); // Initialize label to empty.
  32.     TSet<FFormID> FormIdSet;
  33.  
  34.     // Get game-specific header length to skip (label and other fields).
  35.     int32 HeaderLengthToSkip = GetHeaderLengthToSkip(GameID);
  36.  
  37.     // Read the 4-byte type identifier.
  38.     uint32 Type = 0;
  39.     Archive.Serialize(&Type, sizeof(uint32));
  40.     if (Type != GroupType)
  41.     {
  42.         UE_LOG(LogTemp, Warning, TEXT("Invalid group type: %u"), Type);
  43.         return false;
  44.     }
  45.  
  46.     // Read the total group size, including header and contents.
  47.     uint32 GroupSize = 0;
  48.     Archive.Serialize(&GroupSize, sizeof(uint32));
  49.     if (Archive.IsError())
  50.     {
  51.         UE_LOG(LogTemp, Warning, TEXT("Failed to read group size."));
  52.         return false;
  53.     }
  54.  
  55.     // Read the group label (4 bytes, interpreted as string or integer based on game).
  56.     TArray<uint8> LabelBytes;
  57.     LabelBytes.SetNumUninitialized(4);
  58.     Archive.Serialize(LabelBytes.GetData(), 4);
  59.     if (Archive.IsError())
  60.     {
  61.         UE_LOG(LogTemp, Warning, TEXT("Failed to read group label."));
  62.         return false;
  63.     }
  64.     OutGroup.Label = FString(4, reinterpret_cast<TCHAR*>(LabelBytes.GetData()));
  65.  
  66.     // Skip remaining header fields (e.g., stamp, version).
  67.     Archive.Seek(Archive.Tell() + (HeaderLengthToSkip - 4));
  68.     if (Archive.IsError())
  69.     {
  70.         UE_LOG(LogTemp, Warning, TEXT("Failed to skip group header."));
  71.         return false;
  72.     }
  73.  
  74.     // Calculate the size of records/subgroups to process.
  75.     int32 TotalRecordsSize = GroupSize - 8 - HeaderLengthToSkip;
  76.     int64 StartPos = Archive.Tell();
  77.  
  78.     // Read records or subgroups until the size is consumed or an error occurs.
  79.     while (!Archive.IsError() && (Archive.Tell() - StartPos) < TotalRecordsSize)
  80.     {
  81.         int64 CurrentPos = Archive.Tell();
  82.         uint32 NextType = 0;
  83.         Archive.Serialize(&NextType, sizeof(uint32));
  84.         Archive.Seek(CurrentPos); // Rewind to process the item.
  85.  
  86.         if (NextType == GroupType)
  87.         {
  88.             // Recursively read a subgroup.
  89.             FGroup SubGroup;
  90.             if (!ReadGroupInternal(SubGroup, Archive, GameID, bSkipSubrecords, Depth + 1))
  91.             {
  92.                 UE_LOG(LogTemp, Warning, TEXT("Failed to read subgroup at position %lld."), CurrentPos);
  93.                 return false;
  94.             }
  95.             for (const FFormID& FormId : SubGroup.FormIds)
  96.             {
  97.                 FormIdSet.Add(FormId); // Deduplicate FormIDs.
  98.             }
  99.         }
  100.         else
  101.         {
  102.             // Read a record and extract its FormID.
  103.             FRecord Record;
  104.             if (!URecordHelper::ReadRecord(Record, Archive, GameID, bSkipSubrecords))
  105.             {
  106.                 UE_LOG(LogTemp, Warning, TEXT("Failed to read record at position %lld."), CurrentPos);
  107.                 return false;
  108.             }
  109.             FormIdSet.Add(Record.FormId);
  110.         }
  111.     }
  112.  
  113.     // Convert deduplicated FormIDs to array.
  114.     OutGroup.FormIds = FormIdSet.Array();
  115.     return !Archive.IsError();
  116. }
  117.  
  118. // Internal function to write a group to an archive, handling header and contents.
  119. // Parameters: Group (input group), Archive (output destination), GameID (game format).
  120. // Returns true on success, false on failure.
  121. static bool WriteGroupInternal(const FGroup& Group, FArchive& Archive, EGameID GameID)
  122. {
  123.     // Write GRUP type identifier.
  124.     uint32 Type = GroupType;
  125.     Archive.Serialize(&Type, sizeof(uint32));
  126.     if (Archive.IsError())
  127.     {
  128.         UE_LOG(LogTemp, Warning, TEXT("Failed to write group type."));
  129.         return false;
  130.     }
  131.  
  132.     // Placeholder for group size (to be updated later).
  133.     uint32 GroupSize = 0;
  134.     int64 SizePos = Archive.Tell();
  135.     Archive.Serialize(&GroupSize, sizeof(uint32));
  136.     if (Archive.IsError())
  137.     {
  138.         UE_LOG(LogTemp, Warning, TEXT("Failed to write group size placeholder."));
  139.         return false;
  140.     }
  141.  
  142.     // Write group label (4 bytes).
  143.     TArray<uint8> LabelBytes;
  144.     LabelBytes.SetNumZeroed(4);
  145.     for (int32 i = 0; i < 4 && i < Group.Label.Len(); ++i)
  146.     {
  147.         LabelBytes[i] = static_cast<uint8>(Group.Label[i]);
  148.     }
  149.     Archive.Serialize(LabelBytes.GetData(), 4);
  150.     if (Archive.IsError())
  151.     {
  152.         UE_LOG(LogTemp, Warning, TEXT("Failed to write group label."));
  153.         return false;
  154.     }
  155.  
  156.     // Write remaining header fields (e.g., stamp, version).
  157.     int32 HeaderLengthToSkip = GetHeaderLengthToSkip(GameID);
  158.     TArray<uint8> HeaderPadding;
  159.     HeaderPadding.SetNumZeroed(HeaderLengthToSkip - 4);
  160.     Archive.Serialize(HeaderPadding.GetData(), HeaderPadding.Num());
  161.     if (Archive.IsError())
  162.     {
  163.         UE_LOG(LogTemp, Warning, TEXT("Failed to write group header padding."));
  164.         return false;
  165.     }
  166.  
  167.     // Write minimal records for each FormID (FGroup lacks full record data).
  168.     for (const FFormID& FormId : Group.FormIds)
  169.     {
  170.         // Write a minimal record header (type "NULL", size 12/16, FormID).
  171.         TArray<uint8> TypeBytes;
  172.         TypeBytes.SetNumZeroed(4);
  173.         FString NullType = TEXT("NULL");
  174.         for (int32 i = 0; i < 4 && i < NullType.Len(); ++i)
  175.         {
  176.             TypeBytes[i] = static_cast<uint8>(NullType[i]);
  177.         }
  178.         Archive.Serialize(TypeBytes.GetData(), 4);
  179.         if (Archive.IsError())
  180.         {
  181.             UE_LOG(LogTemp, Warning, TEXT("Failed to write record type for FormID."));
  182.             return false;
  183.         }
  184.  
  185.         // Write record size (minimal: flags + FormID + padding).
  186.         uint32 RecordSize = (GameID == EGameID::Oblivion) ? 12 : 16;
  187.         Archive.Serialize(&RecordSize, sizeof(uint32));
  188.         if (Archive.IsError())
  189.         {
  190.             UE_LOG(LogTemp, Warning, TEXT("Failed to write record size."));
  191.             return false;
  192.         }
  193.  
  194.         // Write flags (0 for minimal record).
  195.         int32 Flags = 0;
  196.         Archive.Serialize(&Flags, sizeof(int32));
  197.         if (Archive.IsError())
  198.         {
  199.             UE_LOG(LogTemp, Warning, TEXT("Failed to write record flags."));
  200.             return false;
  201.         }
  202.  
  203.         // Write FormID.
  204.         int32 FormIdValue = FormId.Value;
  205.         Archive.Serialize(&FormIdValue, sizeof(int32));
  206.         if (Archive.IsError())
  207.         {
  208.             UE_LOG(LogTemp, Warning, TEXT("Failed to write FormID."));
  209.             return false;
  210.         }
  211.  
  212.         // Write padding (4 for Oblivion, 8 for others).
  213.         int32 PaddingLength = (GameID == EGameID::Oblivion) ? 4 : 8;
  214.         TArray<uint8> Padding;
  215.         Padding.SetNumZeroed(PaddingLength);
  216.         Archive.Serialize(Padding.GetData(), PaddingLength);
  217.         if (Archive.IsError())
  218.         {
  219.             UE_LOG(LogTemp, Warning, TEXT("Failed to write record padding."));
  220.             return false;
  221.         }
  222.     }
  223.  
  224.     // Update group size.
  225.     int64 EndPos = Archive.Tell();
  226.     GroupSize = static_cast<uint32>(EndPos - SizePos);
  227.     Archive.Seek(SizePos);
  228.     Archive.Serialize(&GroupSize, sizeof(uint32));
  229.     Archive.Seek(EndPos);
  230.     if (Archive.IsError())
  231.     {
  232.         UE_LOG(LogTemp, Warning, TEXT("Failed to update group size."));
  233.         return false;
  234.     }
  235.  
  236.     return true;
  237. }
  238.  
  239. // Reads a group from a file, creating an archive and delegating to ReadGroupInternal.
  240. bool UGroupHelper::ReadGroupFromFile(FGroup& OutGroup, const FString& FilePath, EGameID GameID, bool bSkipSubrecords)
  241. {
  242.     TUniquePtr<FArchive> FileArchive(IFileManager::Get().CreateFileReader(*FilePath));
  243.     if (!FileArchive)
  244.     {
  245.         UE_LOG(LogTemp, Warning, TEXT("Failed to open file: %s"), *FilePath);
  246.         return false;
  247.     }
  248.     return ReadGroupInternal(OutGroup, *FileArchive, GameID, bSkipSubrecords);
  249. }
  250.  
  251. // Reads a group from a byte array, creating a memory reader and delegating to ReadGroupInternal.
  252. bool UGroupHelper::ReadGroupFromBytes(FGroup& OutGroup, const TArray<uint8>& ByteArray, EGameID GameID, bool bSkipSubrecords)
  253. {
  254.     FMemoryReader MemoryArchive(ByteArray, true);
  255.     return ReadGroupInternal(OutGroup, MemoryArchive, GameID, bSkipSubrecords);
  256. }
  257.  
  258. // Writes a group to a file, creating an archive and delegating to WriteGroupInternal.
  259. bool UGroupHelper::WriteGroupToFile(const FGroup& Group, const FString& FilePath, EGameID GameID)
  260. {
  261.     TUniquePtr<FArchive> FileArchive(IFileManager::Get().CreateFileWriter(*FilePath));
  262.     if (!FileArchive)
  263.     {
  264.         UE_LOG(LogTemp, Warning, TEXT("Failed to create file: %s"), *FilePath);
  265.         return false;
  266.     }
  267.     return WriteGroupInternal(Group, *FileArchive, GameID);
  268. }
  269.  
  270. // Serializes a group to a byte array, creating a memory writer.
  271. bool UGroupHelper::WriteGroupToBytes(const FGroup& Group, TArray<uint8>& OutByteArray, EGameID GameID)
  272. {
  273.     OutByteArray.Empty();
  274.     FMemoryWriter MemoryArchive(OutByteArray, true);
  275.     return WriteGroupInternal(Group, MemoryArchive, GameID);
  276. }
  277.  
  278. // Returns the FormIDs in the group.
  279. TArray<FFormID> UGroupHelper::GetFormIds(const FGroup& Group)
  280. {
  281.     return Group.FormIds;
  282. }
  283.  
  284. // Returns the number of FormIDs in the group.
  285. int32 UGroupHelper::GetFormIdCount(const FGroup& Group)
  286. {
  287.     return Group.FormIds.Num();
  288. }
  289.  
  290. // Checks if a specific FormID exists in the group.
  291. bool UGroupHelper::HasFormId(const FGroup& Group, const FFormID& FormId)
  292. {
  293.     return Group.FormIds.Contains(FormId);
  294. }
  295.  
  296. // Returns the group label.
  297. FString UGroupHelper::GetLabel(const FGroup& Group)
  298. {
  299.     return Group.Label;
  300. }
  301.  
  302. // Adds a FormID to the group.
  303. void UGroupHelper::AddFormId(FGroup& Group, const FFormID& FormId)
  304. {
  305.     Group.FormIds.AddUnique(FormId); // Ensure no duplicates.
  306. }
  307.  
  308. // Removes a FormID from the group.
  309. bool UGroupHelper::RemoveFormId(FGroup& Group, const FFormID& FormId)
  310. {
  311.     return Group.FormIds.Remove(FormId) > 0;
  312. }
  313.  
  314. // Clears all FormIDs from the group.
  315. void UGroupHelper::ClearFormIds(FGroup& Group)
  316. {
  317.     Group.FormIds.Empty();
  318. }
  319.  
  320. // Validates the group (non-empty FormIDs, non-empty label).
  321. bool UGroupHelper::IsValidGroup(const FGroup& Group, EGameID GameID)
  322. {
  323.     if (Group.FormIds.Num() == 0)
  324.     {
  325.         return false;
  326.     }
  327.     if (Group.Label.IsEmpty())
  328.     {
  329.         return false;
  330.     }
  331.     // For non-Morrowind games, ensure FormIDs are non-zero.
  332.     if (GameID != EGameID::Morrowind)
  333.     {
  334.         for (const FFormID& FormId : Group.FormIds)
  335.         {
  336.             if (FormId.Value == 0)
  337.             {
  338.                 return false;
  339.             }
  340.         }
  341.     }
  342.     return true;
  343. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement