Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Plugin.cpp
- // Core plugin functionality for loading and querying Bethesda plugin files
- #include "Plugin.h" // FPlugin struct and UPluginHelper declarations
- #include "Record.h" // FRecord struct for header records
- #include "Group.h" // FGroup struct for group data
- #include "Subrecord.h" // FSubrecord struct for subrecord data
- #include "FormIDHelper.h" // Helper functions for FormID operations
- #include "HAL/FileManager.h" // File I/O operations
- #include "Misc/Paths.h" // Path manipulation utilities
- #include "Serialization/MemoryReader.h" // Memory-based archive reading
- // Forward declaration for GetRemainingBytes to ensure visibility
- static TArray<uint8> GetRemainingBytes(FArchive& Archive);
- // Helper function to read remaining bytes from an archive
- // Returns a byte array of unprocessed data, rewinding the archive to its original position
- static TArray<uint8> GetRemainingBytes(FArchive& Archive)
- {
- TArray<uint8> Bytes; // Buffer for remaining bytes
- int64 CurrentPos = Archive.Tell(); // Current archive position
- int64 TotalSize = Archive.TotalSize(); // Total size of archive
- int64 Remaining = TotalSize - CurrentPos; // Bytes left to read
- if (Remaining > 0)
- {
- Bytes.SetNumUninitialized(Remaining); // Allocate buffer
- Archive.Serialize(Bytes.GetData(), Remaining); // Read bytes
- Archive.Seek(CurrentPos); // Rewind to original position
- }
- return Bytes; // Return byte array
- }
- // Loads a plugin from a file, populating OutPlugin with metadata and FormIDs
- bool UPluginHelper::LoadPlugin(FPlugin& OutPlugin, const FString& FilePath, EGameID GameID, bool bLoadHeaderOnly)
- {
- // Open file for reading
- TUniquePtr<FArchive> FileArchive(IFileManager::Get().CreateFileReader(*FilePath));
- if (!FileArchive)
- {
- // Log failure if file cannot be opened
- UE_LOG(LogTemp, Warning, TEXT("Failed to open file: %s"), *FilePath);
- return false;
- }
- // Initialize plugin metadata
- OutPlugin.GameId = GameID; // Set game identifier
- OutPlugin.Name = FPaths::GetBaseFilename(FilePath); // Extract filename
- OutPlugin.FormIds.Empty(); // Clear FormIDs
- // Read header record
- if (!URecordHelper::ReadRecord(OutPlugin.HeaderRecord, *FileArchive, GameID, false))
- {
- // Log failure if header cannot be read
- UE_LOG(LogTemp, Warning, TEXT("Failed to read header for %s"), *FilePath);
- return false;
- }
- // Validate header type (TES3 for Morrowind, TES4 for others)
- FString ExpectedType = GameID == EGameID::Morrowind ? TEXT("TES3") : TEXT("TES4");
- if (OutPlugin.HeaderRecord.Type != ExpectedType)
- {
- // Log invalid plugin type
- UE_LOG(LogTemp, Warning, TEXT("%s is not a valid plugin: Expected %s, got %s"), *OutPlugin.Name, *ExpectedType, *OutPlugin.HeaderRecord.Type);
- return false;
- }
- // Return early if only header is needed
- if (bLoadHeaderOnly)
- {
- return true;
- }
- // Get file size for loop bounds
- int64 FileSize = FileArchive->TotalSize();
- int64 CurrentPos = FileArchive->Tell();
- // Ensure plugin name has .esp/.esm extension for consistency
- FString TrimmedName = OutPlugin.Name;
- if (!TrimmedName.EndsWith(TEXT(".esp"), ESearchCase::IgnoreCase) && !TrimmedName.EndsWith(TEXT(".esm"), ESearchCase::IgnoreCase))
- {
- TrimmedName += TEXT(".esp");
- }
- TArray<FString> Masters = GetMasters(OutPlugin); // Get master dependencies
- if (GameID == EGameID::Morrowind)
- {
- // Read records directly for Morrowind plugins
- while (!FileArchive->IsError() && CurrentPos < FileSize)
- {
- FRecord Record;
- if (!URecordHelper::ReadRecord(Record, *FileArchive, GameID, false))
- {
- break; // Stop on error or end-of-file
- }
- OutPlugin.FormIds.Add(Record.FormId); // Add record's FormID
- CurrentPos = FileArchive->Tell(); // Update position
- }
- }
- else
- {
- // Read groups for non-Morrowind plugins
- while (!FileArchive->IsError() && CurrentPos < FileSize)
- {
- FGroup Group;
- if (!UGroupHelper::ReadGroupFromBytes(Group, GetRemainingBytes(*FileArchive), GameID, true))
- {
- break; // Stop on error or end-of-file
- }
- // Add all FormIDs from the group
- for (const FFormID& FormId : Group.FormIds)
- {
- OutPlugin.FormIds.Add(FormId);
- }
- CurrentPos = FileArchive->Tell(); // Update position
- }
- }
- // Return true if no archive errors occurred
- return !FileArchive->IsError();
- }
- // Loads a plugin from a byte array, populating OutPlugin with metadata and FormIDs
- bool UPluginHelper::LoadPluginFromBytes(FPlugin& OutPlugin, const TArray<uint8>& ByteArray, EGameID GameID, bool bLoadHeaderOnly)
- {
- // Create memory reader for byte array
- FMemoryReader MemoryArchive(ByteArray, true);
- OutPlugin.GameId = GameID; // Set game identifier
- OutPlugin.Name = TEXT("MemoryPlugin"); // Placeholder name
- OutPlugin.FormIds.Empty(); // Clear FormIDs
- // Read header record
- if (!URecordHelper::ReadRecord(OutPlugin.HeaderRecord, MemoryArchive, GameID, false))
- {
- // Log failure if header cannot be read
- UE_LOG(LogTemp, Warning, TEXT("Failed to read header from byte array"));
- return false;
- }
- // Validate header type (TES3 for Morrowind, TES4 for others)
- FString ExpectedType = GameID == EGameID::Morrowind ? TEXT("TES3") : TEXT("TES4");
- if (OutPlugin.HeaderRecord.Type != ExpectedType)
- {
- // Log invalid plugin type
- UE_LOG(LogTemp, Warning, TEXT("Invalid plugin: Expected %s, got %s"), *ExpectedType, *OutPlugin.HeaderRecord.Type);
- return false;
- }
- // Return early if only header is needed
- if (bLoadHeaderOnly)
- {
- return true;
- }
- // Ensure plugin name for context
- FString TrimmedName = OutPlugin.Name;
- TArray<FString> Masters = GetMasters(OutPlugin); // Get master dependencies
- if (GameID == EGameID::Morrowind)
- {
- // Read records directly for Morrowind plugins
- while (!MemoryArchive.IsError())
- {
- FRecord Record;
- if (!URecordHelper::ReadRecord(Record, MemoryArchive, GameID, false))
- {
- break; // Stop on error or end-of-data
- }
- OutPlugin.FormIds.Add(Record.FormId); // Add record's FormID
- }
- }
- else
- {
- // Read groups for non-Morrowind plugins
- while (!MemoryArchive.IsError())
- {
- FGroup Group;
- if (!UGroupHelper::ReadGroupFromBytes(Group, GetRemainingBytes(MemoryArchive), GameID, true))
- {
- break; // Stop on error or end-of-data
- }
- // Add all FormIDs from the group
- for (const FFormID& FormId : Group.FormIds)
- {
- OutPlugin.FormIds.Add(FormId);
- }
- }
- }
- // Return true if no archive errors occurred
- return !MemoryArchive.IsError();
- }
- // Checks if a plugin file is valid by attempting to load it
- bool UPluginHelper::IsValidPlugin(const FString& FilePath, EGameID GameID, bool bLoadHeaderOnly)
- {
- FPlugin Plugin; // Temporary plugin for validation
- return LoadPlugin(Plugin, FilePath, GameID, bLoadHeaderOnly); // Return load result
- }
- // Gets the plugin's filename
- FString UPluginHelper::GetName(const FPlugin& Plugin)
- {
- return Plugin.Name; // Return stored name
- }
- // Checks if the plugin is a master file (.esm or flagged)
- bool UPluginHelper::IsMasterFile(const FPlugin& Plugin)
- {
- if (Plugin.GameId == EGameID::Morrowind)
- {
- // Morrowind: Check for .esm extension
- return Plugin.Name.EndsWith(TEXT(".esm"), ESearchCase::IgnoreCase);
- }
- // Others: Check master flag in header
- return (Plugin.HeaderRecord.Flags & 0x00000001) != 0;
- }
- // Gets the list of master plugin filenames from MAST subrecords
- TArray<FString> UPluginHelper::GetMasters(const FPlugin& Plugin)
- {
- TArray<FString> Masters; // List of master names
- for (const FSubrecord& Subrecord : Plugin.HeaderRecord.Subrecords)
- {
- if (Subrecord.Type == TEXT("MAST"))
- {
- // Convert raw data to FString (assume null-terminated)
- FString MasterName = FString(UTF8_TO_TCHAR(Subrecord.RawData.GetData()));
- Masters.Add(MasterName); // Add master name
- }
- }
- return Masters; // Return master list
- }
- // Gets the plugin description from HEDR (Morrowind) or SNAM (others)
- FString UPluginHelper::GetDescription(const FPlugin& Plugin)
- {
- // Set subrecord type and offset based on game
- FString Type = Plugin.GameId == EGameID::Morrowind ? TEXT("HEDR") : TEXT("SNAM");
- int32 Offset = Plugin.GameId == EGameID::Morrowind ? 300 : 0;
- for (const FSubrecord& Subrecord : Plugin.HeaderRecord.Subrecords)
- {
- if (Subrecord.Type == Type && Subrecord.RawData.Num() > Offset)
- {
- // Convert raw data to FString, skipping offset
- return FString(UTF8_TO_TCHAR(Subrecord.RawData.GetData() + Offset));
- }
- }
- return TEXT(""); // Return empty string if no description found
- }
- // Gets the unique FormIDs (non-Morrowind plugins only)
- TArray<FFormID> UPluginHelper::GetFormIds(const FPlugin& Plugin)
- {
- if (Plugin.GameId == EGameID::Morrowind)
- {
- // Morrowind plugins don't use FormIDs
- UE_LOG(LogTemp, Warning, TEXT("Morrowind plugins lack FormIDs."));
- return TArray<FFormID>(); // Return empty array
- }
- return Plugin.FormIds.Array(); // Convert TSet to TArray
- }
- // Gets the total number of records and groups from HEDR subrecord
- int32 UPluginHelper::GetRecordAndGroupCount(const FPlugin& Plugin)
- {
- // Set offset based on game (296 for Morrowind, 4 for others)
- int32 Offset = Plugin.GameId == EGameID::Morrowind ? 296 : 4;
- for (const FSubrecord& Subrecord : Plugin.HeaderRecord.Subrecords)
- {
- if (Subrecord.Type == TEXT("HEDR") && Subrecord.RawData.Num() >= Offset + sizeof(int32))
- {
- // Read count as int32 from raw data at offset
- return *reinterpret_cast<const int32*>(Subrecord.RawData.GetData() + Offset);
- }
- }
- return 0; // Return 0 if no count found
- }
- // Checks if a specific master plugin is listed as a dependency
- bool UPluginHelper::HasMaster(const FPlugin& Plugin, const FString& MasterName)
- {
- TArray<FString> Masters = GetMasters(Plugin); // Get master list
- return Masters.Contains(MasterName); // Check for master name
- }
- // Gets the number of master plugins
- int32 UPluginHelper::GetMasterCount(const FPlugin& Plugin)
- {
- return GetMasters(Plugin).Num(); // Return count of masters
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement