9 private sealed class AssetsFileStateEntry
14 public string FilePath {
get;
set; } =
string.Empty;
18 public string? Password {
get;
set; }
22 public bool UseEncryption {
get;
set; }
25 private abstract class DefinitionStateEntry
30 [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
31 public string? AssetsFilePath {
get;
set; }
35 [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
36 public string? AssetEntryName {
get;
set; }
38 internal void Validate(
string? loosePath,
object? definition,
object? legacy =
null)
40 bool hasArchive = AssetsFilePath is not
null;
41 bool hasEntry = AssetEntryName is not
null;
42 if (hasArchive != hasEntry ||
43 (hasArchive && (
string.IsNullOrWhiteSpace(AssetsFilePath) ||
string.IsNullOrWhiteSpace(AssetEntryName))))
44 throw new InvalidDataException($
"Packed definition requires both AssetsFilePath and AssetEntryName: '{AssetsFilePath}' / '{AssetEntryName}'.");
45 int sources = (loosePath is not
null ? 1 : 0) + (definition is not
null ? 1 : 0) +
46 (hasArchive ? 1 : 0) + (legacy is not
null ? 1 : 0);
47 if (sources != 1 || (loosePath is not
null &&
string.IsNullOrWhiteSpace(loosePath)))
48 throw new InvalidDataException(
"Definition state entry requires exactly one source: inline Definition, loose path, or AssetsFilePath + AssetEntryName.");
52 private static IEnumerable<DefinitionStateEntry> PackedEntries(EngineStateSnapshot snapshot,
EngineStateParts parts)
54 if (parts.HasFlag(
EngineStateParts.Tilesheets) && snapshot.Tilesheets is not
null)
55 foreach (var entry
in snapshot.Tilesheets.Values)
56 if (entry?.AssetsFilePath is not
null) yield
return entry;
58 foreach (var entry
in snapshot.Cycles.Values)
59 if (entry?.AssetsFilePath is not
null) yield
return entry;
61 foreach (var entry
in snapshot.Scenes)
62 if (entry?.AssetsFilePath is not
null) yield
return entry;
63 if (parts.HasFlag(
EngineStateParts.Audio) && snapshot.Audio?.AssetsFilePath is not
null)
64 yield
return snapshot.Audio;
65 if (parts.HasFlag(
EngineStateParts.Sprites) && snapshot.Sprites?.AssetsFilePath is not
null)
66 yield
return snapshot.Sprites;
69 private static void ValidateSelectedSources(EngineStateSnapshot snapshot,
EngineStateParts parts)
71 if (parts.HasFlag(
EngineStateParts.Tilesheets) && snapshot.Tilesheets is not
null)
72 foreach (var entry
in snapshot.Tilesheets.Values)
73 entry?.Validate(entry.GtsPath, entry.Definition);
75 foreach (var entry
in snapshot.Cycles.Values)
76 entry?.Validate(entry.GaniPath, entry.Definition);
78 foreach (var entry
in snapshot.Scenes)
79 entry?.Validate(entry.GscnPath, entry.Definition, entry.LegacyScene);
81 audio.Validate(audio.GsndPath, audio.Definition);
82 if (parts.HasFlag(
EngineStateParts.Sprites) && snapshot.Sprites is { } sprites)
83 sprites.Validate(sprites.GsprPath, sprites.Definition, sprites.LegacySprites);
86 private static bool TryPreserve(DefinitionStateEntry entry, DefinitionProvenance? provenance,
90 if (persistence !=
DefinitionPersistence.PreserveSource || provenance is
null || !provenance.Matches(definition))
92 if (provenance.AssetsFilePath is { } archivePath)
94 entry.AssetsFilePath = MakeRelativePath(archivePath, baseDirectory);
95 entry.AssetEntryName = provenance.AssetEntryName;
98 if (provenance.FilePath is { } filePath)
100 loosePath = MakeRelativePath(filePath, baseDirectory);
106 private static AssetsFile? FindAssetsFile(
string path) => AssetsFile.AllAssetsFiles.FirstOrDefault(
107 file => !
string.IsNullOrWhiteSpace(file.SourcePath) &&
string.Equals(
108 Path.GetFullPath(file.SourcePath), Path.GetFullPath(path),
109 OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
111 private static Exception DefinitionError(DefinitionStateEntry entry,
string? baseDirectory, Exception error) =>
112 entry.AssetsFilePath is
null ? error :
new InvalidDataException(
113 $
"Cannot load packed definition '{entry.AssetEntryName}' from GAF '{ResolvePath(entry.AssetsFilePath, baseDirectory)}'.", error);
115 private static T WithDefinitionContext<T>(DefinitionStateEntry entry,
string? baseDirectory, Func<T> materialize)
117 try {
return materialize(); }
118 catch (Exception ex) when (entry.AssetsFilePath is not
null && ex is not OutOfMemoryException)
120 throw DefinitionError(entry, baseDirectory, ex);
124 private static T LoadPacked<T>(DefinitionStateEntry entry,
string? baseDirectory,
AssetTypes expectedType, Func<AssetsFile, string, T> load)
126 var path = ResolvePath(entry.AssetsFilePath!, baseDirectory);
129 var archive = FindAssetsFile(path);
132 if (!File.Exists(path))
throw new FileNotFoundException(
"Archive not found.", path);
133 archive = AssetsFile.LoadOrCreate(path);
137 if (!archive.GetAllEntries().Any(candidate => candidate.AssetType == expectedType &&
138 string.Equals(candidate.AssetName, entry.AssetEntryName, StringComparison.Ordinal)))
139 throw new InvalidDataException($
"Entry does not exist with expected type '{expectedType}'.");
140 return load(archive, entry.AssetEntryName!);
142 catch (Exception ex) when (ex is not OutOfMemoryException)
144 throw new InvalidDataException($
"Cannot load packed definition '{entry.AssetEntryName}' from GAF '{path}'.", ex);
Represents the complete serializable state of the game engine, including assets, scenes,...
AssetTypes
Defines the types of assets that can be stored and managed by the engine.
EngineStateParts
Represents different parts of the engine state that can be managed, saved, or loaded independently....
DefinitionPersistence
Controls how EngineState persists a definition.