diff --git a/src/Blocktavius.AppDQB2/PlanScriptDialog.xaml.cs b/src/Blocktavius.AppDQB2/PlanScriptDialog.xaml.cs index c4c08a1..9cfb30a 100644 --- a/src/Blocktavius.AppDQB2/PlanScriptDialog.xaml.cs +++ b/src/Blocktavius.AppDQB2/PlanScriptDialog.xaml.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -244,7 +245,8 @@ private void UpdatePlan() TryPlanSimpleCopy(sourceSlot, "AUTOCMNDAT.BIN", mode == InclusionMode.Automatic); TryPlanSimpleCopy(sourceSlot, "AUTOSTGDAT.BIN", mode == InclusionMode.Automatic); - TryPlanSimpleCopy(sourceSlot, "CMNDAT.BIN", mode == InclusionMode.Automatic || mode == InclusionMode.JustCmndat, forceBackup: true); + //TryPlanSimpleCopy(sourceSlot, "CMNDAT.BIN", mode == InclusionMode.Automatic || mode == InclusionMode.JustCmndat, forceBackup: true); + TryPlanHackyCmndatEdit(sourceSlot); var sortedStages = sourceSlot.Stages .OrderBy(stage => stage == Project.GetSelectedSourceStage ? 0 : 1) @@ -314,6 +316,20 @@ private bool TryPlanSimpleCopy(SlotVM sourceSlot, string name, bool shouldCopy, return false; } + private bool TryPlanHackyCmndatEdit(SlotVM sourceSlot) + { + var sourceFile = new FileInfo(sourceSlot.GetFullPath("CMNDAT.BIN")); + if (sourceFile.Exists) + { + PlanItems.Add(new CmndatHackPlan + { + BackupDir = this.backupLocation, + }); + return true; + } + return false; + } + public async Task Execute() { if (!CanExecute) return; @@ -629,6 +645,125 @@ await Task.Run(() => } } + class CmndatHackPlan : PlanItemVM, IPlanItemVM + { + public bool WillBeBackedUp => BackupDir != null; + + public bool WillBeModified => true; + + public bool WillBeCopied => true; + + public string ShortName => "CMNDAT.BIN"; + + const int headerLength = 0x2A444; + + public async Task Execute(ExecutionContext context) + { + Status = "workinonit..."; + + var srcPath = context.FromSlot.GetFullPath("CMNDAT.BIN"); + + using var cmndatStream = new FileStream(srcPath, FileMode.Open, FileAccess.Read); + + var header = new byte[headerLength]; + await cmndatStream.ReadExactlyAsync(header, offset: 0, count: headerLength); + + using var zlib = new System.IO.Compression.ZLibStream(cmndatStream, System.IO.Compression.CompressionMode.Decompress); + + const int decompressedLength = 5627194; // hypothesis: The decompressed buffer will always have this length + using var decompressedStream = new MemoryStream(decompressedLength); + await zlib.CopyToAsync(decompressedStream); + await zlib.FlushAsync(); + await decompressedStream.FlushAsync(); + + if (decompressedStream.Length != decompressedLength && System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Break(); // decompressedLength hypothesis invalidated? + } + + Memory data; + if (decompressedStream.TryGetBuffer(out var buffer)) + { + data = buffer.AsMemory(); + } + else + { + data = new Memory(decompressedStream.ToArray()); + } + + var mapData = data.Slice(2401803); // skip to start of first island's minimap data + + const int INTRO_SIZE = 0; + const int TILE_DATA_SIZE = 256 * 256 * 2; + const int OUTRO_SIZE = 4; + const int ISLAND_DATA_SIZE = INTRO_SIZE + TILE_DATA_SIZE + OUTRO_SIZE; + + const int islandId = 13; + const int start = INTRO_SIZE + ISLAND_DATA_SIZE * islandId; + var span = mapData.Slice(start, TILE_DATA_SIZE + OUTRO_SIZE).Span; + + var tile = GetTileToCopy(span); + // The chunk with NW corner at 800,800 is in-bounds on the IoA. + // Tile offsets for this chunk are 100,100 -> 104,104. + for (int z = 100; z < 104; z++) + { + for (int x = 100; x < 104; x++) + { + int offset = GetOffset(x, z); + span[offset] = tile.Item1; + span[offset + 1] = tile.Item2; + } + } + + /* + // What happens if we use "outro" bytes from the IoA? + var existingOutro = Convert.ToHexString(span.Slice(TILE_DATA_SIZE, OUTRO_SIZE)); + span[TILE_DATA_SIZE + 0] = 0x7E; + span[TILE_DATA_SIZE + 1] = 0x80; + span[TILE_DATA_SIZE + 2] = 0x01; + span[TILE_DATA_SIZE + 3] = 0x1E; + var replacedOutro = Convert.ToHexString(span.Slice(TILE_DATA_SIZE, OUTRO_SIZE)); + */ + + Status = "Logic done, saving..."; + + await WriteCmndatFile(header, data, context.ToSlot.GetFullPath("CMNDAT.BIN")); + + Status = "Minimap modified!"; + } + + private (byte, byte) GetTileToCopy(ReadOnlySpan span) + { + // grab whatever tile is in the center of the minimap + const int x = 1024 / 8; + const int z = 1024 / 8; + int offset = GetOffset(x, z); + return (span[offset], span[offset + 1]); + } + + private static int GetOffset(int x, int z) => z * 256 * 2 + x * 2; + + private static async Task WriteCmndatFile(byte[] origHeader, Memory uncompressedContent, string dstPath) + { + using var cmndatStream = new FileStream(dstPath, FileMode.Create, FileAccess.Write); + await cmndatStream.WriteAsync(origHeader); + + using var compressedBody = new MemoryStream(); + using (var zlib = new ZLibStream(compressedBody, CompressionMode.Compress, leaveOpen: true)) + { + await zlib.WriteAsync(uncompressedContent); + await zlib.FlushAsync(); + compressedBody.Flush(); + } + + compressedBody.Seek(0, SeekOrigin.Begin); + await compressedBody.CopyToAsync(cmndatStream); + + await cmndatStream.FlushAsync(); + cmndatStream.Close(); + } + } + class CopyWithModificationsPlanItemVM : PlanItemVM, IPlanItemVM { public required bool DestIsSource { get; init; } diff --git a/src/Blocktavius.DQB2/Minimap.cs b/src/Blocktavius.DQB2/Minimap.cs index f64e54a..823ccf4 100644 --- a/src/Blocktavius.DQB2/Minimap.cs +++ b/src/Blocktavius.DQB2/Minimap.cs @@ -66,6 +66,19 @@ public I2DSampler ReadMapCropped(int islandId, IStage cropper, IRea { var sampler = ReadMap(islandId); + // TEMP INTRO/OUTRO DEBUGGING CODE: + var start = INTRO_SIZE + ISLAND_DATA_SIZE * islandId; + string introHex = "[nope]"; + if (start > 0) + { + var introBytes = data.Slice(start - OUTRO_SIZE, OUTRO_SIZE).ToArray(); + introHex = Convert.ToHexString(introBytes); + } + var end = start + TILE_DATA_SIZE; + var outroBytes = data.Slice(end, OUTRO_SIZE).ToArray(); + var outroHex = Convert.ToHexString(outroBytes); + var debugString = $"Island {islandId}, intro {introHex}, outro {outroHex}"; + // Minimap tile grid is 256x256; chunk grid is 64x64. // This means there are 4x4 map tiles in each chunk. const int scale = 4;