Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 136 additions & 1 deletion src/Blocktavius.AppDQB2/PlanScriptDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<byte> data;
if (decompressedStream.TryGetBuffer(out var buffer))
{
data = buffer.AsMemory();
}
else
{
data = new Memory<byte>(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<byte> 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<byte> 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; }
Expand Down
13 changes: 13 additions & 0 deletions src/Blocktavius.DQB2/Minimap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ public I2DSampler<MinimapTile> 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;
Expand Down