malice936

Plugin.cpp

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