475 lines
No EOL
15 KiB
C#
475 lines
No EOL
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Threading;
|
|
using JD.Core.ProjectManaging;
|
|
using Ubisoft.Collections;
|
|
|
|
namespace Packager.AssetCooker
|
|
{
|
|
/// <summary>
|
|
/// This is the entry point for different possible tasks that can be done using AssetCooker
|
|
/// </summary>
|
|
public class AssetCooker : IDisposable
|
|
{
|
|
private readonly engineWrapper _engine;
|
|
private cookerWrapper _cooker;
|
|
private List<string> _outputList;
|
|
private readonly List<string> _inputFilesToProcess;
|
|
private readonly HashSet<string> _excludeFileList;
|
|
|
|
public Parameters.Platform Platform { get; set; }
|
|
|
|
public int RebaseEngineVersion { get; set; }
|
|
|
|
public int DataVersion { get; set; }
|
|
|
|
public string Inputs { get; set; }
|
|
|
|
public string MergeList { get; set; }
|
|
|
|
public bool ExitOnError { get; set; }
|
|
|
|
public string OutputFile { get; set; }
|
|
|
|
public string OutputDependenciesFolder { get; set; }
|
|
|
|
public string ExcludeFiles { get; set; }
|
|
|
|
public int ForceMipMap { get; set; }
|
|
|
|
public bool CookLogicToBinary { get; set; }
|
|
|
|
public string BundleOutput { get; set; }
|
|
|
|
public string CommonFat { get; set; }
|
|
|
|
public bool GetDependencies { get; set; }
|
|
|
|
public bool Cook { get; set; }
|
|
|
|
public bool OverrideScene { get; set; }
|
|
|
|
public string OriginalScene { get; set; }
|
|
|
|
public string ReplacementScene { get; set; }
|
|
|
|
public bool GenerateCookedList { get; set; }
|
|
|
|
public bool BuildAtl { get; set; }
|
|
|
|
public bool PackSgs { get; set; }
|
|
|
|
public string CacheKey { get; set; }
|
|
|
|
public bool Unique { get; set; }
|
|
|
|
public List<string> CookedFileList { get; set; }
|
|
|
|
public MessageLogger Logger { get; }
|
|
|
|
public AssetCooker()
|
|
{
|
|
Logger = new MessageLogger(nameof(AssetCooker));
|
|
|
|
_excludeFileList = new HashSet<string>();
|
|
_engine = new engineWrapper("");
|
|
InitializeCooker();
|
|
ForceMipMap = -1;
|
|
|
|
_outputList = new List<string>();
|
|
_inputFilesToProcess = new List<string>();
|
|
}
|
|
|
|
private void InitializeCooker()
|
|
{
|
|
_cooker = new cookerWrapper();
|
|
_cooker.init(DataPath.Value);
|
|
_cooker.enableCacheFiles();
|
|
|
|
Logger.AttachCookerWrapper(_cooker);
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
Initialize();
|
|
Execute();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Logger.DetachCookerWrapper(_cooker);
|
|
|
|
_cooker.destroy();
|
|
_engine.destroy();
|
|
}
|
|
|
|
private void Initialize()
|
|
{
|
|
ExtractMergeListContent();
|
|
|
|
ExtractInputFiles();
|
|
|
|
CreateOutputDirectory();
|
|
|
|
CreateExcludeFileLsit();
|
|
|
|
SetCookerParameters();
|
|
|
|
}
|
|
|
|
private void SetCookerParameters()
|
|
{
|
|
_cooker.setCacheKey(CacheKey);
|
|
_cooker.rebaseEngineVersion(RebaseEngineVersion);
|
|
}
|
|
|
|
private void CreateExcludeFileLsit()
|
|
{
|
|
if (ExcludeFiles == null)
|
|
return;
|
|
|
|
foreach (string file in ExcludeFiles.Split(','))
|
|
{
|
|
var fileContent = File.ReadAllLines(file.Trim());
|
|
_excludeFileList.AddRange(fileContent);
|
|
}
|
|
}
|
|
|
|
private void CreateOutputDirectory()
|
|
{
|
|
var directoryName = Path.GetDirectoryName(OutputFile);
|
|
|
|
if (directoryName != null && !Directory.Exists(directoryName))
|
|
Directory.CreateDirectory(directoryName);
|
|
}
|
|
|
|
private void ExtractInputFiles()
|
|
{
|
|
if (Inputs == null)
|
|
return;
|
|
|
|
foreach (var input in Inputs.Split(','))
|
|
{
|
|
_inputFilesToProcess.Add(input.Trim());
|
|
}
|
|
}
|
|
|
|
private void ExtractMergeListContent()
|
|
{
|
|
if (MergeList != null)
|
|
{
|
|
_inputFilesToProcess.Clear();
|
|
_inputFilesToProcess.AddRange(File.ReadAllLines(MergeList).Distinct());
|
|
_inputFilesToProcess.Sort();
|
|
}
|
|
}
|
|
|
|
private void CookIt()
|
|
{
|
|
var cookingMaster = new Master(_cooker);
|
|
|
|
Logger.LogInfo($"{nameof(Master)} was created, its log file can be found at \"{cookingMaster.Logger.LogFilePath}\".");
|
|
|
|
cookingMaster.CookFiles(_outputList.ToArray(), Platform.ToString(), CookLogicToBinary, ForceMipMap, RebaseEngineVersion);
|
|
while (!cookingMaster.FinishedCookingFiles())
|
|
{
|
|
Thread.Sleep(100);
|
|
}
|
|
}
|
|
|
|
private void BundleIt()
|
|
{
|
|
var bundleName = GetBundleName();
|
|
|
|
_outputList = _outputList.Where(file => !file.EndsWith(".atl.ckd") && !file.EndsWith(".als") && !file.EndsWith(".sfi") && !file.EndsWith(".bik")).ToList();
|
|
|
|
_cooker.bundle(bundleName, Platform.ToString(), CookLogicToBinary, _outputList, DataVersion);
|
|
}
|
|
|
|
private void Execute()
|
|
{
|
|
CreateOutputFileList(_inputFilesToProcess);
|
|
|
|
if (GetDependencies)
|
|
{
|
|
ExtractDependencies();
|
|
}
|
|
|
|
if (Cook)
|
|
{
|
|
CookIt();
|
|
}
|
|
|
|
if (OverrideScene)
|
|
{
|
|
ReplaceOriginalScene();
|
|
}
|
|
|
|
if (BuildAtl)
|
|
{
|
|
BuildAtlasContainer();
|
|
}
|
|
|
|
if (PackSgs)
|
|
{
|
|
BuildSgsContainer();
|
|
}
|
|
|
|
if (_excludeFileList != null)
|
|
{
|
|
// exclude files from a list
|
|
var validOutputFiles = _outputList.Except(_excludeFileList);
|
|
_outputList = validOutputFiles.ToList();
|
|
}
|
|
|
|
if (GenerateCookedList)
|
|
{
|
|
GenerateCookedFileNames();
|
|
}
|
|
|
|
if (CommonFat != null)
|
|
{
|
|
_cooker.buildGlobalFat(CommonFat, Platform.ToString(), _outputList);
|
|
}
|
|
|
|
if (BundleOutput != null)
|
|
{
|
|
BundleIt();
|
|
}
|
|
|
|
if (Unique)
|
|
{
|
|
RemoveDuplicateFilesFromOutput();
|
|
}
|
|
|
|
if (OutputFile != null)
|
|
{
|
|
File.WriteAllLines(OutputFile, _outputList);
|
|
}
|
|
|
|
}
|
|
|
|
private string GetBundleName()
|
|
{
|
|
return BundleOutput.EndsWith(".ipk") ? BundleOutput.ToLower() : _cooker.getBundleName(BundleOutput.ToLower(), Platform.ToString());
|
|
}
|
|
|
|
private void RemoveDuplicateFilesFromOutput()
|
|
{
|
|
_outputList = _outputList.Distinct().ToList();
|
|
}
|
|
|
|
private void GenerateCookedFileNames()
|
|
{
|
|
_outputList = _outputList.Select(ToCookFileName).ToList();
|
|
CookedFileList = _outputList;
|
|
}
|
|
|
|
private string ToCookFileName(string originalName)
|
|
{
|
|
if (_cooker.isCookable(originalName))
|
|
{
|
|
var cookedname = "";
|
|
|
|
_cooker.getCookedNamePlatform(originalName, Platform.ToString(), ref cookedname);
|
|
|
|
return ChangeFileNameToCookedVersion(originalName, cookedname);
|
|
}
|
|
|
|
switch (originalName)
|
|
{
|
|
case "atlascontainer":
|
|
string uvAtlasContainer = null;
|
|
_cooker.getCookedNamePlatform("atlascontainer", Platform.ToString(), ref uvAtlasContainer);
|
|
return uvAtlasContainer;
|
|
case "sgscontainer":
|
|
string sgsContainer = null;
|
|
_cooker.getCookedNamePlatform("sgscontainer", Platform.ToString(), ref sgsContainer);
|
|
return sgsContainer;
|
|
}
|
|
|
|
return originalName;
|
|
}
|
|
|
|
private string ChangeFileNameToCookedVersion(string originalName, string cookedname)
|
|
{
|
|
// for png and jpg files,
|
|
// if the cooked file does not exist, consider that it was set as not to be cooked
|
|
var ext = Path.GetExtension(originalName);
|
|
if (ext == ".png" || ext == ".jpg")
|
|
{
|
|
var file = _cooker.getDataRoot() + cookedname;
|
|
file = file.Replace("/", @"\");
|
|
if (File.Exists(file))
|
|
{
|
|
return cookedname;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return cookedname;
|
|
}
|
|
return originalName;
|
|
}
|
|
|
|
private void BuildSgsContainer()
|
|
{
|
|
var sgsList = _outputList.Where(path => Path.GetExtension(path) == ".sgs").ToList();
|
|
|
|
string sgsContainer = null;
|
|
|
|
_cooker.getCookedNamePlatform("sgscontainer", Platform.ToString(), ref sgsContainer);
|
|
_cooker.buildSgsContainer(sgsContainer, Platform.ToString(), CookLogicToBinary, sgsList);
|
|
|
|
_outputList = _outputList.Except(sgsList).ToList();
|
|
_outputList.Add(sgsContainer);
|
|
}
|
|
|
|
private void BuildAtlasContainer()
|
|
{
|
|
// build atl container
|
|
var atlBuildList = new List<string>();
|
|
|
|
foreach (var file in _outputList)
|
|
{
|
|
var ext = Path.GetExtension(file);
|
|
if (!string.IsNullOrEmpty(ext))
|
|
ext = ext.Substring(1);
|
|
|
|
switch (ext)
|
|
{
|
|
case "tga":
|
|
case "png":
|
|
atlBuildList.Add(file);
|
|
break;
|
|
case "atl":
|
|
{
|
|
string tgaFile = Path.ChangeExtension(file, "tga");
|
|
string pngFile = Path.ChangeExtension(file, "png");
|
|
if (!_outputList.Contains(tgaFile) && !_outputList.Contains(pngFile))
|
|
{
|
|
atlBuildList.Add(file);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
string uvAtlasContainer = null;
|
|
_cooker.getCookedNamePlatform("atlascontainer", Platform.ToString(), ref uvAtlasContainer);
|
|
_cooker.buildAtlasContainer(uvAtlasContainer, Platform.ToString(), atlBuildList);
|
|
_outputList.Add(uvAtlasContainer);
|
|
}
|
|
|
|
private void ReplaceOriginalScene()
|
|
{
|
|
// derive scene config paths
|
|
var originalSgs = Path.ChangeExtension(OriginalScene, "sgs");
|
|
var replacementSgs = Path.ChangeExtension(ReplacementScene, "sgs");
|
|
|
|
// transform to cooked paths
|
|
string originalPath = null;
|
|
string replacementPath = null;
|
|
string originalSgsPath = null;
|
|
string replacementSgsPath = null;
|
|
_cooker.getCookedNamePlatform(OriginalScene, Platform.ToString(), ref originalPath);
|
|
_cooker.getCookedNamePlatform(ReplacementScene, Platform.ToString(), ref replacementPath);
|
|
_cooker.getCookedNamePlatform(originalSgs, Platform.ToString(), ref originalSgsPath);
|
|
_cooker.getCookedNamePlatform(replacementSgs, Platform.ToString(), ref replacementSgsPath);
|
|
|
|
// make paths absolute
|
|
originalPath = Path.Combine(_cooker.getDataRoot(), originalPath);
|
|
replacementPath = Path.Combine(_cooker.getDataRoot(), replacementPath);
|
|
originalSgsPath = Path.Combine(_cooker.getDataRoot(), originalSgsPath);
|
|
replacementSgsPath = Path.Combine(_cooker.getDataRoot(), replacementSgsPath);
|
|
|
|
if (string.Compare(originalPath, replacementPath, StringComparison.OrdinalIgnoreCase) != 0)
|
|
{
|
|
// override cooked files
|
|
File.Copy(replacementPath, originalPath, true);
|
|
File.Copy(replacementSgsPath, originalSgsPath, true);
|
|
|
|
// make sure the overridden files will get replaced by next cooking session
|
|
File.SetCreationTime(originalPath, new DateTime(1970, 1, 1));
|
|
File.SetLastAccessTime(originalPath, new DateTime(1970, 1, 1));
|
|
File.SetLastWriteTime(originalPath, new DateTime(1970, 1, 1));
|
|
File.SetCreationTime(originalSgsPath, new DateTime(1970, 1, 1));
|
|
File.SetLastAccessTime(originalSgsPath, new DateTime(1970, 1, 1));
|
|
File.SetLastWriteTime(originalSgsPath, new DateTime(1970, 1, 1));
|
|
}
|
|
}
|
|
|
|
[HandleProcessCorruptedStateExceptions]
|
|
private void ExtractDependencies()
|
|
{
|
|
// retrieve dependencies
|
|
var globalDependencies = new List<string>();
|
|
|
|
foreach (var file in _outputList)
|
|
{
|
|
var localDependencies = new List<string>();
|
|
try
|
|
{
|
|
GetFileDependencies(file, ref localDependencies);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Logger.LogException(exception);
|
|
}
|
|
globalDependencies.AddRange(localDependencies);
|
|
|
|
//Create an OutputFile for each InputFile
|
|
if (!string.IsNullOrEmpty(OutputDependenciesFolder))
|
|
{
|
|
var output = Path.Combine(OutputDependenciesFolder, Path.GetFileNameWithoutExtension(file) + ".txt");
|
|
try
|
|
{
|
|
File.WriteAllLines(output, localDependencies);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Logger.LogException(exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
var uniqueDependencies = new HashSet<string>();
|
|
|
|
foreach (string file in globalDependencies)
|
|
uniqueDependencies.Add(file);
|
|
|
|
CreateOutputFileList(uniqueDependencies);
|
|
}
|
|
|
|
private void CreateOutputFileList(IEnumerable<string> fileList)
|
|
{
|
|
_outputList.Clear();
|
|
|
|
foreach (var filepath in fileList)
|
|
_outputList.Add(filepath.Trim());
|
|
}
|
|
|
|
private void GetFileDependencies(string rootFilename, ref List<string> files)
|
|
{
|
|
Stopwatch watch = Stopwatch.StartNew();
|
|
if (rootFilename == "common.alias")
|
|
{
|
|
_cooker.getGameDependencies(Platform.ToString(), files);
|
|
}
|
|
else
|
|
{
|
|
string depFile = rootFilename.Replace('\\', '/').ToLower();
|
|
files.Add(depFile);
|
|
_cooker.getDependencies(Platform.ToString(), depFile, files);
|
|
}
|
|
|
|
watch.Stop();
|
|
TimeSpan ts = watch.Elapsed;
|
|
Logger.LogInfo($"Time to get dependencies: {ts.TotalMilliseconds / 1000.0:f3} s ({files.Count} files)");
|
|
}
|
|
}
|
|
} |