Jeremy Pritts 85172b7393
File system improvements (#1936)
* WIP file system improvements

* Switch to a hierarchial virtual file system

* EnumerateFiles and EnumerateDirectories

* Change import code to use FileSystem

* Use FileSystem when loading assemblies

* Use FileSystem in MonoManager initialization

* Remove set method for PlatformGameStructure::RootPath

* Renaming cleanup
2025-09-13 15:17:41 -07:00

179 lines
5.6 KiB
C#

using AssetRipper.Assets.Bundles;
using AssetRipper.Import.AssetCreation;
using AssetRipper.Import.Configuration;
using AssetRipper.Import.Logging;
using AssetRipper.Import.Structure.Assembly;
using AssetRipper.Import.Structure.Assembly.Managers;
using AssetRipper.Import.Structure.Platforms;
using AssetRipper.IO.Files;
using AssetRipper.IO.Files.ResourceFiles;
namespace AssetRipper.Import.Structure;
public sealed class GameStructure : IDisposable
{
public GameBundle FileCollection { get; private set; }
public PlatformGameStructure? PlatformStructure { get; private set; }
public PlatformGameStructure? MixedStructure { get; private set; }
public IAssemblyManager AssemblyManager { get; set; }
public FileSystem FileSystem { get; }
private GameStructure(List<string> paths, FileSystem fileSystem, CoreConfiguration configuration)
{
Logger.SendStatusChange("loading_step_detect_platform");
FileSystem = fileSystem;
PlatformChecker.CheckPlatform(paths, fileSystem, out PlatformGameStructure? platformStructure, out MixedGameStructure? mixedStructure);
PlatformStructure = platformStructure;
PlatformStructure?.CollectFiles(configuration.ImportSettings.IgnoreStreamingAssets);
MixedStructure = mixedStructure;
//MixedStructure?.CollectFiles(configuration.IgnoreStreamingAssets);
//The PlatformGameStructure constructor adds all the paths to the Assemblies and Files dictionaries
//No bundles or assemblies have been loaded yet
Logger.SendStatusChange("loading_step_initialize_layout");
InitializeAssemblyManager(configuration);
Logger.SendStatusChange("loading_step_begin_scheme_processing");
InitializeGameCollection(configuration.ImportSettings.DefaultVersion, configuration.ImportSettings.TargetVersion);
if (!FileCollection.HasAnyAssetCollections())
{
Logger.Log(LogType.Warning, LogCategory.Import, "The game structure processor could not find any valid assets.");
}
}
public bool IsValid => FileCollection.HasAnyAssetCollections();
public string? Name => PlatformStructure?.Name ?? MixedStructure?.Name;
public static GameStructure Load(IEnumerable<string> paths, FileSystem fileSystem, CoreConfiguration configuration)
{
List<string> toProcess = ZipExtractor.Process(paths);
if (toProcess.Count == 0)
{
throw new ArgumentException("Game files not found", nameof(paths));
}
return new GameStructure(toProcess, fileSystem, configuration);
}
[MemberNotNull(nameof(FileCollection))]
private void InitializeGameCollection(UnityVersion defaultVersion, UnityVersion targetVersion)
{
Logger.SendStatusChange("loading_step_create_file_collection");
GameAssetFactory assetFactory = new GameAssetFactory(AssemblyManager);
IEnumerable<string> filePaths;
if (PlatformStructure is null || MixedStructure is null)
{
filePaths = (PlatformStructure ?? MixedStructure)?.Files.Values() ?? [];
}
else
{
filePaths = PlatformStructure.Files.Union(MixedStructure.Files).Select(pair => pair.Value);
}
FileCollection = GameBundle.FromPaths(
filePaths,
assetFactory,
FileSystem,
new GameInitializer(PlatformStructure, MixedStructure, FileSystem, defaultVersion, targetVersion));
}
[MemberNotNull(nameof(AssemblyManager))]
private void InitializeAssemblyManager(CoreConfiguration configuration)
{
ScriptingBackend scriptBackend = GetScriptingBackend(configuration.DisableScriptImport);
Logger.Info(LogCategory.Import, $"Files use the '{scriptBackend}' scripting backend.");
AssemblyManager = scriptBackend switch
{
ScriptingBackend.Mono => new MonoManager(OnRequestAssembly),
ScriptingBackend.IL2Cpp => new IL2CppManager(OnRequestAssembly, configuration.ImportSettings.ScriptContentLevel),
_ => new BaseManager(OnRequestAssembly),
};
Logger.SendStatusChange("loading_step_load_assemblies");
try
{
//Loads any Mono or IL2Cpp assemblies
AssemblyManager.Initialize(PlatformStructure ?? MixedStructure ?? throw new Exception("No platform structure"));
}
catch (Exception ex)
{
Logger.Error(LogCategory.Import, "Could not initialize assembly manager. Switching to the 'Unknown' scripting backend.");
Logger.Error(ex);
AssemblyManager = new BaseManager(OnRequestAssembly);
}
}
private ScriptingBackend GetScriptingBackend(bool disableScriptImport)
{
if (disableScriptImport)
{
Logger.Info(LogCategory.Import, "Script import disabled by settings.");
return ScriptingBackend.Unknown;
}
if (PlatformStructure != null)
{
ScriptingBackend backend = PlatformStructure.Backend;
if (backend != ScriptingBackend.Unknown)
{
return backend;
}
}
if (MixedStructure != null)
{
ScriptingBackend backend = MixedStructure.Backend;
if (backend != ScriptingBackend.Unknown)
{
return backend;
}
}
return ScriptingBackend.Unknown;
}
private void OnRequestAssembly(string assembly)
{
string assemblyName = $"{assembly}.dll";
ResourceFile? resFile = FileCollection.ResolveResource(assemblyName);
if (resFile is not null)
{
resFile.Stream.Position = 0;
AssemblyManager.Read(resFile.Stream, assemblyName);
}
else
{
string? path = RequestAssembly(assembly);
if (path is null)
{
Logger.Log(LogType.Warning, LogCategory.Import, $"Assembly '{assembly}' hasn't been found");
return;
}
AssemblyManager.Load(path, FileSystem);
}
Logger.Info(LogCategory.Import, $"Assembly '{assembly}' has been loaded");
}
public string? RequestAssembly(string assembly)
{
return PlatformStructure?.RequestAssembly(assembly) ?? MixedStructure?.RequestAssembly(assembly);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool _)
{
AssemblyManager?.Dispose();
FileCollection?.Dispose();
}
}