Advertisement
malice936

Record.cpp

Apr 13th, 2025 (edited)
234
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 25.11 KB | Gaming | 0 0
  1. // Includes the Record header, defining the FRecord struct and URecordHelper class for record-related functionality.
  2. #include "Record.h"
  3.  
  4. // Includes the Subrecord header, providing utilities for parsing and managing subrecords within a record.
  5. #include "Subrecord.h"
  6.  
  7. // Includes FileManager for creating file readers and writers, used in file-based record operations.
  8. #include "HAL/FileManager.h"
  9.  
  10. // Includes MemoryReader for deserializing data from byte arrays, used in ReadRecordFromBytes.
  11. #include "Serialization/MemoryReader.h"
  12.  
  13. // Includes MemoryWriter for serializing data to byte arrays, used in WriteRecordToBytes.
  14. #include "Serialization/MemoryWriter.h"
  15.  
  16. // Core function to parse a record from an FArchive, populating OutRecord with data based on game-specific logic.
  17. // Parameters: OutRecord (output record), Archive (data source), GameID (game-specific format), bSkipSubrecords (skip subrecord parsing).
  18. // Returns true on success, false on failure (e.g., archive errors, invalid data).
  19. bool URecordHelper::ReadRecord(FRecord& OutRecord, FArchive& Archive, EGameID GameID, bool bSkipSubrecords)
  20. {
  21.     // Clear existing subrecords to ensure OutRecord starts fresh, preventing data accumulation.
  22.     OutRecord.Subrecords.Empty();
  23.  
  24.     // Store the starting position of the record for size tracking
  25.     int64 RecordStartPos = Archive.Tell(); // Added to track the start of the record
  26.  
  27.     // Read the 4-byte record type (e.g., "NPC_", "WEAP") into a byte array.
  28.     TArray<uint8> TypeBytes;
  29.     TypeBytes.SetNumUninitialized(4); // Allocate 4 bytes without initialization for efficiency.
  30.     Archive.Serialize(TypeBytes.GetData(), 4); // Serialize 4 bytes from the archive.
  31.     if (Archive.IsError()) // Check for read errors (e.g., end of file, corruption).
  32.     {
  33.         if (Archive.AtEnd())
  34.         {
  35.             // Reached end of file, not an error for the last record
  36.             return false;
  37.         }
  38.         UE_LOG(LogTemp, Warning, TEXT("Failed to read record type at position %lld"), RecordStartPos);
  39.         return false;
  40.     }
  41.     // Convert the byte array to an FString using a null-terminated char buffer - Added to fix encoding issue with record type
  42.     char TypeBuffer[5];
  43.     FMemory::Memcpy(TypeBuffer, TypeBytes.GetData(), 4);
  44.     TypeBuffer[4] = '\0'; // Null-terminate the string - Ensures proper C-string termination
  45.     OutRecord.Type = FString(TypeBuffer); // Construct FString from ANSI string - Forces ASCII interpretation
  46.  
  47.     // Read the total size of the record's data (32-bit integer), excluding the type and size fields.
  48.     int32 RecordSize = 0; // Renamed TotalSubrecordsSize to RecordSize for clarity - Reflects the entire record data size
  49.     Archive.Serialize(&RecordSize, sizeof(int32));
  50.     if (Archive.IsError()) // Check for read errors during size serialization.
  51.     {
  52.         UE_LOG(LogTemp, Warning, TEXT("Failed to read record size at position %lld"), Archive.Tell());
  53.         return false;
  54.     }
  55.     UE_LOG(LogTemp, Log, TEXT("Record %s size: %d at position %lld"), *OutRecord.Type, RecordSize, RecordStartPos);
  56.  
  57.     // Skip 4 bytes for Morrowind-specific fields (e.g., version or unused data), not present in other games.
  58.     if (GameID == EGameID::Morrowind)
  59.     {
  60.         Archive.Seek(Archive.Tell() + 4); // Advance the archive cursor by 4 bytes.
  61.         if (Archive.IsError())
  62.         {
  63.             UE_LOG(LogTemp, Warning, TEXT("Failed to skip Morrowind-specific field at position %lld"), Archive.Tell());
  64.             return false;
  65.         }
  66.     }
  67.  
  68.     // Read the record flags (32-bit integer), indicating properties like compression status.
  69.     Archive.Serialize(&OutRecord.Flags, sizeof(int32));
  70.     if (Archive.IsError()) // Check for read errors during flags serialization.
  71.     {
  72.         UE_LOG(LogTemp, Warning, TEXT("Failed to read record flags at position %lld"), Archive.Tell());
  73.         return false;
  74.     }
  75.  
  76.     // Handle FormID for non-Morrowind games (e.g., Skyrim, Oblivion), which uniquely identify records.
  77.     if (GameID != EGameID::Morrowind)
  78.     {
  79.         // Read the 32-bit FormID value from the archive.
  80.         int32 FormIdValue = 0;
  81.         Archive.Serialize(&FormIdValue, sizeof(int32));
  82.         if (Archive.IsError()) // Check for read errors during FormID serialization.
  83.         {
  84.             UE_LOG(LogTemp, Warning, TEXT("Failed to read FormID at position %lld"), Archive.Tell());
  85.             return false;
  86.         }
  87.         // Construct an FFormID with the read value and assign it to OutRecord.
  88.         OutRecord.FormId = FFormID(FormIdValue);
  89.  
  90.         // Skip padding bytes: 4 for Oblivion, 8 for other non-Morrowind games (e.g., Skyrim).
  91.         int32 SkipLength = (GameID == EGameID::Oblivion) ? 4 : 8;
  92.         Archive.Seek(Archive.Tell() + SkipLength); // Advance the cursor by the skip length.
  93.         if (Archive.IsError())
  94.         {
  95.             UE_LOG(LogTemp, Warning, TEXT("Failed to skip padding bytes at position %lld"), Archive.Tell());
  96.             return false;
  97.         }
  98.     }
  99.     else
  100.     {
  101.         // For Morrowind, set FormID to 0, as it does not use FormIDs.
  102.         OutRecord.FormId = FFormID(0);
  103.     }
  104.  
  105.     // Calculate the fixed header size for the record (type, size, Morrowind skip, flags, and FormID/padding if applicable)
  106.     int64 HeaderSize = 4 + sizeof(int32) + (GameID == EGameID::Morrowind ? 4 : 0) + sizeof(int32) + (GameID != EGameID::Morrowind ? sizeof(int32) + ((GameID == EGameID::Oblivion) ? 4 : 8) : 0); // Added to compute header size
  107.     UE_LOG(LogTemp, Log, TEXT("Header size: %lld, starting subrecord parsing at position %lld"), HeaderSize, Archive.Tell());
  108.  
  109.     // If subrecords are to be skipped, advance the archive cursor to the end of the record
  110.     if (bSkipSubrecords)
  111.     {
  112.         Archive.Seek(RecordStartPos + HeaderSize + RecordSize); // Updated to advance to the correct end of the record
  113.         if (Archive.IsError())
  114.         {
  115.             UE_LOG(LogTemp, Warning, TEXT("Failed to seek to end of record (skipping subrecords) at position %lld"), Archive.Tell());
  116.             return false;
  117.         }
  118.         return true; // Return success if no errors occurred during seeking.
  119.     }
  120.  
  121.     // If the record is compressed (based on flags), skip subrecord parsing, as compression is not handled here.
  122.     if (AreFieldsCompressed(OutRecord))
  123.     {
  124.         Archive.Seek(RecordStartPos + HeaderSize + RecordSize); // Updated to advance to the correct end of the record
  125.         if (Archive.IsError())
  126.         {
  127.             UE_LOG(LogTemp, Warning, TEXT("Failed to seek to end of record (compressed) at position %lld"), Archive.Tell());
  128.             return false;
  129.         }
  130.         return true; // Return success if no errors occurred during seeking.
  131.     }
  132.  
  133.     // Read subrecords until the record size is consumed or an error occurs.
  134.     int64 StartPos = Archive.Tell(); // Store the starting position for subrecord parsing.
  135.     int32 LargeSubrecordSize = 0; // Tracks size override for large subrecords (via XXXX subrecord).
  136.     UE_LOG(LogTemp, Log, TEXT("Starting subrecord loop for record %s, expected bytes to read: %d"), *OutRecord.Type, RecordSize);
  137.     while (!Archive.IsError() && (Archive.Tell() - StartPos) < RecordSize) // Updated condition to use RecordSize directly for subrecord data
  138.     {
  139.         // Log position before reading each subrecord
  140.         int64 SubrecordStartPos = Archive.Tell();
  141.         UE_LOG(LogTemp, Log, TEXT("Reading subrecord at position %lld, bytes remaining: %lld"), SubrecordStartPos, RecordSize - (SubrecordStartPos - StartPos));
  142.  
  143.         // Create a new subrecord and attempt to parse it from the archive.
  144.         FSubrecord Subrecord;
  145.         if (!USubrecordHelper::ReadSubrecord(Subrecord, Archive, GameID, LargeSubrecordSize))
  146.         {
  147.             UE_LOG(LogTemp, Warning, TEXT("Failed to read subrecord at position %lld for record %s"), Archive.Tell(), *OutRecord.Type);
  148.             return false; // Return false if subrecord parsing fails.
  149.         }
  150.  
  151.         // Handle XXXX subrecord, which specifies the size of the next large subrecord.
  152.         if (Subrecord.Type == TEXT("XXXX"))
  153.         {
  154.             // Ensure the subrecord has enough data to read a 32-bit integer.
  155.             if (Subrecord.RawData.Num() >= sizeof(int32))
  156.             {
  157.                 // Extract the size from the subrecord's raw data.
  158.                 LargeSubrecordSize = *reinterpret_cast<const int32*>(Subrecord.RawData.GetData());
  159.             }
  160.             else
  161.             {
  162.                 // Reset size if XXXX subrecord is invalid.
  163.                 LargeSubrecordSize = 0;
  164.             }
  165.         }
  166.         else
  167.         {
  168.             // Reset large subrecord size for normal subrecords and add the subrecord to the record.
  169.             LargeSubrecordSize = 0;
  170.             OutRecord.Subrecords.Add(Subrecord);
  171.             UE_LOG(LogTemp, Log, TEXT("Added subrecord %s to record %s, total subrecords: %d"), *Subrecord.Type, *OutRecord.Type, OutRecord.Subrecords.Num());
  172.             if (Subrecord.Type == TEXT("VHGT"))
  173.             {
  174.                 UE_LOG(LogTemp, Log, TEXT("VHGT subrecord found, data size: %d"), Subrecord.RawData.Num());
  175.             }
  176.         }
  177.     }
  178.  
  179.     // Ensure the archive position is at the end of the record
  180.     Archive.Seek(RecordStartPos + HeaderSize + RecordSize); // Updated to ensure correct positioning after record
  181.     if (Archive.IsError())
  182.     {
  183.         UE_LOG(LogTemp, Warning, TEXT("Failed to seek to end of record at position %lld"), Archive.Tell());
  184.         return false;
  185.     }
  186.  
  187.     // Log the record type and position for debugging
  188.     UE_LOG(LogTemp, Log, TEXT("Parsed record type %s at position %lld, next record starts at %lld"), *OutRecord.Type, RecordStartPos, Archive.Tell());
  189.  
  190.     // Return true if no errors occurred during parsing, false otherwise.
  191.     return true;
  192. }
  193.  
  194. // Reads a record from a file, creating an FArchive and delegating to ReadRecord for parsing.
  195. // Parameters: OutRecord (output record), FilePath (file to read), GameID (game-specific format), bSkipSubrecords (skip subrecords).
  196. // Returns true on success, false on failure (e.g., file not found).
  197. bool URecordHelper::ReadRecordFromFile(FRecord& OutRecord, const FString& FilePath, EGameID GameID, bool bSkipSubrecords)
  198. {
  199.     // Create a file reader archive for the specified file path.
  200.     TUniquePtr<FArchive> FileArchive(IFileManager::Get().CreateFileReader(*FilePath));
  201.     if (!FileArchive) // Check if the file could be opened.
  202.     {
  203.         // Log a warning if the file cannot be opened (e.g., does not exist, permissions issue).
  204.         UE_LOG(LogTemp, Warning, TEXT("Failed to open file: %s"), *FilePath);
  205.         return false;
  206.     }
  207.     // Delegate to ReadRecord to parse the record from the file archive.
  208.     return ReadRecord(OutRecord, *FileArchive, GameID, bSkipSubrecords);
  209. }
  210.  
  211. // Reads a record from a byte array, creating an FMemoryReader and delegating to ReadRecord.
  212. // Parameters: OutRecord (output record), ByteArray (input data), GameID (game-specific format), bSkipSubrecords (skip subrecords).
  213. // Returns true on success, false on failure (e.g., corrupt data).
  214. bool URecordHelper::ReadRecordFromBytes(FRecord& OutRecord, const TArray<uint8>& ByteArray, EGameID GameID, bool bSkipSubrecords)
  215. {
  216.     // Create a memory reader archive from the byte array, enabling persistent data access.
  217.     FMemoryReader MemoryArchive(ByteArray, true);
  218.     // Delegate to ReadRecord to parse the record from the memory archive.
  219.     return ReadRecord(OutRecord, MemoryArchive, GameID, bSkipSubrecords);
  220. }
  221.  
  222. // Writes a record to a file, serializing its fields and subrecords in a game-specific format.
  223. // Parameters: Record (input record), FilePath (output file), GameID (game-specific format).
  224. // Returns true on success, false on failure (e.g., invalid path, write error).
  225. bool URecordHelper::WriteRecordToFile(const FRecord& Record, const FString& FilePath, EGameID GameID)
  226. {
  227.     // Create a file writer archive for the specified file path.
  228.     TUniquePtr<FArchive> FileArchive(IFileManager::Get().CreateFileWriter(*FilePath));
  229.     if (!FileArchive) // Check if the file could be created/opened.
  230.     {
  231.         // Log a warning if the file cannot be created (e.g., permissions issue, invalid path).
  232.         UE_LOG(LogTemp, Warning, TEXT("Failed to create file: %s"), *FilePath);
  233.         return false;
  234.     }
  235.  
  236.     // Serialize the 4-byte record type (e.g., "NPC_").
  237.     TArray<uint8> TypeBytes;
  238.     TypeBytes.SetNumZeroed(4); // Allocate and zero-initialize 4 bytes.
  239.     for (int32 i = 0; i < 4 && i < Record.Type.Len(); ++i) // Copy up to 4 characters from Type.
  240.     {
  241.         TypeBytes[i] = static_cast<uint8>(Record.Type[i]); // Convert TCHAR to byte.
  242.     }
  243.     FileArchive->Serialize(TypeBytes.GetData(), 4); // Write the type bytes.
  244.     if (FileArchive->IsError()) // Check for write errors.
  245.     {
  246.         return false;
  247.     }
  248.  
  249.     // Calculate the total size of subrecords, including type, length field, and data.
  250.     int32 TotalSubrecordsSize = 0;
  251.     for (const FSubrecord& Subrecord : Record.Subrecords)
  252.     {
  253.         TotalSubrecordsSize += 4; // 4 bytes for subrecord type.
  254.         TotalSubrecordsSize += (GameID == EGameID::Morrowind) ? 4 : 2; // Length field size (4 for Morrowind, 2 for others).
  255.         TotalSubrecordsSize += Subrecord.RawData.Num(); // Size of subrecord data.
  256.     }
  257.     FileArchive->Serialize(&TotalSubrecordsSize, sizeof(int32)); // Write the total size.
  258.     if (FileArchive->IsError()) // Check for write errors.
  259.     {
  260.         return false;
  261.     }
  262.  
  263.     // Write 4 bytes of padding for Morrowind (unused field).
  264.     if (GameID == EGameID::Morrowind)
  265.     {
  266.         int32 Padding = 0;
  267.         FileArchive->Serialize(&Padding, sizeof(int32));
  268.         if (FileArchive->IsError()) // Check for write errors.
  269.         {
  270.             return false;
  271.         }
  272.     }
  273.  
  274.     // Serialize the record flags (32-bit integer).
  275.     FileArchive->Serialize(&const_cast<int32&>(Record.Flags), sizeof(int32));
  276.     if (FileArchive->IsError()) // Check for write errors.
  277.     {
  278.         return false;
  279.     }
  280.  
  281.     // Serialize FormID for non-Morrowind games.
  282.     if (GameID != EGameID::Morrowind)
  283.     {
  284.         // Access the FormID value (assumes FFormID has a public 'Value' field).
  285.         int32 FormIdValue = Record.FormId.Value;
  286.         FileArchive->Serialize(&FormIdValue, sizeof(int32)); // Write the FormID.
  287.         if (FileArchive->IsError()) // Check for write errors.
  288.         {
  289.             return false;
  290.         }
  291.  
  292.         // Write padding bytes: 4 for Oblivion, 8 for other non-Morrowind games.
  293.         int32 SkipLength = (GameID == EGameID::Oblivion) ? 4 : 8;
  294.         TArray<uint8> Padding;
  295.         Padding.SetNumZeroed(SkipLength); // Allocate and zero-initialize padding.
  296.         FileArchive->Serialize(Padding.GetData(), SkipLength); // Write padding.
  297.         if (FileArchive->IsError()) // Check for write errors.
  298.         {
  299.             return false;
  300.         }
  301.     }
  302.  
  303.     // Serialize each subrecord.
  304.     for (const FSubrecord& Subrecord : Record.Subrecords)
  305.     {
  306.         // Serialize the 4-byte subrecord type.
  307.         TArray<uint8> SubTypeBytes;
  308.         SubTypeBytes.SetNumZeroed(4); // Allocate and zero-initialize 4 bytes.
  309.         for (int32 i = 0; i < 4 && i < Subrecord.Type.Len(); ++i) // Copy up to 4 characters.
  310.         {
  311.             SubTypeBytes[i] = static_cast<uint8>(Subrecord.Type[i]); // Convert TCHAR to byte.
  312.         }
  313.         FileArchive->Serialize(SubTypeBytes.GetData(), 4); // Write the type bytes.
  314.         if (FileArchive->IsError()) // Check for write errors.
  315.         {
  316.             return false;
  317.         }
  318.  
  319.         // Serialize the subrecord data length (4 bytes for Morrowind, 2 bytes for others).
  320.         if (GameID == EGameID::Morrowind)
  321.         {
  322.             int32 DataLength = Subrecord.RawData.Num();
  323.             FileArchive->Serialize(&DataLength, sizeof(int32)); // Write 32-bit length.
  324.             if (FileArchive->IsError()) // Check for write errors.
  325.             {
  326.                 return false;
  327.             }
  328.         }
  329.         else
  330.         {
  331.             int16 DataLength = static_cast<int16>(Subrecord.RawData.Num()); // Cast to 16-bit for non-Morrowind.
  332.             FileArchive->Serialize(&DataLength, sizeof(int16)); // Write 16-bit length.
  333.             if (FileArchive->IsError()) // Check for write errors.
  334.             {
  335.                 return false;
  336.             }
  337.         }
  338.  
  339.         // Serialize the subrecord's raw data.
  340.         FileArchive->Serialize(const_cast<uint8*>(Subrecord.RawData.GetData()), Subrecord.RawData.Num());
  341.         if (FileArchive->IsError()) // Check for write errors.
  342.         {
  343.             return false;
  344.         }
  345.     }
  346.  
  347.     // Return true if all writes succeeded.
  348.     return true;
  349. }
  350.  
  351. // Serializes a record to a byte array, preserving its structure in a game-specific format.
  352. // Parameters: Record (input record), OutByteArray (output bytes), GameID (game-specific format).
  353. // Returns true on success, false on failure (e.g., serialization error).
  354. bool URecordHelper::WriteRecordToBytes(const FRecord& Record, TArray<uint8>& OutByteArray, EGameID GameID)
  355. {
  356.     // Create a memory writer archive to serialize data into OutByteArray.
  357.     FMemoryWriter MemoryArchive(OutByteArray, true);
  358.     OutByteArray.Empty(); // Clear the output array to ensure a fresh start.
  359.  
  360.     // Serialize the 4-byte record type (e.g., "NPC_").
  361.     TArray<uint8> TypeBytes;
  362.     TypeBytes.SetNumZeroed(4); // Allocate and zero-initialize 4 bytes.
  363.     for (int32 i = 0; i < 4 && i < Record.Type.Len(); ++i) // Copy up to 4 characters.
  364.     {
  365.         TypeBytes[i] = static_cast<uint8>(Record.Type[i]); // Convert TCHAR to byte.
  366.     }
  367.     MemoryArchive.Serialize(TypeBytes.GetData(), 4); // Write the type bytes.
  368.     if (MemoryArchive.IsError()) // Check for write errors.
  369.     {
  370.         return false;
  371.     }
  372.  
  373.     // Calculate the total size of subrecords.
  374.     int32 TotalSubrecordsSize = 0;
  375.     for (const FSubrecord& Subrecord : Record.Subrecords)
  376.     {
  377.         TotalSubrecordsSize += 4; // 4 bytes for subrecord type.
  378.         TotalSubrecordsSize += (GameID == EGameID::Morrowind) ? 4 : 2; // Length field size.
  379.         TotalSubrecordsSize += Subrecord.RawData.Num(); // Size of subrecord data.
  380.     }
  381.     MemoryArchive.Serialize(&TotalSubrecordsSize, sizeof(int32)); // Write the total size.
  382.     if (MemoryArchive.IsError()) // Check for write errors.
  383.     {
  384.         return false;
  385.     }
  386.  
  387.     // Write 4 bytes of padding for Morrowind.
  388.     if (GameID == EGameID::Morrowind)
  389.     {
  390.         int32 Padding = 0;
  391.         MemoryArchive.Serialize(&Padding, sizeof(int32));
  392.         if (MemoryArchive.IsError()) // Check for write errors.
  393.         {
  394.             return false;
  395.         }
  396.     }
  397.  
  398.     // Serialize the record flags.
  399.     MemoryArchive.Serialize(&const_cast<int32&>(Record.Flags), sizeof(int32));
  400.     if (MemoryArchive.IsError()) // Check for write errors.
  401.     {
  402.         return false;
  403.     }
  404.  
  405.     // Serialize FormID for non-Morrowind games.
  406.     if (GameID != EGameID::Morrowind)
  407.     {
  408.         // Access the FormID value (assumes FFormID has a public 'Value' field).
  409.         int32 FormIdValue = Record.FormId.Value;
  410.         MemoryArchive.Serialize(&FormIdValue, sizeof(int32)); // Write the FormID.
  411.         if (MemoryArchive.IsError()) // Check for write errors.
  412.         {
  413.             return false;
  414.         }
  415.  
  416.         // Write padding bytes: 4 for Oblivion, 8 for others.
  417.         int32 SkipLength = (GameID == EGameID::Oblivion) ? 4 : 8;
  418.         TArray<uint8> Padding;
  419.         Padding.SetNumZeroed(SkipLength); // Allocate and zero-initialize padding.
  420.         MemoryArchive.Serialize(Padding.GetData(), SkipLength); // Write padding.
  421.         if (MemoryArchive.IsError()) // Check for write errors.
  422.         {
  423.             return false;
  424.         }
  425.     }
  426.  
  427.     // Serialize each subrecord.
  428.     for (const FSubrecord& Subrecord : Record.Subrecords)
  429.     {
  430.         // Serialize the 4-byte subrecord type.
  431.         TArray<uint8> SubTypeBytes;
  432.         SubTypeBytes.SetNumZeroed(4); // Allocate and zero-initialize 4 bytes.
  433.         for (int32 i = 0; i < 4 && i < Subrecord.Type.Len(); ++i)
  434.         {
  435.             SubTypeBytes[i] = static_cast<uint8>(Subrecord.Type[i]); // Convert TCHAR to byte.
  436.         }
  437.         MemoryArchive.Serialize(SubTypeBytes.GetData(), 4); // Write the type bytes.
  438.         if (MemoryArchive.IsError()) // Check for write errors.
  439.         {
  440.             return false;
  441.         }
  442.  
  443.         // Serialize the subrecord data length.
  444.         if (GameID == EGameID::Morrowind)
  445.         {
  446.             int32 DataLength = Subrecord.RawData.Num();
  447.             MemoryArchive.Serialize(&DataLength, sizeof(int32)); // Write 32-bit length.
  448.             if (MemoryArchive.IsError()) // Check for write errors.
  449.             {
  450.                 return false;
  451.             }
  452.         }
  453.         else
  454.         {
  455.             int16 DataLength = static_cast<int16>(Subrecord.RawData.Num()); // Cast to 16-bit.
  456.             MemoryArchive.Serialize(&DataLength, sizeof(int16)); // Write 16-bit length.
  457.             if (MemoryArchive.IsError()) // Check for write errors.
  458.             {
  459.                 return false;
  460.             }
  461.         }
  462.  
  463.         // Serialize the subrecord's raw data.
  464.         MemoryArchive.Serialize(const_cast<uint8*>(Subrecord.RawData.GetData()), Subrecord.RawData.Num());
  465.         if (MemoryArchive.IsError()) // Check for write errors.
  466.         {
  467.             return false;
  468.         }
  469.     }
  470.  
  471.     // Return true if all writes succeeded.
  472.     return true;
  473. }
  474.  
  475. // Returns the record's 4-byte type identifier (e.g., "NPC_") as an FString.
  476. // Simple accessor for Blueprint and C++ use, with no side effects.
  477. FString URecordHelper::GetType(const FRecord& Record)
  478. {
  479.     return Record.Type;
  480. }
  481.  
  482. // Returns the record's flags as a 32-bit integer, indicating properties like compression.
  483. // Simple accessor for querying record metadata.
  484. int32 URecordHelper::GetFlags(const FRecord& Record)
  485. {
  486.     return Record.Flags;
  487. }
  488.  
  489. // Returns the record's FormID, uniquely identifying it in non-Morrowind games.
  490. // Simple accessor for accessing the FormID field.
  491. FFormID URecordHelper::GetFormId(const FRecord& Record)
  492. {
  493.     return Record.FormId;
  494. }
  495.  
  496. // Returns the array of subrecords stored in the record, providing access to detailed data.
  497. // Simple accessor for Blueprint and C++ use, returning a copy of the subrecords array.
  498. TArray<FSubrecord> URecordHelper::GetSubrecords(const FRecord& Record)
  499. {
  500.     return Record.Subrecords;
  501. }
  502.  
  503. // Checks if the record is compressed by examining its flags (bit 0x00040000).
  504. // Returns true if compressed, false otherwise, used to determine parsing behavior.
  505. bool URecordHelper::AreFieldsCompressed(const FRecord& Record)
  506. {
  507.     return (Record.Flags & 0x00040000) != 0;
  508. }
  509.  
  510. // Retrieves the first subrecord matching the specified SubrecordType (e.g., "NAME").
  511. // Parameters: Record (input record), SubrecordType (type to find), OutSubrecord (output subrecord).
  512. // Returns true if found, false otherwise, populating OutSubrecord on success.
  513. bool URecordHelper::GetSubrecordByType(const FRecord& Record, const FString& SubrecordType, FSubrecord& OutSubrecord)
  514. {
  515.     // Iterate through subrecords to find a matching type (case-sensitive).
  516.     for (const FSubrecord& Subrecord : Record.Subrecords)
  517.     {
  518.         if (Subrecord.Type.Equals(SubrecordType, ESearchCase::CaseSensitive))
  519.         {
  520.             OutSubrecord = Subrecord; // Assign the found subrecord.
  521.             return true;
  522.         }
  523.     }
  524.     return false; // Return false if no matching subrecord is found.
  525. }
  526.  
  527. // Checks if a subrecord with the specified SubrecordType exists in the record.
  528. // Returns true if found, false otherwise, useful for conditional logic.
  529. bool URecordHelper::HasSubrecord(const FRecord& Record, const FString& SubrecordType)
  530. {
  531.     // Iterate through subrecords to check for a matching type (case-sensitive).
  532.     for (const FSubrecord& Subrecord : Record.Subrecords)
  533.     {
  534.         if (Subrecord.Type.Equals(SubrecordType, ESearchCase::CaseSensitive))
  535.         {
  536.             return true;
  537.         }
  538.     }
  539.     return false;
  540. }
  541.  
  542. // Returns the number of subrecords in the record, providing a count for iteration or validation.
  543. // Simple accessor returning the size of the Subrecords array.
  544. int32 URecordHelper::GetSubrecordCount(const FRecord& Record)
  545. {
  546.     return Record.Subrecords.Num();
  547. }
  548.  
  549. // Validates the record by checking for a non-empty Type and a valid FormID (non-zero for non-Morrowind).
  550. // Parameters: Record (input record), GameID (game-specific context).
  551. // Returns true if valid, false otherwise, ensuring data integrity.
  552. bool URecordHelper::IsValidRecord(const FRecord& Record, EGameID GameID)
  553. {
  554.     // Check if the record type is empty (invalid record).
  555.     if (Record.Type.IsEmpty())
  556.     {
  557.         return false;
  558.     }
  559.     // For non-Morrowind games, ensure FormID is non-zero (assumes FFormID has a 'Value' field).
  560.     if (GameID != EGameID::Morrowind && Record.FormId.Value == 0)
  561.     {
  562.         return false;
  563.     }
  564.     return true;
  565. }
  566.  
  567. // Clears all subrecords from the record, resetting the Subrecords array to empty.
  568. // Parameters: Record (modified record).
  569. // Used to reset a record's subrecord data, e.g., before repopulating.
  570. void URecordHelper::ClearSubrecords(FRecord& Record)
  571. {
  572.     Record.Subrecords.Empty();
  573. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement