559 lines
17 KiB
Ucode
559 lines
17 KiB
Ucode
/**
|
|
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
|
|
*/
|
|
|
|
/**
|
|
* This class reads a set of files from Live/NP servers and uses it to
|
|
* update the game.
|
|
*/
|
|
class IniLocPatcher extends Object
|
|
native
|
|
config(Engine);
|
|
|
|
/** Holds the list of files to download and their download state */
|
|
struct native IniLocFileEntry
|
|
{
|
|
/** The file to read from the online service */
|
|
var string Filename;
|
|
/** Unique name of file only used for downloading */
|
|
var string DLName;
|
|
/** Hash to verify integrity of file against cached version */
|
|
var string HashCode;
|
|
/** Whether the file should be treated as unicode or not */
|
|
var bool bIsUnicode;
|
|
/** The state of that read */
|
|
var EOnlineEnumerationReadState ReadState;
|
|
};
|
|
|
|
/** The list of files to request from the online service */
|
|
var config array<IniLocFileEntry> Files;
|
|
|
|
/** if TRUE Then file list is downloaded from EMS otherwise it comes from config */
|
|
var config bool bRequestEmsFileList;
|
|
/** Max age in seconds to keep cached EMS files */
|
|
var config int MaxCachedFileAge;
|
|
|
|
/** Interface for downloading files */
|
|
var transient OnlineTitleFileInterface TitleFileInterface;
|
|
/** Interface for caching files to/from disk */
|
|
var transient OnlineTitleFileCacheInterface TitleFileCacheInterface;
|
|
|
|
/** list of delegates that get called whenever a file is downloaded or loaded from cache */
|
|
var array<delegate<OnReadTitleFileComplete> > ReadTitleFileCompleteDelegates;
|
|
// @ZOMBIE_FOXTROT_BEGIN - ccooper 1/9/2014 - Adding delegate container for OnAllTitleFilesCompleted
|
|
var array<delegate<OnAllTitleFilesCompleted> > AllTitleFilesCompletedDelegates;
|
|
// @ZOMBIE_FOXTROT_END
|
|
/**
|
|
* Delegate fired when a file read from the network platform's title specific storage is complete
|
|
*
|
|
* @param bWasSuccessful whether the file read was successful or not
|
|
* @param FileName the name of the file this was for
|
|
*/
|
|
delegate OnReadTitleFileComplete(bool bWasSuccessful,string FileName);
|
|
|
|
/**
|
|
* Delegate fired when all title files have completed processing
|
|
*/
|
|
delegate OnAllTitleFilesCompleted();
|
|
|
|
/**
|
|
* Initializes the patcher, sets delegates, vars, etc.
|
|
*/
|
|
function Init()
|
|
{
|
|
local OnlineSubsystem OnlineSub;
|
|
local int Index;
|
|
|
|
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
|
|
if (OnlineSub != None)
|
|
{
|
|
TitleFileInterface = OnlineSub.TitleFileInterface;
|
|
if (TitleFileInterface != None)
|
|
{
|
|
// Set the callback for notifications of files completing download
|
|
TitleFileInterface.AddReadTitleFileCompleteDelegate(OnDownloadFileComplete);
|
|
}
|
|
else
|
|
{
|
|
// Mark all as failed to be read since there is no way to read them
|
|
for (Index = 0; Index < Files.Length; Index++)
|
|
{
|
|
Files[Index].ReadState = OERS_Failed;
|
|
}
|
|
}
|
|
// Hook up the caching interface
|
|
TitleFileCacheInterface = OnlineSub.TitleFileCacheInterface;
|
|
if (TitleFileCacheInterface != None)
|
|
{
|
|
// Set the callback for notifications of files completing a load
|
|
TitleFileCacheInterface.AddLoadTitleFileCompleteDelegate(OnFileCacheLoadComplete);
|
|
TitleFileCacheInterface.AddSaveTitleFileCompleteDelegate(OnFileCacheSaveComplete);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the set of files from the online service
|
|
*/
|
|
function DownloadFiles()
|
|
{
|
|
local int FileIdx;
|
|
|
|
if (bRequestEmsFileList)
|
|
{
|
|
// Delete stale files
|
|
if (MaxCachedFileAge > 0 &&
|
|
TitleFileCacheInterface != None)
|
|
{
|
|
TitleFileCacheInterface.DeleteTitleFiles(MaxCachedFileAge);
|
|
}
|
|
// Get a list of files that should be downloaded
|
|
TitleFileInterface.AddRequestTitleFileListCompleteDelegate(OnRequestTitleFileListComplete);
|
|
TitleFileInterface.RequestTitleFileList();
|
|
}
|
|
else
|
|
{
|
|
// config DLNames and Filenames are expected to match unless requested
|
|
for (FileIdx = 0; FileIdx < Files.Length; FileIdx++)
|
|
{
|
|
Files[FileIdx].DLName = Files[FileIdx].Filename;
|
|
}
|
|
StartLoadingFiles();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delegate fired when the request for a list of files completes
|
|
*
|
|
* @param bWasSuccessful whether the request completed successfully
|
|
* @param ResultStr contains the list of files and associated meta data
|
|
*/
|
|
function OnRequestTitleFileListComplete(bool bWasSuccessful, array<string> ResultStr)
|
|
{
|
|
local JsonObject Root;
|
|
local int JsonObjectIdx;
|
|
local IniLocFileEntry RequestFileEntry;
|
|
local int Index;
|
|
|
|
TitleFileInterface.ClearRequestTitleFileListCompleteDelegate(OnRequestTitleFileListComplete);
|
|
if (bWasSuccessful)
|
|
{
|
|
Root = class'JsonObject'.static.DecodeJson(ResultStr[0]);
|
|
if (Root != None)
|
|
{
|
|
Files.Length = 0;
|
|
// fill in the list of RequestFiles
|
|
for (JsonObjectIdx=0; JsonObjectIdx < Root.ObjectArray.Length; JsonObjectIdx++)
|
|
{
|
|
RequestFileEntry.Filename = Root.ObjectArray[JsonObjectIdx].GetStringValue("file_name");
|
|
RequestFileEntry.DLName = Root.ObjectArray[JsonObjectIdx].GetStringValue("dl_name");
|
|
RequestFileEntry.HashCode = Root.ObjectArray[JsonObjectIdx].GetStringValue("hash_code");
|
|
// Any file that is not INT or INI should be Unicode
|
|
RequestFileEntry.bIsUnicode = InStr(RequestFileEntry.FileName,".ini",,true) == INDEX_NONE && InStr(RequestFileEntry.FileName,".int",,true) == INDEX_NONE;
|
|
Files.AddItem(RequestFileEntry);
|
|
}
|
|
// Determine which files can be loaded from the cache that don't require downloading
|
|
StartLoadingFiles();
|
|
}
|
|
else
|
|
{
|
|
`log(`location @ "Download of file list failed. Bad json."
|
|
@"ResultStr="$ResultStr[0]);
|
|
|
|
// @ZOMBIE_FOXTROT_BEGIN - ccooper 1/13/2014 - Changed code to call the completed delegate if the title list fails since the client still wants to know about it
|
|
for(Index = 0; Index < AllTitleFilesCompletedDelegates.Length; ++Index)
|
|
{
|
|
if(AllTitleFilesCompletedDelegates[Index] != none)
|
|
{
|
|
OnAllTitleFilesCompleted = AllTitleFilesCompletedDelegates[Index];
|
|
OnAllTitleFilesCompleted();
|
|
OnAllTitleFilesCompleted = none;
|
|
}
|
|
}
|
|
// @ZOMBIE_FOXTROT_END
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
`log(`location@"Download of file list failed.",, 'DevConfig');
|
|
|
|
// @ZOMBIE_FOXTROT_BEGIN - ccooper 1/13/2014 - Changed code to call the completed delegate if the title list fails since the client still wants to know about it
|
|
for(Index = 0; Index < AllTitleFilesCompletedDelegates.Length; ++Index)
|
|
{
|
|
if(AllTitleFilesCompletedDelegates[Index] != none)
|
|
{
|
|
OnAllTitleFilesCompleted = AllTitleFilesCompletedDelegates[Index];
|
|
OnAllTitleFilesCompleted();
|
|
OnAllTitleFilesCompleted = none;
|
|
}
|
|
}
|
|
// @ZOMBIE_FOXTROT_END
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kick off the cache/download requests
|
|
*/
|
|
function StartLoadingFiles()
|
|
{
|
|
local int Index;
|
|
|
|
// If there is online interface, then try to download the files
|
|
if (bRequestEmsFileList && TitleFileCacheInterface != None)
|
|
{
|
|
`log(`location@"if (bRequestEmsFileList && TitleFileCacheInterface != None)"@`showvar(Files.Length),, 'DevConfig');
|
|
// Iterate through files trying to download them
|
|
for (Index = 0; Index < Files.Length; Index++)
|
|
{
|
|
// Kick off the read of that file if not already started or failed
|
|
if (Files[Index].ReadState == OERS_NotStarted)
|
|
{
|
|
Files[Index].ReadState = OERS_InProgress;
|
|
if (!TitleFileCacheInterface.LoadTitleFile(Files[Index].DLName))
|
|
{
|
|
Files[Index].ReadState = OERS_Failed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (TitleFileInterface != None)
|
|
{
|
|
`log(`location@"else if (TitleFileInterface != None)"@`showvar(Files.Length),, 'DevConfig');
|
|
// Iterate through files trying to download them
|
|
for (Index = 0; Index < Files.Length; Index++)
|
|
{
|
|
// Kick off the read of that file if not already started or failed
|
|
if (Files[Index].ReadState == OERS_NotStarted)
|
|
{
|
|
// If this is a loc file name, make sure we are getting the right language
|
|
Files[Index].Filename = UpdateLocFileName(Files[Index].Filename);
|
|
// @ZOMBIE_FOXTROT_BEGIN - vspencer 11/26/2014 - The file to download should be localized also
|
|
Files[Index].DLName = UpdateLocFileName(Files[Index].DLName);
|
|
// @ZOMBIE_FOXTROT_END
|
|
if (TitleFileInterface.ReadTitleFile(Files[Index].DLName))
|
|
{
|
|
Files[Index].ReadState = OERS_InProgress;
|
|
}
|
|
else
|
|
{
|
|
Files[Index].ReadState = OERS_Failed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies us when the download of a file is complete
|
|
*
|
|
* @param bWasSuccessful true if the download completed ok, false otherwise
|
|
* @param FileName the file that was downloaded (or failed to)
|
|
*/
|
|
function OnDownloadFileComplete(bool bWasSuccessful,string FileName)
|
|
{
|
|
local bool bSuccessLoad;
|
|
local int Index;
|
|
local array<byte> FileData;
|
|
|
|
// Iterate through files to verify that this is one that we requested
|
|
for (Index = 0; Index < Files.Length; Index++)
|
|
{
|
|
if (Files[Index].DLName == FileName)
|
|
{
|
|
if (bWasSuccessful)
|
|
{
|
|
// Read the contents so that they can be processed
|
|
if (TitleFileInterface.GetTitleFileContents(FileName,FileData) &&
|
|
FileData.Length > 0)
|
|
{
|
|
bSuccessLoad = true;
|
|
Files[Index].ReadState = OERS_Done;
|
|
// Cache it so that the file doesn't need to be downloaded next time
|
|
if (bRequestEmsFileList &&
|
|
TitleFileCacheInterface != None)
|
|
{
|
|
TitleFileCacheInterface.SaveTitleFile(Files[Index].DLName,Files[Index].Filename,FileData);
|
|
}
|
|
// Clear memory copy of file from title file downloader
|
|
TitleFileInterface.ClearDownloadedFile(FileName);
|
|
// patch loc and class
|
|
ProcessIniLocFile(Files[Index].Filename,Files[Index].bIsUnicode,FileData);
|
|
}
|
|
else
|
|
{
|
|
Files[Index].ReadState = OERS_Failed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
`Log("Failed to download the file from system interface."
|
|
@"DLName="$Files[Index].DLName
|
|
@"Filename="$Files[Index].Filename);
|
|
Files[Index].ReadState = OERS_Failed;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// Notify that download complete
|
|
TriggerDownloadCompleteDelegates(bSuccessLoad,FileName);
|
|
}
|
|
|
|
/**
|
|
* Delegate fired when a file read from the local cache is complete
|
|
*
|
|
* @param bWasSuccessful whether the file read was successful or not
|
|
* @param FileName the name of the file this was for
|
|
*/
|
|
function OnFileCacheLoadComplete(bool bWasSuccessful,string FileName)
|
|
{
|
|
local int Index;
|
|
local array<byte> FileData;
|
|
local bool bRequiresDownload;
|
|
|
|
bRequiresDownload = true;
|
|
// Iterate through files to verify that this is one that we requested
|
|
for (Index = 0; Index < Files.Length; Index++)
|
|
{
|
|
if (Files[Index].DLName == FileName)
|
|
{
|
|
if (bWasSuccessful)
|
|
{
|
|
// Compare requested hash vs the one from the cache
|
|
if (TitleFileCacheInterface.GetTitleFileHash(FileName) == Files[Index].HashCode)
|
|
{
|
|
// Read the contents so that they can be processed
|
|
if (TitleFileCacheInterface.GetTitleFileContents(FileName,FileData) &&
|
|
FileData.Length > 0)
|
|
{
|
|
Files[Index].ReadState = OERS_Done;
|
|
bRequiresDownload = false;
|
|
// patch loc and class
|
|
ProcessIniLocFile(Files[Index].Filename,Files[Index].bIsUnicode,FileData);
|
|
// Clear memory copy of file from title file downloader
|
|
TitleFileCacheInterface.ClearCachedFile(FileName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
`Log("Hash for file cache entry not valid."
|
|
@"DLName="$Files[Index].DLName
|
|
@"Filename="$Files[Index].Filename);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// File could not be loaded from cache or hash was invalid. so, download it
|
|
if (bRequiresDownload)
|
|
{
|
|
// File was invalid so delete it
|
|
TitleFileCacheInterface.DeleteTitleFile(FileName);
|
|
// Start the download
|
|
if (TitleFileInterface != None &&
|
|
TitleFileInterface.ReadTitleFile(FileName))
|
|
{
|
|
Files[Index].ReadState = OERS_InProgress;
|
|
}
|
|
else
|
|
{
|
|
Files[Index].ReadState = OERS_Failed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Notify that load completed
|
|
TriggerDownloadCompleteDelegates(true,FileName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delegate fired when a file save to the local cache is complete
|
|
*
|
|
* @param bWasSuccessful whether the file read was successful or not
|
|
* @param FileName the name of the file this was for
|
|
*/
|
|
function OnFileCacheSaveComplete(bool bWasSuccessful,string FileName)
|
|
{
|
|
// clear the memory for the entry that was saved
|
|
TitleFileCacheInterface.ClearCachedFile(FileName);
|
|
}
|
|
|
|
/**
|
|
* Triggers list of delegates whenever a file has been loaded
|
|
*/
|
|
function TriggerDownloadCompleteDelegates(bool bSuccess,string FileName)
|
|
{
|
|
local int Index;
|
|
local delegate<OnReadTitleFileComplete> OnReadTitleFileComplete;
|
|
|
|
// Call delegates for file completion
|
|
for (Index=0; Index < ReadTitleFileCompleteDelegates.Length; Index++)
|
|
{
|
|
if (ReadTitleFileCompleteDelegates[Index] != None)
|
|
{
|
|
OnReadTitleFileComplete = ReadTitleFileCompleteDelegates[Index];
|
|
OnReadTitleFileComplete(bSuccess,FileName);
|
|
}
|
|
}
|
|
// See if all files are done
|
|
CheckForAllFilesComplete();
|
|
}
|
|
|
|
/**
|
|
* Triggers the final delegate if all files have been processed
|
|
*/
|
|
function CheckForAllFilesComplete()
|
|
{
|
|
local int Index;
|
|
local bool bAllFilesComplete;
|
|
|
|
bAllFilesComplete = true;
|
|
for (Index = 0; Index < Files.Length; Index++)
|
|
{
|
|
if (Files[Index].ReadState == OERS_NotStarted ||
|
|
Files[Index].ReadState == OERS_InProgress)
|
|
{
|
|
bAllFilesComplete = false;
|
|
}
|
|
}
|
|
if (bAllFilesComplete)
|
|
{
|
|
// @ZOMBIE_FOXTROT_BEGIN - ccooper 1/9/2014 - Changed delegate call to delegate container calls
|
|
for(Index = 0; Index < AllTitleFilesCompletedDelegates.Length; ++Index)
|
|
{
|
|
if(AllTitleFilesCompletedDelegates[Index] != none)
|
|
{
|
|
OnAllTitleFilesCompleted = AllTitleFilesCompletedDelegates[Index];
|
|
OnAllTitleFilesCompleted();
|
|
OnAllTitleFilesCompleted = none;
|
|
}
|
|
}
|
|
// @ZOMBIE_FOXTROT_END
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes the data, merges with the INI/Loc system, and then reloads the config for the
|
|
* affected objects
|
|
*
|
|
* @param FileName the name of the file being merged
|
|
* @param bIsUnicode whether the file should be treated as unicode or not
|
|
* @param FileData the file data to merge with the config cache
|
|
*/
|
|
native function ProcessIniLocFile(string FileName,bool bIsUnicode,const out array<byte> FileData);
|
|
|
|
/**
|
|
* Adds a loc/ini file to download
|
|
*
|
|
* @param FileName the file to download
|
|
*/
|
|
function AddFileToDownload(string FileName)
|
|
{
|
|
local int FileIndex;
|
|
|
|
FileIndex = Files.Find('FileName',FileName);
|
|
// Don't add more than once
|
|
if (FileIndex == INDEX_NONE)
|
|
{
|
|
// Add a new entry which will default to not started
|
|
FileIndex = Files.Length;
|
|
Files.Length = FileIndex + 1;
|
|
Files[FileIndex].FileName = FileName;
|
|
Files[FileIndex].DLName = FileName;
|
|
// Any file that is not INT or INI should be Unicode
|
|
Files[FileIndex].bIsUnicode = InStr(FileName,".ini",,true) == INDEX_NONE && InStr(FileName,".int",,true) == INDEX_NONE;
|
|
//`log("Files["$FileIndex$"].bIsUnicode = "$Files[FileIndex].bIsUnicode,, 'DevConfig');
|
|
}
|
|
else
|
|
{
|
|
Files[FileIndex].ReadState = OERS_NotStarted;
|
|
}
|
|
// Kick off the download
|
|
DownloadFiles();
|
|
}
|
|
|
|
/**
|
|
* Adds the specified delegate to the registered downloader. Since the file read can come from
|
|
* different objects, this method hides that detail, but still lets callers get notifications
|
|
*
|
|
* @param ReadTitleFileCompleteDelegate the delegate to set
|
|
*/
|
|
function AddReadFileDelegate(delegate<OnReadTitleFileComplete> ReadTitleFileCompleteDelegate)
|
|
{
|
|
// Add the delegate if not None and not found
|
|
if (ReadTitleFileCompleteDelegate != None &&
|
|
ReadTitleFileCompleteDelegates.Find(ReadTitleFileCompleteDelegate) == INDEX_NONE)
|
|
{
|
|
ReadTitleFileCompleteDelegates.AddItem(ReadTitleFileCompleteDelegate);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears the specified delegate from any registered downloaders
|
|
*
|
|
* @param ReadTitleFileCompleteDelegate the delegate to remove from the downloader
|
|
*/
|
|
function ClearReadFileDelegate(delegate<OnReadTitleFileComplete> ReadTitleFileCompleteDelegate)
|
|
{
|
|
local int RemoveIndex;
|
|
|
|
RemoveIndex = ReadTitleFileCompleteDelegates.Find(ReadTitleFileCompleteDelegate);
|
|
if (RemoveIndex != INDEX_NONE)
|
|
{
|
|
ReadTitleFileCompleteDelegates.Remove(RemoveIndex,1);
|
|
}
|
|
}
|
|
|
|
// @ZOMBIE_FOXTROT_BEGIN - ccooper 1/9/2014 - Adding function to add delegates for all files downloads and patching complete
|
|
function AddAllTitleFilesCompletedDelegate(delegate<OnAllTitleFilesCompleted> InDelegate)
|
|
{
|
|
if (AllTitleFilesCompletedDelegates.Find(InDelegate) == INDEX_NONE)
|
|
{
|
|
AllTitleFilesCompletedDelegates[AllTitleFilesCompletedDelegates.Length] = InDelegate;
|
|
}
|
|
}
|
|
function ClearAllTitleFilesCompletedDelegate(delegate<OnAllTitleFilesCompleted> InDelegate)
|
|
{
|
|
local int RemoveIndex;
|
|
RemoveIndex = AllTitleFilesCompletedDelegates.Find(InDelegate);
|
|
if (RemoveIndex != INDEX_NONE)
|
|
{
|
|
AllTitleFilesCompletedDelegates.Remove(RemoveIndex,1);
|
|
}
|
|
}
|
|
// @ZOMBIE_FOXTROT_END
|
|
|
|
/**
|
|
* Tells any subclasses to clear their cached file data
|
|
*/
|
|
function ClearCachedFiles()
|
|
{
|
|
local int Index;
|
|
|
|
// Iterate through files trying to download them
|
|
for (Index = 0; Index < Files.Length; Index++)
|
|
{
|
|
// Reset their status
|
|
Files[Index].ReadState = OERS_NotStarted;
|
|
}
|
|
if (TitleFileInterface != None)
|
|
{
|
|
// memory copy of downloaded files
|
|
TitleFileInterface.ClearDownloadedFiles();
|
|
}
|
|
if (TitleFileCacheInterface != None)
|
|
{
|
|
// memory copy of cached files
|
|
TitleFileCacheInterface.ClearCachedFiles();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the proper language extension for the loc file
|
|
*
|
|
* @param FileName the file name being modified
|
|
*
|
|
* @return the modified file name for this language setting
|
|
*/
|
|
native function string UpdateLocFileName(string FileName); |