feat(launcher): stabilize prepared content updates
Some checks failed
CI / linux-portable (push) Failing after 3m12s
CI / windows-gate (push) Failing after 6m35s
CI / release (push) Has been skipped

This commit is contained in:
Erik 2026-08-25 19:17:13 +02:00
parent f160f3fee1
commit af9327a17b
42 changed files with 3706 additions and 147 deletions

View file

@ -27,6 +27,12 @@ internal sealed class HeadlessContentDescriptor
[JsonRequired]
public string PreparedAssetPath { get; init; } = string.Empty;
public string? PreparedAssetOverlayPath { get; init; }
public uint? PreparedAssetBaseRecipeVersion { get; init; }
public uint? PreparedAssetEffectiveRecipeVersion { get; init; }
}
// MF-1 (Campaign OP OP7 review fix, 2026-08-11): record, not class — the

View file

@ -173,6 +173,18 @@ internal static class HeadlessConfigurationLoader
throw new HeadlessConfigurationException(
"process.content requires non-empty datDirectory and preparedAssetPath.");
}
bool hasOverlay = !string.IsNullOrWhiteSpace(
content.PreparedAssetOverlayPath);
bool hasBaseRecipe = content.PreparedAssetBaseRecipeVersion is > 0;
bool hasEffectiveRecipe =
content.PreparedAssetEffectiveRecipeVersion is > 0;
if (hasOverlay != hasBaseRecipe || hasOverlay != hasEffectiveRecipe)
{
throw new HeadlessConfigurationException(
"process.content overlay path, base recipe, and effective recipe "
+ "must be supplied together.");
}
}
private static void ValidateSession(

View file

@ -43,15 +43,56 @@ internal sealed class ProductionHeadlessProcessContentFactory
string datDirectory = Path.GetFullPath(descriptor.DatDirectory);
string preparedAssetPath =
Path.GetFullPath(descriptor.PreparedAssetPath);
string? overlayPath = string.IsNullOrWhiteSpace(
descriptor.PreparedAssetOverlayPath)
? null
: Path.GetFullPath(descriptor.PreparedAssetOverlayPath);
IDatReaderWriter? dats = null;
IPreparedAssetSource? prepared = null;
try
{
dats = RuntimeDatCollectionFactory.OpenReadOnly(datDirectory);
prepared = new PakPreparedAssetSource(
preparedAssetPath,
dats,
diagnostic);
if (overlayPath is null)
{
prepared = new PakPreparedAssetSource(
preparedAssetPath,
dats,
diagnostic);
}
else
{
if (descriptor.PreparedAssetBaseRecipeVersion is not > 0
|| descriptor.PreparedAssetEffectiveRecipeVersion
!= AcDream.Content.Pak.PakFormat.CurrentBakeToolVersion)
{
throw new InvalidDataException(
"Layered prepared content does not match the client's recipe.");
}
var baseSource = new PakPreparedAssetSource(
preparedAssetPath,
PreparedAssetCatalogIdentity.From(
dats,
descriptor.PreparedAssetBaseRecipeVersion.Value),
diagnostic);
try
{
var overlaySource = new PakPreparedAssetSource(
overlayPath,
PreparedAssetCatalogIdentity.From(
dats,
descriptor.PreparedAssetEffectiveRecipeVersion.Value),
diagnostic);
prepared = new LayeredPreparedAssetSource(
baseSource,
overlaySource);
}
catch
{
baseSource.Dispose();
throw;
}
}
MagicCatalog magic = MagicCatalog.Load(dats);
Region region = dats.Get<Region>(0x13000000u)
?? throw new InvalidOperationException(