Advertisement
malice936

Plugin.cpp

Apr 13th, 2025
255
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 10.93 KB | Gaming | 0 0
  1. // Plugin.cpp
  2. // Core plugin functionality for loading and querying Bethesda plugin files
  3.  
  4. #include "Plugin.h"             // FPlugin struct and UPluginHelper declarations
  5. #include "Record.h"             // FRecord struct for header records
  6. #include "Group.h"              // FGroup struct for group data
  7. #include "Subrecord.h"          // FSubrecord struct for subrecord data
  8. #include "FormIDHelper.h"       // Helper functions for FormID operations
  9. #include "HAL/FileManager.h"    // File I/O operations
  10. #include "Misc/Paths.h"         // Path manipulation utilities
  11. #include "Serialization/MemoryReader.h" // Memory-based archive reading
  12.  
  13. // Forward declaration for GetRemainingBytes to ensure visibility
  14. static TArray<uint8> GetRemainingBytes(FArchive& Archive);
  15.  
  16. // Helper function to read remaining bytes from an archive
  17. // Returns a byte array of unprocessed data, rewinding the archive to its original position
  18. static TArray<uint8> GetRemainingBytes(FArchive& Archive)
  19. {
  20.     TArray<uint8> Bytes; // Buffer for remaining bytes
  21.     int64 CurrentPos = Archive.Tell(); // Current archive position
  22.     int64 TotalSize = Archive.TotalSize(); // Total size of archive
  23.     int64 Remaining = TotalSize - CurrentPos; // Bytes left to read
  24.     if (Remaining > 0)
  25.     {
  26.         Bytes.SetNumUninitialized(Remaining); // Allocate buffer
  27.         Archive.Serialize(Bytes.GetData(), Remaining); // Read bytes
  28.         Archive.Seek(CurrentPos); // Rewind to original position
  29.     }
  30.     return Bytes; // Return byte array
  31. }
  32.  
  33. // Loads a plugin from a file, populating OutPlugin with metadata and FormIDs
  34. bool UPluginHelper::LoadPlugin(FPlugin& OutPlugin, const FString& FilePath, EGameID GameID, bool bLoadHeaderOnly)
  35. {
  36.     // Open file for reading
  37.     TUniquePtr<FArchive> FileArchive(IFileManager::Get().CreateFileReader(*FilePath));
  38.     if (!FileArchive)
  39.     {
  40.         // Log failure if file cannot be opened
  41.         UE_LOG(LogTemp, Warning, TEXT("Failed to open file: %s"), *FilePath);
  42.         return false;
  43.     }
  44.  
  45.     // Initialize plugin metadata
  46.     OutPlugin.GameId = GameID; // Set game identifier
  47.     OutPlugin.Name = FPaths::GetBaseFilename(FilePath); // Extract filename
  48.     OutPlugin.FormIds.Empty(); // Clear FormIDs
  49.  
  50.     // Read header record
  51.     if (!URecordHelper::ReadRecord(OutPlugin.HeaderRecord, *FileArchive, GameID, false))
  52.     {
  53.         // Log failure if header cannot be read
  54.         UE_LOG(LogTemp, Warning, TEXT("Failed to read header for %s"), *FilePath);
  55.         return false;
  56.     }
  57.  
  58.     // Validate header type (TES3 for Morrowind, TES4 for others)
  59.     FString ExpectedType = GameID == EGameID::Morrowind ? TEXT("TES3") : TEXT("TES4");
  60.     if (OutPlugin.HeaderRecord.Type != ExpectedType)
  61.     {
  62.         // Log invalid plugin type
  63.         UE_LOG(LogTemp, Warning, TEXT("%s is not a valid plugin: Expected %s, got %s"), *OutPlugin.Name, *ExpectedType, *OutPlugin.HeaderRecord.Type);
  64.         return false;
  65.     }
  66.  
  67.     // Return early if only header is needed
  68.     if (bLoadHeaderOnly)
  69.     {
  70.         return true;
  71.     }
  72.  
  73.     // Get file size for loop bounds
  74.     int64 FileSize = FileArchive->TotalSize();
  75.     int64 CurrentPos = FileArchive->Tell();
  76.  
  77.     // Ensure plugin name has .esp/.esm extension for consistency
  78.     FString TrimmedName = OutPlugin.Name;
  79.     if (!TrimmedName.EndsWith(TEXT(".esp"), ESearchCase::IgnoreCase) && !TrimmedName.EndsWith(TEXT(".esm"), ESearchCase::IgnoreCase))
  80.     {
  81.         TrimmedName += TEXT(".esp");
  82.     }
  83.     TArray<FString> Masters = GetMasters(OutPlugin); // Get master dependencies
  84.  
  85.     if (GameID == EGameID::Morrowind)
  86.     {
  87.         // Read records directly for Morrowind plugins
  88.         while (!FileArchive->IsError() && CurrentPos < FileSize)
  89.         {
  90.             FRecord Record;
  91.             if (!URecordHelper::ReadRecord(Record, *FileArchive, GameID, false))
  92.             {
  93.                 break; // Stop on error or end-of-file
  94.             }
  95.             OutPlugin.FormIds.Add(Record.FormId); // Add record's FormID
  96.             CurrentPos = FileArchive->Tell(); // Update position
  97.         }
  98.     }
  99.     else
  100.     {
  101.         // Read groups for non-Morrowind plugins
  102.         while (!FileArchive->IsError() && CurrentPos < FileSize)
  103.         {
  104.             FGroup Group;
  105.             if (!UGroupHelper::ReadGroupFromBytes(Group, GetRemainingBytes(*FileArchive), GameID, true))
  106.             {
  107.                 break; // Stop on error or end-of-file
  108.             }
  109.             // Add all FormIDs from the group
  110.             for (const FFormID& FormId : Group.FormIds)
  111.             {
  112.                 OutPlugin.FormIds.Add(FormId);
  113.             }
  114.             CurrentPos = FileArchive->Tell(); // Update position
  115.         }
  116.     }
  117.  
  118.     // Return true if no archive errors occurred
  119.     return !FileArchive->IsError();
  120. }
  121.  
  122. // Loads a plugin from a byte array, populating OutPlugin with metadata and FormIDs
  123. bool UPluginHelper::LoadPluginFromBytes(FPlugin& OutPlugin, const TArray<uint8>& ByteArray, EGameID GameID, bool bLoadHeaderOnly)
  124. {
  125.     // Create memory reader for byte array
  126.     FMemoryReader MemoryArchive(ByteArray, true);
  127.     OutPlugin.GameId = GameID; // Set game identifier
  128.     OutPlugin.Name = TEXT("MemoryPlugin"); // Placeholder name
  129.     OutPlugin.FormIds.Empty(); // Clear FormIDs
  130.  
  131.     // Read header record
  132.     if (!URecordHelper::ReadRecord(OutPlugin.HeaderRecord, MemoryArchive, GameID, false))
  133.     {
  134.         // Log failure if header cannot be read
  135.         UE_LOG(LogTemp, Warning, TEXT("Failed to read header from byte array"));
  136.         return false;
  137.     }
  138.  
  139.     // Validate header type (TES3 for Morrowind, TES4 for others)
  140.     FString ExpectedType = GameID == EGameID::Morrowind ? TEXT("TES3") : TEXT("TES4");
  141.     if (OutPlugin.HeaderRecord.Type != ExpectedType)
  142.     {
  143.         // Log invalid plugin type
  144.         UE_LOG(LogTemp, Warning, TEXT("Invalid plugin: Expected %s, got %s"), *ExpectedType, *OutPlugin.HeaderRecord.Type);
  145.         return false;
  146.     }
  147.  
  148.     // Return early if only header is needed
  149.     if (bLoadHeaderOnly)
  150.     {
  151.         return true;
  152.     }
  153.  
  154.     // Ensure plugin name for context
  155.     FString TrimmedName = OutPlugin.Name;
  156.     TArray<FString> Masters = GetMasters(OutPlugin); // Get master dependencies
  157.  
  158.     if (GameID == EGameID::Morrowind)
  159.     {
  160.         // Read records directly for Morrowind plugins
  161.         while (!MemoryArchive.IsError())
  162.         {
  163.             FRecord Record;
  164.             if (!URecordHelper::ReadRecord(Record, MemoryArchive, GameID, false))
  165.             {
  166.                 break; // Stop on error or end-of-data
  167.             }
  168.             OutPlugin.FormIds.Add(Record.FormId); // Add record's FormID
  169.         }
  170.     }
  171.     else
  172.     {
  173.         // Read groups for non-Morrowind plugins
  174.         while (!MemoryArchive.IsError())
  175.         {
  176.             FGroup Group;
  177.             if (!UGroupHelper::ReadGroupFromBytes(Group, GetRemainingBytes(MemoryArchive), GameID, true))
  178.             {
  179.                 break; // Stop on error or end-of-data
  180.             }
  181.             // Add all FormIDs from the group
  182.             for (const FFormID& FormId : Group.FormIds)
  183.             {
  184.                 OutPlugin.FormIds.Add(FormId);
  185.             }
  186.         }
  187.     }
  188.  
  189.     // Return true if no archive errors occurred
  190.     return !MemoryArchive.IsError();
  191. }
  192.  
  193. // Checks if a plugin file is valid by attempting to load it
  194. bool UPluginHelper::IsValidPlugin(const FString& FilePath, EGameID GameID, bool bLoadHeaderOnly)
  195. {
  196.     FPlugin Plugin; // Temporary plugin for validation
  197.     return LoadPlugin(Plugin, FilePath, GameID, bLoadHeaderOnly); // Return load result
  198. }
  199.  
  200. // Gets the plugin's filename
  201. FString UPluginHelper::GetName(const FPlugin& Plugin)
  202. {
  203.     return Plugin.Name; // Return stored name
  204. }
  205.  
  206. // Checks if the plugin is a master file (.esm or flagged)
  207. bool UPluginHelper::IsMasterFile(const FPlugin& Plugin)
  208. {
  209.     if (Plugin.GameId == EGameID::Morrowind)
  210.     {
  211.         // Morrowind: Check for .esm extension
  212.         return Plugin.Name.EndsWith(TEXT(".esm"), ESearchCase::IgnoreCase);
  213.     }
  214.     // Others: Check master flag in header
  215.     return (Plugin.HeaderRecord.Flags & 0x00000001) != 0;
  216. }
  217.  
  218. // Gets the list of master plugin filenames from MAST subrecords
  219. TArray<FString> UPluginHelper::GetMasters(const FPlugin& Plugin)
  220. {
  221.     TArray<FString> Masters; // List of master names
  222.     for (const FSubrecord& Subrecord : Plugin.HeaderRecord.Subrecords)
  223.     {
  224.         if (Subrecord.Type == TEXT("MAST"))
  225.         {
  226.             // Convert raw data to FString (assume null-terminated)
  227.             FString MasterName = FString(UTF8_TO_TCHAR(Subrecord.RawData.GetData()));
  228.             Masters.Add(MasterName); // Add master name
  229.         }
  230.     }
  231.     return Masters; // Return master list
  232. }
  233.  
  234. // Gets the plugin description from HEDR (Morrowind) or SNAM (others)
  235. FString UPluginHelper::GetDescription(const FPlugin& Plugin)
  236. {
  237.     // Set subrecord type and offset based on game
  238.     FString Type = Plugin.GameId == EGameID::Morrowind ? TEXT("HEDR") : TEXT("SNAM");
  239.     int32 Offset = Plugin.GameId == EGameID::Morrowind ? 300 : 0;
  240.     for (const FSubrecord& Subrecord : Plugin.HeaderRecord.Subrecords)
  241.     {
  242.         if (Subrecord.Type == Type && Subrecord.RawData.Num() > Offset)
  243.         {
  244.             // Convert raw data to FString, skipping offset
  245.             return FString(UTF8_TO_TCHAR(Subrecord.RawData.GetData() + Offset));
  246.         }
  247.     }
  248.     return TEXT(""); // Return empty string if no description found
  249. }
  250.  
  251. // Gets the unique FormIDs (non-Morrowind plugins only)
  252. TArray<FFormID> UPluginHelper::GetFormIds(const FPlugin& Plugin)
  253. {
  254.     if (Plugin.GameId == EGameID::Morrowind)
  255.     {
  256.         // Morrowind plugins don't use FormIDs
  257.         UE_LOG(LogTemp, Warning, TEXT("Morrowind plugins lack FormIDs."));
  258.         return TArray<FFormID>(); // Return empty array
  259.     }
  260.     return Plugin.FormIds.Array(); // Convert TSet to TArray
  261. }
  262.  
  263. // Gets the total number of records and groups from HEDR subrecord
  264. int32 UPluginHelper::GetRecordAndGroupCount(const FPlugin& Plugin)
  265. {
  266.     // Set offset based on game (296 for Morrowind, 4 for others)
  267.     int32 Offset = Plugin.GameId == EGameID::Morrowind ? 296 : 4;
  268.     for (const FSubrecord& Subrecord : Plugin.HeaderRecord.Subrecords)
  269.     {
  270.         if (Subrecord.Type == TEXT("HEDR") && Subrecord.RawData.Num() >= Offset + sizeof(int32))
  271.         {
  272.             // Read count as int32 from raw data at offset
  273.             return *reinterpret_cast<const int32*>(Subrecord.RawData.GetData() + Offset);
  274.         }
  275.     }
  276.     return 0; // Return 0 if no count found
  277. }
  278.  
  279. // Checks if a specific master plugin is listed as a dependency
  280. bool UPluginHelper::HasMaster(const FPlugin& Plugin, const FString& MasterName)
  281. {
  282.     TArray<FString> Masters = GetMasters(Plugin); // Get master list
  283.     return Masters.Contains(MasterName); // Check for master name
  284. }
  285.  
  286. // Gets the number of master plugins
  287. int32 UPluginHelper::GetMasterCount(const FPlugin& Plugin)
  288. {
  289.     return GetMasters(Plugin).Num(); // Return count of masters
  290. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement