Optimize prepared asset package v2

This commit is contained in:
Erik 2026-08-27 20:09:09 +02:00
parent d123c4b67c
commit 0dd966f3a0
28 changed files with 1277 additions and 106 deletions

View file

@ -153,7 +153,7 @@ public sealed class BakeDeterminismTests : IDisposable {
Assert.Equal(1, reportA.UniqueCellStructureCollisions);
Assert.Equal(7, reportA.CellStructureCollisionAliases);
Assert.Equal(8, reportA.EnvCellTopologyKeys);
Assert.Equal(10, reportA.PhysicalBlobs);
Assert.Equal(10 + reportA.TexturePayloadKeys, reportA.PhysicalBlobs);
Assert.Equal(reportA.TotalKeys, reportB.TotalKeys);
Assert.True(File.ReadAllBytes(pathA).AsSpan().SequenceEqual(File.ReadAllBytes(pathB)));

View file

@ -14,7 +14,7 @@ public sealed class InstalledPreparedCollisionCatalogTests
if (datDir is null)
Assert.Fail("Lane=PreparedPackage requires installed retail DATs and a validated acdream.pak; see docs/release-gate.md.");
string packagePath = Path.Combine(datDir, "acdream.pak");
string packagePath = ResolvePackagePath(datDir);
if (!File.Exists(packagePath))
Assert.Fail("Lane=PreparedPackage requires installed retail DATs and a validated acdream.pak; see docs/release-gate.md.");
@ -36,7 +36,17 @@ public sealed class InstalledPreparedCollisionCatalogTests
PreparedAssetReadStatus.Loaded,
source.ReadEnvCellTopology(0xA9B4_013Fu).Status);
Assert.Equal(4, source.CollisionStats.Loaded);
Assert.True(source.MappedVirtualBytes > 1L << 30);
Assert.Equal(new FileInfo(packagePath).Length, source.MappedVirtualBytes);
Assert.InRange(source.MappedVirtualBytes, 1L, 5L * 1024 * 1024 * 1024);
}
private static string ResolvePackagePath(string datDir)
{
string? configured =
Environment.GetEnvironmentVariable("ACDREAM_PAK_PATH");
return !string.IsNullOrWhiteSpace(configured)
? Path.GetFullPath(configured)
: Path.Combine(datDir, "acdream.pak");
}
private static string? ResolveDatDir()

View file

@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using AcDream.Content;
using Chorizite.Core.Render.Enums;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Enums;
@ -112,6 +113,100 @@ public sealed class MeshExtractorSolidFaceExtractionTests
Assert.False(batch.Key.IsSolid);
}
[Theory]
[InlineData(PixelFormat.PFID_DXT1, TextureFormat.DXT1, 8)]
[InlineData(PixelFormat.PFID_DXT3, TextureFormat.DXT3, 16)]
[InlineData(PixelFormat.PFID_DXT5, TextureFormat.DXT5, 16)]
public void PrepareMeshData_UneditedDxtSurface_PreservesNativeBlocks(
PixelFormat sourceFormat,
TextureFormat expectedFormat,
int sourceBytes)
{
var dats = new FakeMeshExtractorDats();
byte[] blocks = Enumerable.Range(0, sourceBytes).Select(i => (byte)i).ToArray();
RegisterTexturedQuad(dats, SurfaceType.Base1Image, sourceFormat, blocks);
var extractor = new MeshExtractor(dats, NullLogger.Instance, sideStagedSink: null);
ObjectMeshData mesh = Assert.IsType<ObjectMeshData>(
extractor.PrepareMeshData(GfxObjId, isSetup: false));
KeyValuePair<(int Width, int Height, TextureFormat Format), List<TextureBatchData>> group =
Assert.Single(mesh.TextureBatches);
Assert.Equal(expectedFormat, group.Key.Format);
TextureBatchData batch = Assert.Single(group.Value);
Assert.Same(blocks, batch.TextureData);
Assert.Null(batch.UploadPixelFormat);
Assert.Null(batch.UploadPixelType);
}
[Fact]
public void PrepareMeshData_DxtClipMap_DecodesForSurfaceLocalAlphaEdit()
{
var dats = new FakeMeshExtractorDats();
RegisterTexturedQuad(
dats,
SurfaceType.Base1Image | SurfaceType.Base1ClipMap,
PixelFormat.PFID_DXT1,
new byte[8]);
var extractor = new MeshExtractor(dats, NullLogger.Instance, sideStagedSink: null);
ObjectMeshData mesh = Assert.IsType<ObjectMeshData>(
extractor.PrepareMeshData(GfxObjId, isSetup: false));
KeyValuePair<(int Width, int Height, TextureFormat Format), List<TextureBatchData>> group =
Assert.Single(mesh.TextureBatches);
Assert.Equal(TextureFormat.RGBA8, group.Key.Format);
Assert.Equal(4 * 4 * 4, Assert.Single(group.Value).TextureData.Length);
}
[Fact]
public void PrepareMeshData_TranslucentDxt_DecodesForAlphaScale()
{
var dats = new FakeMeshExtractorDats();
RegisterTexturedQuad(
dats,
SurfaceType.Base1Image,
PixelFormat.PFID_DXT1,
new byte[8],
translucency: 0.25f);
var extractor = new MeshExtractor(dats, NullLogger.Instance, sideStagedSink: null);
ObjectMeshData mesh = Assert.IsType<ObjectMeshData>(
extractor.PrepareMeshData(GfxObjId, isSetup: false));
KeyValuePair<(int Width, int Height, TextureFormat Format), List<TextureBatchData>> group =
Assert.Single(mesh.TextureBatches);
Assert.Equal(TextureFormat.RGBA8, group.Key.Format);
Assert.Equal(4 * 4 * 4, Assert.Single(group.Value).TextureData.Length);
}
private static void RegisterTexturedQuad(
FakeMeshExtractorDats dats,
SurfaceType surfaceType,
PixelFormat pixelFormat,
byte[] sourceData,
float translucency = 0.0f)
{
dats.RegisterRootGfxObj(GfxObjId, BuildQuadGfxObj(TexturedSurfaceId, noPos: false));
dats.Register(TexturedSurfaceId, new Surface
{
Type = surfaceType,
OrigTextureId = SurfaceTextureId,
Translucency = translucency,
});
dats.Register(SurfaceTextureId, new SurfaceTexture
{
Textures = new List<QualifiedDataId<RenderSurface>> { RenderSurfaceId },
});
dats.Register(RenderSurfaceId, new RenderSurface
{
Width = 4,
Height = 4,
Format = pixelFormat,
SourceData = sourceData,
});
}
/// <summary>One quad (4 verts), single polygon, PosSurface referencing <paramref name="surfaceId"/>.</summary>
private static GfxObj BuildQuadGfxObj(uint surfaceId, bool noPos)
{

View file

@ -88,7 +88,7 @@ public sealed class PakEquivalenceTests {
var pakPath = Path.Combine(Path.GetTempPath(), $"acdream-equivtest-{System.Guid.NewGuid():N}.pak");
try {
var header = new PakHeader {
FormatVersion = 1,
FormatVersion = PakFormat.CurrentFormatVersion,
PortalIteration = (uint)dats.Portal.Iteration!.CurrentIteration,
CellIteration = (uint)dats.Cell.Iteration!.CurrentIteration,
HighResIteration = (uint)dats.HighRes.Iteration!.CurrentIteration,

View file

@ -50,7 +50,7 @@ public class PakRoundTripTests : IDisposable {
private static PakHeader WritePak(string path, (PakAssetType Type, uint FileId, ObjectMeshData Data)[] blobs) {
var header = new PakHeader {
FormatVersion = 1,
FormatVersion = PakFormat.CurrentFormatVersion,
PortalIteration = 10,
CellIteration = 20,
HighResIteration = 30,
@ -231,7 +231,8 @@ public class PakRoundTripTests : IDisposable {
[Theory]
[InlineData(0u)]
[InlineData(2u)]
[InlineData(1u)]
[InlineData(3u)]
public void Reader_RejectsWrongFormatVersion(uint wrongVersion) {
var path = NewTempPakPath();
WritePak(path, MakeBlobSet(2));
@ -339,7 +340,7 @@ public class PakRoundTripTests : IDisposable {
// Recompute CRC over the tampered blob region.
fs.Position = (long)entry.Offset;
var blobBytes = new byte[entry.Length];
var blobBytes = new byte[entry.StoredLength];
fs.ReadExactly(blobBytes);
uint newCrc = Crc32.Compute(blobBytes);

View file

@ -0,0 +1,292 @@
using System.Buffers.Binary;
using System.Security.Cryptography;
using AcDream.Content.Pak;
using Chorizite.Core.Render.Enums;
using DatReaderWriter.Enums;
using RetailCullMode = DatReaderWriter.Enums.CullMode;
namespace AcDream.Content.Tests;
public sealed class PakV2Tests : IDisposable
{
private readonly List<string> _paths = [];
[Fact]
public void BlobCodec_UsesRawFallbackAndCompressedRoundTripsExactly()
{
byte[] small = [1, 2, 3, 4];
PakBlobCodec.Encoded raw = PakBlobCodec.Encode(small);
Assert.False(raw.Compressed);
Assert.Equal(small, raw.Bytes);
byte[] repetitive = new byte[64 * 1024];
for (int i = 0; i < repetitive.Length; i++)
repetitive[i] = (byte)(i & 7);
PakBlobCodec.Encoded compressed = PakBlobCodec.Encode(repetitive);
Assert.True(compressed.Compressed);
Assert.True(compressed.Bytes.Length < repetitive.Length / 4);
Assert.Equal(repetitive, PakBlobCodec.Decode(compressed.Bytes));
}
[Theory]
[InlineData(new byte[] { })]
[InlineData(new byte[] { 1, 2, 3 })]
[InlineData(new byte[] { 4, 0, 0, 0, 255 })]
public void BlobCodec_RejectsMalformedCompressedPayload(byte[] stored) =>
Assert.Throws<InvalidDataException>(() => PakBlobCodec.Decode(stored));
[Fact]
public void BlobCodec_RejectsAllocationBombBeforeAllocating()
{
byte[] stored = new byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(
stored,
PakBlobCodec.MaximumDecodedBytes + 1u);
Assert.Throws<InvalidDataException>(() => PakBlobCodec.Decode(stored));
}
[Fact]
public void Writer_DeduplicatesTextureBytesGloballyAndReaderSharesArray()
{
string path = NewPath();
byte[] texture = new byte[16 * 1024];
for (int i = 0; i < texture.Length; i++)
texture[i] = (byte)(i & 15);
ObjectMeshData first = TexturedMesh(1, 0x0800_0001u, texture);
ObjectMeshData second = TexturedMesh(2, 0x0800_0002u, texture.ToArray());
using (var writer = NewWriter(path))
{
writer.AddBlob(PakKey.Compose(PakAssetType.GfxObjMesh, 1), first);
writer.AddBlob(PakKey.Compose(PakAssetType.GfxObjMesh, 2), second);
Assert.Equal(1, writer.TextureBlobCount);
Assert.Equal(3, writer.EntryCount);
writer.Finish();
}
using var reader = new PakReader(path);
Assert.Equal(1, reader.CountEntries(PakAssetType.TexturePayload));
Assert.True(reader.TryReadObjectMeshData(
PakKey.Compose(PakAssetType.GfxObjMesh, 1),
out ObjectMeshData? firstRead));
Assert.True(reader.TryReadObjectMeshData(
PakKey.Compose(PakAssetType.GfxObjMesh, 2),
out ObjectMeshData? secondRead));
ObjectMeshDataEquality.AssertEqual(first, firstRead);
ObjectMeshDataEquality.AssertEqual(second, secondRead);
Assert.Same(
firstRead!.TextureBatches.Single().Value.Single().TextureData,
secondRead!.TextureBatches.Single().Value.Single().TextureData);
}
[Fact]
public void Writer_IsByteDeterministicWithExternalTexturesAndCompression()
{
string firstPath = NewPath();
string secondPath = NewPath();
byte[] texture = Enumerable.Repeat((byte)0x5A, 32 * 1024).ToArray();
WriteDeterministic(firstPath, texture);
WriteDeterministic(secondPath, texture);
Assert.Equal(File.ReadAllBytes(firstPath), File.ReadAllBytes(secondPath));
}
[Fact]
public void CorruptTextureDemotesOnlyReferencingMesh()
{
string path = NewPath();
byte[] badTexture = Enumerable.Repeat((byte)0x11, 16 * 1024).ToArray();
byte[] goodTexture = Enumerable.Repeat((byte)0x22, 16 * 1024).ToArray();
ulong badMeshKey = PakKey.Compose(PakAssetType.GfxObjMesh, 1);
ulong goodMeshKey = PakKey.Compose(PakAssetType.GfxObjMesh, 2);
ulong badTextureKey = TexturePayloadKey(badTexture);
using (var writer = NewWriter(path))
{
writer.AddBlob(badMeshKey, TexturedMesh(1, 1, badTexture));
writer.AddBlob(goodMeshKey, TexturedMesh(2, 2, goodTexture));
writer.Finish();
}
PakTocEntry textureEntry;
using (var reader = new PakReader(path))
textureEntry = reader.GetTocEntryForTest(badTextureKey);
using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
{
stream.Position = (long)textureEntry.Offset + textureEntry.StoredLength / 2;
int value = stream.ReadByte();
stream.Position--;
stream.WriteByte((byte)(value ^ 0xFF));
}
using var corruptReader = new PakReader(path);
Assert.Equal(
PakObjectReadStatus.Corrupt,
corruptReader.ReadObjectMeshData(badMeshKey, out _));
Assert.Equal(PakEntryState.Corrupt, corruptReader.ProbeEntry(badTextureKey));
Assert.Equal(
PakObjectReadStatus.Loaded,
corruptReader.ReadObjectMeshData(goodMeshKey, out ObjectMeshData? good));
Assert.Equal(goodTexture, good!.TextureBatches.Single().Value.Single().TextureData);
}
[Fact]
public void Reader_DemotesInvalidCompressedStreamBehindValidCrc()
{
string path = NewPath();
ulong key = PakKey.Compose(PakAssetType.GfxObjCollision, 7);
using (var writer = NewWriter(path))
{
writer.AddBlob(key, new byte[32 * 1024]);
writer.Finish();
}
PakTocEntry entry;
long tocEntryPosition;
using (var reader = new PakReader(path))
{
entry = reader.GetTocEntryForTest(key);
Assert.True(entry.IsCompressed);
tocEntryPosition = FindTocEntryPosition(path, key);
}
byte[] invalid = new byte[checked((int)entry.StoredLength)];
BinaryPrimitives.WriteUInt32LittleEndian(invalid, 32 * 1024);
invalid.AsSpan(sizeof(uint)).Fill(0xFF);
using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
{
stream.Position = (long)entry.Offset;
stream.Write(invalid);
stream.Position = tocEntryPosition + 20;
Span<byte> crc = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(crc, Crc32.Compute(invalid));
stream.Write(crc);
}
using var corruptReader = new PakReader(path);
Assert.Equal(
PakObjectReadStatus.Corrupt,
corruptReader.ReadBlobBytes(key, out byte[]? bytes));
Assert.Null(bytes);
Assert.Equal(PakEntryState.Corrupt, corruptReader.ProbeEntry(key));
}
[Fact]
public void TextureCache_IsReferenceSharingAndStrictlyBounded()
{
var cache = new PakTexturePayloadCache(maximumBytes: 6, maximumEntries: 2);
byte[] first = [1, 2, 3, 4];
byte[] second = [5, 6, 7, 8];
Assert.Same(first, cache.AddOrGet(1, first));
Assert.Same(first, cache.AddOrGet(1, first.ToArray()));
Assert.Same(second, cache.AddOrGet(2, second));
Assert.False(cache.TryGet(1, out _));
Assert.True(cache.TryGet(2, out byte[] cachedSecond));
Assert.Same(second, cachedSecond);
Assert.Equal(1, cache.Count);
Assert.Equal(4, cache.Bytes);
byte[] tooLarge = new byte[7];
Assert.Same(tooLarge, cache.AddOrGet(3, tooLarge));
Assert.False(cache.TryGet(3, out _));
Assert.Equal(4, cache.Bytes);
}
private static ObjectMeshData TexturedMesh(
uint objectId,
uint surfaceId,
byte[] texture)
{
var mesh = new ObjectMeshData { ObjectId = objectId };
mesh.TextureBatches[(64, 64, TextureFormat.RGBA8)] =
[
new TextureBatchData
{
Key = new TextureKey
{
SurfaceId = surfaceId,
PaletteId = 1,
Stippling = StipplingType.Positive,
},
TextureData = texture,
Indices = [0, 1, 2],
CullMode = RetailCullMode.Clockwise,
},
];
return mesh;
}
private static PakWriter NewWriter(string path) =>
new(path, new PakHeader
{
PortalIteration = 1,
CellIteration = 2,
HighResIteration = 3,
LanguageIteration = 4,
});
private static void WriteDeterministic(string path, byte[] texture)
{
using var writer = NewWriter(path);
writer.AddBlob(
PakKey.Compose(PakAssetType.GfxObjMesh, 2),
TexturedMesh(2, 12, texture.ToArray()));
writer.AddBlob(
PakKey.Compose(PakAssetType.GfxObjMesh, 1),
TexturedMesh(1, 11, texture.ToArray()));
writer.Finish();
}
private static ulong TexturePayloadKey(byte[] bytes)
{
byte[] digest = SHA256.HashData(bytes);
ulong payloadId =
((ulong)digest[0] << 48)
| ((ulong)digest[1] << 40)
| ((ulong)digest[2] << 32)
| ((ulong)digest[3] << 24)
| ((ulong)digest[4] << 16)
| ((ulong)digest[5] << 8)
| digest[6];
return PakKey.ComposeOpaque(PakAssetType.TexturePayload, payloadId);
}
private string NewPath()
{
string path = Path.Combine(
Path.GetTempPath(),
$"acdream-pak-v2-{Guid.NewGuid():N}.pak");
_paths.Add(path);
return path;
}
private static long FindTocEntryPosition(string path, ulong key)
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
PakHeader header = PakHeader.ReadFrom(stream);
var bytes = new byte[PakTocEntry.Size];
for (uint i = 0; i < header.TocCount; i++)
{
long position = checked((long)header.TocOffset + i * PakTocEntry.Size);
stream.Position = position;
stream.ReadExactly(bytes);
if (PakTocEntry.ReadFrom(bytes).Key == key)
return position;
}
throw new KeyNotFoundException($"pak key 0x{key:X16} not found");
}
public void Dispose()
{
foreach (string path in _paths)
{
try { File.Delete(path); }
catch (IOException) { }
}
}
}

View file

@ -0,0 +1,29 @@
using AcDream.Launcher.Core.Installation;
namespace AcDream.Launcher.Core.Tests.Installation;
public sealed class ContentMigrationCatalogTests
{
[Fact]
public void RecipeFiveToSixRequiresOneExplicitFullRebuild()
{
ContentMigrationPlan plan = ContentMigrationCatalog.Resolve(5, 6);
Assert.Equal(ContentWorkKind.FullRebuild, plan.Kind);
Assert.Equal(5u, plan.FromRecipeVersion);
Assert.Equal(6u, plan.TargetRecipeVersion);
Assert.Contains("pak v2", plan.Reason, StringComparison.OrdinalIgnoreCase);
Assert.Empty(plan.EffectiveDatIds);
Assert.Empty(plan.EffectiveLandblocks);
}
[Fact]
public void AnyOlderRecipeToSixCollapsesToOneFullRebuild()
{
ContentMigrationPlan plan = ContentMigrationCatalog.Resolve(1, 6);
Assert.Equal(ContentWorkKind.FullRebuild, plan.Kind);
Assert.Equal(6u, plan.TargetRecipeVersion);
Assert.Contains("pak v2", plan.Reason, StringComparison.OrdinalIgnoreCase);
}
}

View file

@ -133,7 +133,7 @@ public sealed class LauncherContentStateStoreTests : IDisposable
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
byte[] bytes = new byte[64];
BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(0, 4), 0x4B504341u);
BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(4, 4), 1);
BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(4, 4), 2);
BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(8, 4), 100);
BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(12, 4), 200);
BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(16, 4), 300);

View file

@ -147,6 +147,33 @@ public sealed class LauncherInstallerTests : IDisposable
StringComparison.Ordinal);
}
[Fact]
public async Task InsufficientFreeSpaceStopsBeforeBakeAndReportsExactRequirement()
{
bool childStarted = false;
var runner = new FakeBakeProcessRunner((_, _, _) =>
{
childStarted = true;
return Task.FromResult(new BakeProcessResult(0, string.Empty));
});
var installer = new LauncherInstaller(
_paths,
_bakeExecutable,
processRunner: runner,
availableFreeSpace: _ =>
LauncherInstaller.FullRebuildRequiredFreeBytes - 1);
LauncherInstallException error = await Assert.ThrowsAsync<LauncherInstallException>(
() => installer.InstallAsync(_dats, threads: 2));
Assert.False(childStarted);
Assert.Contains("2.0 GiB", error.Message, StringComparison.Ordinal);
Assert.Contains("active package", error.Message, StringComparison.Ordinal);
Assert.False(File.Exists(
LauncherInstaller.GetFullRebuildCandidatePath(
Path.Combine(_paths.DataDirectory, "pak", "acdream.pak"))));
}
[Fact]
public async Task FullContentMigrationPersistsClientGateAcrossLauncherRestart()
{

View file

@ -58,8 +58,8 @@ public sealed class LauncherOverlayInstallerTests : IDisposable
request.OutputPath,
LauncherInstallRecordStore.CurrentBakeToolVersion);
long bytes = new FileInfo(request.OutputPath).Length;
output($"{{\"v\":1,\"e\":\"started\",\"bakeToolVersion\":5}}\n");
output($"{{\"v\":1,\"e\":\"completed\",\"bakeToolVersion\":5,"
output($"{{\"v\":1,\"e\":\"started\",\"bakeToolVersion\":{LauncherInstallRecordStore.CurrentBakeToolVersion}}}\n");
output($"{{\"v\":1,\"e\":\"completed\",\"bakeToolVersion\":{LauncherInstallRecordStore.CurrentBakeToolVersion},"
+ $"\"outputBytes\":{bytes},\"failures\":0}}\n");
await Task.Yield();
return new BakeProcessResult(0, string.Empty);
@ -70,8 +70,8 @@ public sealed class LauncherOverlayInstallerTests : IDisposable
recordStore: recordStore,
processRunner: runner);
var migration = new ContentMigrationPlan(
4,
5,
LauncherInstallRecordStore.CurrentBakeToolVersion - 1,
LauncherInstallRecordStore.CurrentBakeToolVersion,
ContentWorkKind.Overlay,
"bounded fixture update",
[0x01001234u, 0x02005678u],
@ -93,7 +93,9 @@ public sealed class LauncherOverlayInstallerTests : IDisposable
observed.Arguments.SkipWhile(value => value != "--landblocks").Take(2));
Assert.NotNull(result.Record.PreparedAssetOverlayPath);
Assert.True(File.Exists(result.Record.PreparedAssetOverlayPath));
Assert.Equal(5u, result.Record.ResolvedBakeToolVersion);
Assert.Equal(
LauncherInstallRecordStore.CurrentBakeToolVersion,
result.Record.ResolvedBakeToolVersion);
Assert.True(result.Record.RequiresClientCompatibilityConfirmation);
Assert.False(File.Exists(
new LauncherContentStateStore(_paths).OverlayCandidatePath));
@ -145,7 +147,9 @@ public sealed class LauncherOverlayInstallerTests : IDisposable
TaskCreationOptions.RunContinuationsAsynchronously);
var runner = new FakeRunner(async (request, _, cancellationToken) =>
{
LauncherContentStateStoreTests.WritePakHeader(request.OutputPath, 5);
LauncherContentStateStoreTests.WritePakHeader(
request.OutputPath,
LauncherInstallRecordStore.CurrentBakeToolVersion);
entered.SetResult();
await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
return new BakeProcessResult(0, string.Empty);
@ -160,8 +164,8 @@ public sealed class LauncherOverlayInstallerTests : IDisposable
_dats,
2,
new ContentMigrationPlan(
4,
5,
LauncherInstallRecordStore.CurrentBakeToolVersion - 1,
LauncherInstallRecordStore.CurrentBakeToolVersion,
ContentWorkKind.Overlay,
"bounded fixture update",
[0x01001234u]),
@ -189,11 +193,11 @@ public sealed class LauncherOverlayInstallerTests : IDisposable
{
LauncherContentStateStoreTests.WritePakHeader(
recordStore.PreparedAssetPath,
recipe: 4);
recipe: LauncherInstallRecordStore.CurrentBakeToolVersion - 1);
LauncherInstallRecord baseRecord =
await LauncherContentStateStoreTests.RecordAsync(
recordStore.PreparedAssetPath,
recipe: 4,
recipe: LauncherInstallRecordStore.CurrentBakeToolVersion - 1,
datDirectory: Path.GetFullPath(_dats));
Directory.CreateDirectory(_paths.DataDirectory);
await File.WriteAllTextAsync(

View file

@ -142,6 +142,7 @@ public sealed class LauncherWindowViewModelTests
Assert.Equal("World data update required", viewModel.InstallationBannerTitle);
Assert.Contains("complete replacement pak", viewModel.FirstRunWizardShell.Body);
Assert.Contains("existing package stays", viewModel.FirstRunWizardShell.Body);
Assert.Contains("2 GiB", viewModel.FirstRunWizardShell.Body);
Assert.Equal("Rebuild world data", viewModel.FirstRunWizardShell.StartActionText);
Assert.Null(installer.InstallRequest);