Skip to content

Commit 956bcc1

Browse files
committed
Add zone header inspector & UI/CLI support
Introduce a shared ZoneHeaderInspector that parses and validates XFile / XAssetList headers and returns a structured ZoneHeaderReport (fields, statuses, overall health). Integrate the inspector into the editor: FileReportForm now uses the inspector to render validated field lines, and MainWindowForm adds a zone header summary panel, colored/sectioned DataGridView rows, and improved column/row styling. Add a new ffcli command (zoneheader / zh) to validate zone headers from the CLI with optional JSON output. This centralises header validation (memalloc magic values, asset counts, pointer placeholders) so the editor, report form and CLI present identical diagnostics.
1 parent d2082f2 commit 956bcc1

5 files changed

Lines changed: 584 additions & 51 deletions

File tree

Call of Duty FastFile Editor/UI/FileReportForm.cs

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -424,49 +424,62 @@ private Section BuildZoneHeaderSection()
424424
{
425425
var zone = _fastFile.OpenedFastFileZone;
426426
int hdrSize = FastFileConstants.GetZoneHeaderSize(
427-
_fastFile.IsCod4File ? GameVersion.CoD4 :
428-
_fastFile.IsCod5File ? GameVersion.WaW :
429-
_fastFile.IsMW2File ? GameVersion.MW2 : GameVersion.Unknown,
430-
_isXbox360, _isPC);
427+
_fastFile.GameVersionEnum, _isXbox360, _isPC, _fastFile.IsWii);
431428

429+
int parsedAssetCount = zone?.ZoneFileAssets?.ZoneAssetRecords?.Count ?? 0;
430+
var report = ZoneHeaderInspector.Build(
431+
_zoneBytes,
432+
_fastFile.GameVersionEnum,
433+
_isXbox360, _isPC, _fastFile.IsWii, _fastFile.IsGhostsFile,
434+
_zoneBytes.LongLength,
435+
parsedAssetCount);
436+
437+
// One validated line per field, e.g. "[CRIT] 0x08 BlockSizeTemp : 0x.. (12,345) — expected 0x.."
432438
var fields = new StringBuilder();
433-
fields.AppendLine("XFile (memory block allocation):");
434-
fields.AppendLine($" 0x00 ZoneSize : 0x{zone.ZoneSize:X8} ({zone.ZoneSize:N0})");
435-
fields.AppendLine($" 0x04 ExternalSize : 0x{zone.ExternalSize:X8}");
436-
fields.AppendLine($" 0x08 BlockSizeTemp : 0x{zone.BlockSizeTemp:X8}");
437-
fields.AppendLine($" 0x0C BlockSizePhysical: 0x{zone.BlockSizePhysical:X8}");
438-
fields.AppendLine($" 0x10 BlockSizeRuntime : 0x{zone.BlockSizeRuntime:X8}");
439-
fields.AppendLine($" 0x14 BlockSizeVirtual : 0x{zone.BlockSizeVirtual:X8}");
440-
fields.AppendLine($" 0x18 BlockSizeLarge : 0x{zone.BlockSizeLarge:X8}");
441-
fields.AppendLine($" 0x1C BlockSizeCallback: 0x{zone.BlockSizeCallback:X8}");
442-
if (hdrSize >= 0x34)
443-
fields.AppendLine($" 0x20 BlockSizeVertex : 0x{zone.BlockSizeVertex:X8}");
444-
fields.AppendLine();
445-
fields.AppendLine("XAssetList:");
446-
int slOffset = FastFileConstants.GetScriptStringCountOffset(
447-
_fastFile.IsCod4File ? GameVersion.CoD4 :
448-
_fastFile.IsCod5File ? GameVersion.WaW :
449-
_fastFile.IsMW2File ? GameVersion.MW2 : GameVersion.Unknown,
450-
_isXbox360, _isPC);
451-
fields.AppendLine($" 0x{slOffset:X2} ScriptStringCount: {zone.ScriptStringCount}");
452-
fields.AppendLine($" 0x{slOffset + 4:X2} ScriptStringsPtr : 0x{zone.ScriptStringsPtr:X8}");
453-
fields.AppendLine($" 0x{slOffset + 8:X2} AssetCount : {zone.AssetCount}");
454-
fields.AppendLine($" 0x{slOffset + 12:X2} AssetsPtr : 0x{zone.AssetsPtr:X8}");
439+
foreach (var f in report.Fields)
440+
{
441+
if (f.Severity == ZoneFieldSeverity.Section)
442+
{
443+
fields.AppendLine();
444+
fields.AppendLine(f.Name);
445+
continue;
446+
}
447+
448+
string marker = f.Severity switch
449+
{
450+
ZoneFieldSeverity.Good => "[ OK ]",
451+
ZoneFieldSeverity.Warning => "[WARN]",
452+
ZoneFieldSeverity.Critical => "[CRIT]",
453+
_ => " "
454+
};
455+
fields.Append($" {marker} {f.Offset,-5} {f.Name,-18}: {f.Hex} ({f.Decimal})");
456+
if (!string.IsNullOrWhiteSpace(f.Status))
457+
fields.Append($" — {f.Status}");
458+
fields.AppendLine();
459+
}
455460

456461
var summary = new StringBuilder();
457-
summary.AppendLine($"Zone header size: {hdrSize} bytes (0x{hdrSize:X})");
458-
summary.AppendLine($"Asset count: {zone.AssetCount}");
459-
summary.AppendLine($"Script string count: {zone.ScriptStringCount}");
462+
summary.AppendLine(report.Headline);
463+
summary.AppendLine();
464+
string banner = report.OverallSeverity switch
465+
{
466+
ZoneFieldSeverity.Good => "OK: ",
467+
ZoneFieldSeverity.Warning => "WARNING: ",
468+
ZoneFieldSeverity.Critical => "CRITICAL: ",
469+
_ => ""
470+
};
471+
summary.AppendLine(banner + report.Health);
460472

461-
// MemAlloc validation
462-
uint expectedMemAlloc1 = _fastFile.IsCod4File ? CoD4Definition.MemAlloc1Value
463-
: _fastFile.IsCod5File ? (_isXbox360 ? CoD5Definition.Xbox360MemAlloc1Value : CoD5Definition.MemAlloc1Value)
464-
: _fastFile.IsMW2File ? MW2Definition.MemAlloc1Value
465-
: 0;
466-
if (expectedMemAlloc1 != 0 && zone.BlockSizeTemp != expectedMemAlloc1)
473+
// List the fields that need attention so the right-hand pane is actionable.
474+
var flagged = report.Fields
475+
.Where(f => f.Severity == ZoneFieldSeverity.Warning || f.Severity == ZoneFieldSeverity.Critical)
476+
.ToList();
477+
if (flagged.Count > 0)
467478
{
468479
summary.AppendLine();
469-
summary.AppendLine($"WARNING: BlockSizeTemp 0x{zone.BlockSizeTemp:X} != expected 0x{expectedMemAlloc1:X}");
480+
summary.AppendLine("Issues:");
481+
foreach (var f in flagged)
482+
summary.AppendLine($" - {f.Name}: {f.Status}");
470483
}
471484

472485
return new Section

Call of Duty FastFile Editor/UI/MainWindowForm.Designer.cs

Lines changed: 35 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Call of Duty FastFile Editor/UI/MainWindowForm.cs

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,8 +1836,16 @@ private void PerformRawFileRename()
18361836
ReloadAllRawFileNodesAndUI();
18371837
}
18381838

1839+
// Row tint colours for the Zone Header grid, keyed off field severity.
1840+
private static readonly Color ZoneSectionBack = Color.FromArgb(225, 231, 239);
1841+
private static readonly Color ZoneGoodBack = Color.FromArgb(231, 247, 231);
1842+
private static readonly Color ZoneWarnBack = Color.FromArgb(255, 247, 224);
1843+
private static readonly Color ZoneCriticalBack = Color.FromArgb(253, 231, 231);
1844+
18391845
/// <summary>
1840-
/// Populates the DataGridView with Zone decimal values.
1846+
/// Renders the zone header as a sectioned, validated report: a health banner at the top
1847+
/// and one annotated row per field, with the crash-critical fields (MemAlloc values, asset
1848+
/// count, pointer placeholders) highlighted when their values are wrong.
18411849
/// </summary>
18421850
private void LoadZoneHeaderValues(ZoneFile zone)
18431851
{
@@ -1846,17 +1854,94 @@ private void LoadZoneHeaderValues(ZoneFile zone)
18461854
_openedFastFile.OpenedFastFileZone.ReadHeaderFields();
18471855
}
18481856

1849-
// Convert the dictionary to a list of objects with matching property names
1850-
var dataSource = zone.HeaderFieldValues.Select(kvp => new
1857+
zone ??= _openedFastFile.OpenedFastFileZone;
1858+
int parsedAssetCount = zone?.ZoneFileAssets?.ZoneAssetRecords?.Count ?? 0;
1859+
var report = ZoneHeaderInspector.Build(
1860+
zone?.Data,
1861+
_openedFastFile.GameVersionEnum,
1862+
_openedFastFile.IsXbox360,
1863+
_openedFastFile.IsPC,
1864+
_openedFastFile.IsWii,
1865+
_openedFastFile.IsGhostsFile,
1866+
zone?.Data?.LongLength ?? 0,
1867+
parsedAssetCount);
1868+
1869+
// --- Health banner ---
1870+
string glyph = report.OverallSeverity switch
1871+
{
1872+
ZoneFieldSeverity.Good => "✓ ",
1873+
ZoneFieldSeverity.Warning => "⚠ ",
1874+
ZoneFieldSeverity.Critical => "✕ ",
1875+
_ => ""
1876+
};
1877+
zoneHeaderSummaryLabel.Font = new Font("Segoe UI", 9.75f, FontStyle.Regular);
1878+
zoneHeaderSummaryLabel.Text = report.Headline + Environment.NewLine + glyph + report.Health;
1879+
(zoneHeaderSummaryPanel.BackColor, zoneHeaderSummaryLabel.ForeColor) = report.OverallSeverity switch
1880+
{
1881+
ZoneFieldSeverity.Good => (ZoneGoodBack, Color.FromArgb(27, 94, 32)),
1882+
ZoneFieldSeverity.Warning => (ZoneWarnBack, Color.FromArgb(124, 89, 0)),
1883+
ZoneFieldSeverity.Critical => (ZoneCriticalBack, Color.FromArgb(155, 28, 28)),
1884+
_ => (SystemColors.Control, SystemColors.ControlText)
1885+
};
1886+
1887+
// --- Grid columns (built once) ---
1888+
if (zoneInfoDataGridView.Columns.Count == 0)
1889+
{
1890+
void AddCol(string name, string header, int width, DataGridViewAutoSizeColumnMode mode = DataGridViewAutoSizeColumnMode.None)
1891+
{
1892+
var col = new DataGridViewTextBoxColumn
1893+
{
1894+
Name = name,
1895+
HeaderText = header,
1896+
Width = width,
1897+
AutoSizeMode = mode,
1898+
SortMode = DataGridViewColumnSortMode.NotSortable,
1899+
Resizable = DataGridViewTriState.True
1900+
};
1901+
zoneInfoDataGridView.Columns.Add(col);
1902+
}
1903+
1904+
AddCol("Field", "Field", 165);
1905+
AddCol("Decimal", "Decimal", 120);
1906+
AddCol("Hex", "Hex", 110);
1907+
AddCol("Offset", "Offset", 70);
1908+
AddCol("Meaning", "Meaning", 0, DataGridViewAutoSizeColumnMode.Fill);
1909+
AddCol("Status", "Status", 300);
1910+
1911+
zoneInfoDataGridView.Columns["Decimal"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
1912+
zoneInfoDataGridView.Columns["Hex"].DefaultCellStyle.Font = new Font("Consolas", 9f);
1913+
zoneInfoDataGridView.Columns["Meaning"].DefaultCellStyle.WrapMode = DataGridViewTriState.True;
1914+
zoneInfoDataGridView.Columns["Status"].DefaultCellStyle.WrapMode = DataGridViewTriState.True;
1915+
zoneInfoDataGridView.ColumnHeadersDefaultCellStyle.Font = new Font("Segoe UI", 9f, FontStyle.Bold);
1916+
zoneInfoDataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
1917+
}
1918+
1919+
// --- Rows ---
1920+
zoneInfoDataGridView.Rows.Clear();
1921+
foreach (var field in report.Fields)
18511922
{
1852-
ZoneName = kvp.Key,
1853-
ZoneDecValue = kvp.Value,
1854-
ZoneHexValue = Utilities.ConvertToBigEndianHex(kvp.Value),
1855-
ZoneOffset = _openedFastFile.OpenedFastFileZone.GetZoneOffset(kvp.Key)
1856-
}).ToList();
1923+
int index = zoneInfoDataGridView.Rows.Add(
1924+
field.Name, field.Decimal, field.Hex, field.Offset, field.Meaning, field.Status);
1925+
var row = zoneInfoDataGridView.Rows[index];
18571926

1858-
// Assign the data source to the DataGridView
1859-
zoneInfoDataGridView.DataSource = dataSource;
1927+
switch (field.Severity)
1928+
{
1929+
case ZoneFieldSeverity.Section:
1930+
row.DefaultCellStyle.BackColor = ZoneSectionBack;
1931+
row.DefaultCellStyle.Font = new Font("Segoe UI", 9f, FontStyle.Bold);
1932+
break;
1933+
case ZoneFieldSeverity.Good:
1934+
row.DefaultCellStyle.BackColor = ZoneGoodBack;
1935+
break;
1936+
case ZoneFieldSeverity.Warning:
1937+
row.DefaultCellStyle.BackColor = ZoneWarnBack;
1938+
break;
1939+
case ZoneFieldSeverity.Critical:
1940+
row.DefaultCellStyle.BackColor = ZoneCriticalBack;
1941+
row.DefaultCellStyle.Font = new Font("Segoe UI", 9f, FontStyle.Bold);
1942+
break;
1943+
}
1944+
}
18601945
}
18611946

18621947
/// <summary>

0 commit comments

Comments
 (0)