Skip to content

Commit f02ee0c

Browse files
committed
Add StructuredDataDef viewer and parsing
Introduce support for IW4/MW2 StructuredDataDef assets: add StructuredDataDefAsset model (name, offset, counts, DumpText), wire parsing in Iw4AssetBridge (convert StructuredDataDefSet -> model and render human-readable dumps), and expose a new "Struct Data" tab in MainWindowForm with a list and double-click viewer. Add a read-only StructuredDataDefViewerForm to display/copy the layout dump. Extend game definitions: add IsStructuredDataDefType to IGameDefinition and GameDefinitionBase, and implement recognition in MW2GameDefinition with platform-specific asset IDs. Register the new viewer form in the project file. The feature is read-only and relies on the IW4 pointer-walk reader to produce layout dumps (available for MW2 PS3 via the walker).
1 parent 8a40df6 commit f02ee0c

8 files changed

Lines changed: 315 additions & 1 deletion

File tree

Call of Duty FastFile Editor/Call of Duty FastFile Editor.csproj.user

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
<Compile Update="UI\StringTableViewerForm.cs">
3838
<SubType>Form</SubType>
3939
</Compile>
40+
<Compile Update="UI\StructuredDataDefViewerForm.cs">
41+
<SubType>Form</SubType>
42+
</Compile>
4043
<Compile Update="UI\SupportedFormatsForm.cs">
4144
<SubType>Form</SubType>
4245
</Compile>

Call of Duty FastFile Editor/GameDefinitions/GameDefinitionBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public abstract class GameDefinitionBase : IGameDefinition
2828
"stringtable",
2929
"weapon",
3030
"image",
31+
"structureddatadef",
3132
"col_map_sp",
3233
"col_map_mp"
3334
};
@@ -55,6 +56,7 @@ public abstract class GameDefinitionBase : IGameDefinition
5556
public virtual bool IsXAnimType(int assetType) => assetType == XAnimAssetType;
5657
public virtual bool IsMaterialType(int assetType) => false; // Override in game-specific definitions
5758
public virtual bool IsTechSetType(int assetType) => false; // Override in game-specific definitions
59+
public virtual bool IsStructuredDataDefType(int assetType) => false; // Override in game-specific definitions
5860
public virtual bool IsStringTableType(int assetType) => assetType == StringTableAssetType;
5961
public virtual bool IsWeaponType(int assetType) => WeaponAssetType != 0 && assetType == WeaponAssetType;
6062
public virtual bool IsImageType(int assetType) => ImageAssetType != 0 && assetType == ImageAssetType;

Call of Duty FastFile Editor/GameDefinitions/IGameDefinition.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ public interface IGameDefinition
153153
/// </summary>
154154
bool IsStringTableType(int assetType);
155155

156+
/// <summary>
157+
/// Checks if the given asset type value is a structureddatadef.
158+
/// </summary>
159+
bool IsStructuredDataDefType(int assetType);
160+
156161
/// <summary>
157162
/// Parses a material asset from the zone data at the given offset.
158163
/// </summary>

Call of Duty FastFile Editor/GameDefinitions/MW2GameDefinition.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ public MW2GameDefinition(bool isXbox360, bool isPC = false)
8787
/// </summary>
8888
public override bool IsTechSetType(int assetType) => assetType == TechSetAssetType;
8989

90+
/// <summary>
91+
/// MW2 StructuredDataDef asset type ID. PS3 = 0x26, Xbox 360 = 0x25, PC = 0x27.
92+
/// </summary>
93+
public byte StructuredDataDefAssetType => IsPC
94+
? (byte)MW2AssetTypePC.structureddatadef
95+
: (IsXbox360 ? (byte)MW2AssetTypeXbox360.structureddatadef : (byte)MW2AssetTypePS3.structureddatadef);
96+
97+
/// <summary>
98+
/// Recognise structureddatadef records so the Asset Pool view names them. For MW2 PS3 the
99+
/// def layout is dumped from the IW4 pointer-walk; other platforms have no parser yet.
100+
/// </summary>
101+
public override bool IsStructuredDataDefType(int assetType) => assetType == StructuredDataDefAssetType;
102+
90103
public override string GetAssetTypeName(int assetType)
91104
{
92105
if (IsPC)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace Call_of_Duty_FastFile_Editor.Models
2+
{
3+
/// <summary>
4+
/// Editor-side view model for a StructuredDataDefSet asset (MW2 / IW4).
5+
///
6+
/// The structureddatadef asset stores the data-structure + enum layouts the game uses (defined
7+
/// under raw/mp/*.def). The original source format isn't shipped, so this is a read-only
8+
/// <b>dump</b> of the parsed layout: each def's enums (entry name = enum value), structs
9+
/// (property name : type @ byte offset), indexed/enumed arrays, and the root type. The IW4
10+
/// pointer-walk reader (<c>FastFileLib.Iw4.StructuredDataReader</c>) produces the structure;
11+
/// <c>Iw4AssetBridge</c> renders <see cref="DumpText"/> from it.
12+
/// </summary>
13+
public class StructuredDataDefAsset
14+
{
15+
/// <summary>DefSet name (e.g. <c>mp/playerconstantdata.def</c>).</summary>
16+
public string Name { get; set; } = string.Empty;
17+
18+
/// <summary>Zone byte offset of the DefSet asset header.</summary>
19+
public int Offset { get; set; }
20+
21+
/// <summary>Number of <c>StructuredDataDef</c>s in the set.</summary>
22+
public int DefCount { get; set; }
23+
24+
/// <summary>Total enums across all defs (for the list summary).</summary>
25+
public int EnumCount { get; set; }
26+
27+
/// <summary>Total structs across all defs (for the list summary).</summary>
28+
public int StructCount { get; set; }
29+
30+
/// <summary>Pre-rendered, human-readable dump of the parsed layout (shown in the viewer).</summary>
31+
public string DumpText { get; set; } = string.Empty;
32+
33+
public override string ToString() => Name;
34+
}
35+
}

Call of Duty FastFile Editor/Services/Iw4AssetBridge.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public sealed class Iw4BridgeResult
1515
public List<WeaponAsset> Weapons { get; } = new();
1616
public List<TechSetAsset> TechSets { get; } = new();
1717
public List<MenuList> MenuLists { get; } = new();
18+
public List<StructuredDataDefAsset> StructuredDataDefs { get; } = new();
1819
}
1920

2021
/// <summary>
@@ -97,6 +98,9 @@ public static class Iw4AssetBridge
9798
case FastFileLib.Iw4.MenuList ml:
9899
iw4MenuLists.Add(ml);
99100
break;
101+
case FastFileLib.Iw4.StructuredDataDefSet sd:
102+
result.StructuredDataDefs.Add(ToStructuredDataDef(sd));
103+
break;
100104
}
101105
}
102106

@@ -444,5 +448,111 @@ private static TechSetAsset ToTechSet(FastFileLib.Iw4.MaterialTechniqueSet ts)
444448
AdditionalData = "IW4 pointer-walk",
445449
};
446450
}
451+
452+
// ---- StructuredDataDef (IW4 raw/mp/*.def layout dump) ----
453+
454+
private static StructuredDataDefAsset ToStructuredDataDef(FastFileLib.Iw4.StructuredDataDefSet sd)
455+
{
456+
var defs = sd.DefsPtr is { IsResolved: true, Result: not null }
457+
? sd.DefsPtr.Result
458+
: Array.Empty<FastFileLib.Iw4.StructuredDataDef>();
459+
460+
return new StructuredDataDefAsset
461+
{
462+
Name = sd.Name,
463+
Offset = sd.Offset,
464+
DefCount = sd.DefCount,
465+
EnumCount = defs.Sum(d => d.EnumCount),
466+
StructCount = defs.Sum(d => d.StructCount),
467+
DumpText = RenderStructuredDataDump(sd, defs),
468+
};
469+
}
470+
471+
/// <summary>Renders a StructuredDataType (the spec's tagged union) to a readable type string.</summary>
472+
private static string TypeString(FastFileLib.Iw4.StructuredDataType? t)
473+
{
474+
if (t == null) return "?";
475+
return t.Type switch
476+
{
477+
FastFileLib.Iw4.StructuredDataTypeCategory.DataInt => "int",
478+
FastFileLib.Iw4.StructuredDataTypeCategory.DataByte => "byte",
479+
FastFileLib.Iw4.StructuredDataTypeCategory.DataBool => "bool",
480+
FastFileLib.Iw4.StructuredDataTypeCategory.DataString => $"string[{t.UnionValue}]",
481+
FastFileLib.Iw4.StructuredDataTypeCategory.DataEnum => $"enum[{t.UnionValue}]",
482+
FastFileLib.Iw4.StructuredDataTypeCategory.DataStruct => $"struct[{t.UnionValue}]",
483+
FastFileLib.Iw4.StructuredDataTypeCategory.DataIndexedArray => $"indexedArray[{t.UnionValue}]",
484+
FastFileLib.Iw4.StructuredDataTypeCategory.DataEnumArray => $"enumArray[{t.UnionValue}]",
485+
FastFileLib.Iw4.StructuredDataTypeCategory.DataFloat => "float",
486+
FastFileLib.Iw4.StructuredDataTypeCategory.DataShort => "short",
487+
FastFileLib.Iw4.StructuredDataTypeCategory.DataCount => "count",
488+
_ => $"type{(int)t.Type}",
489+
};
490+
}
491+
492+
private static T[] Resolve<T>(FastFileLib.Iw4.ZonePointer<T[]>? p)
493+
=> p is { IsResolved: true, Result: not null } ? p.Result : Array.Empty<T>();
494+
495+
private static string RenderStructuredDataDump(
496+
FastFileLib.Iw4.StructuredDataDefSet sd, FastFileLib.Iw4.StructuredDataDef[] defs)
497+
{
498+
var sb = new StringBuilder();
499+
sb.AppendLine($"// {sd.Name}");
500+
sb.AppendLine($"// StructuredDataDefSet — defCount = {sd.DefCount}");
501+
sb.AppendLine();
502+
503+
for (int di = 0; di < defs.Length; di++)
504+
{
505+
var def = defs[di];
506+
sb.AppendLine($"def #{di} version={def.Version} formatChecksum=0x{def.FormatChecksum:X8} size={def.Size}");
507+
508+
var enums = Resolve(def.EnumsPtr);
509+
sb.AppendLine($" enums ({enums.Length}):");
510+
for (int ei = 0; ei < enums.Length; ei++)
511+
{
512+
var entries = Resolve(enums[ei].EntriesPtr);
513+
sb.AppendLine($" enum[{ei}] ({entries.Length} entries):");
514+
foreach (var entry in entries)
515+
sb.AppendLine($" {S(entry.StringPtr)} = {entry.Index}");
516+
}
517+
518+
var structs = Resolve(def.StructsPtr);
519+
sb.AppendLine($" structs ({structs.Length}):");
520+
for (int si = 0; si < structs.Length; si++)
521+
{
522+
var st = structs[si];
523+
var props = Resolve(st.PropertiesPtr);
524+
sb.AppendLine($" struct[{si}] size={st.Size} bitOffset={st.BitOffset} ({props.Length} properties):");
525+
foreach (var prop in props)
526+
sb.AppendLine($" +0x{prop.Offset:X} {S(prop.NamePtr)} : {TypeString(prop.Type)}");
527+
}
528+
529+
var indexedArrays = Resolve(def.IndexedArraysPtr);
530+
if (indexedArrays.Length > 0)
531+
{
532+
sb.AppendLine($" indexedArrays ({indexedArrays.Length}):");
533+
for (int k = 0; k < indexedArrays.Length; k++)
534+
{
535+
var ia = indexedArrays[k];
536+
sb.AppendLine($" indexedArray[{k}] size={ia.ArraySize} element={TypeString(ia.ElementType)} elementSize={ia.ElementSize}");
537+
}
538+
}
539+
540+
var enumedArrays = Resolve(def.EnumedArraysPtr);
541+
if (enumedArrays.Length > 0)
542+
{
543+
sb.AppendLine($" enumedArrays ({enumedArrays.Length}):");
544+
for (int k = 0; k < enumedArrays.Length; k++)
545+
{
546+
var ea = enumedArrays[k];
547+
sb.AppendLine($" enumArray[{k}] enum={ea.EnumIndex} element={TypeString(ea.ElementType)} elementSize={ea.ElementSize}");
548+
}
549+
}
550+
551+
sb.AppendLine($" rootType: {TypeString(def.RootType)}");
552+
sb.AppendLine();
553+
}
554+
555+
return sb.ToString();
556+
}
447557
}
448558
}

Call of Duty FastFile Editor/UI/MainWindowForm.cs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public partial class MainWindowForm : Form
8080
/// </summary>
8181
private List<StringTable> _stringTables;
8282

83+
/// <summary>
84+
/// List of StructuredDataDef (MW2 / IW4) layout dumps extracted from the zone file.
85+
/// </summary>
86+
private List<StructuredDataDefAsset> _structuredDataDefs = new();
87+
8388
/// <summary>
8489
/// List of tags extracted from the zone file.
8590
/// </summary>
@@ -666,6 +671,7 @@ private void LoadAssetRecordsData(bool forcePatternMatching = false, bool loadRa
666671
_weapons = new List<WeaponAsset>();
667672
_images = new List<ImageAsset>();
668673
_stringTables = new List<StringTable>();
674+
_structuredDataDefs = new List<StructuredDataDefAsset>();
669675
_hasUnsupportedAssets = true; // IW6 has many types we don't parse
670676
_originalLocalizeCount = 0;
671677
_hasUnsavedChanges = false;
@@ -696,6 +702,7 @@ private void LoadAssetRecordsData(bool forcePatternMatching = false, bool loadRa
696702
_weapons = loadWeapons ? (_processResult.Weapons ?? new List<WeaponAsset>()) : new List<WeaponAsset>();
697703
_images = loadImages ? (_processResult.Images ?? new List<ImageAsset>()) : new List<ImageAsset>();
698704
_stringTables = loadStringTables ? (_processResult.StringTables ?? new List<StringTable>()) : new List<StringTable>();
705+
_structuredDataDefs = new List<StructuredDataDefAsset>(); // only the IW4 walk (MW2 PS3) produces these
699706

700707
// MW2 PS3: prefer the IW4 pointer-following reader over pattern matching for every asset
701708
// type it walks (rawfile, localize, stringtable, weapon, techset), but only when the full
@@ -728,6 +735,7 @@ private void LoadAssetRecordsData(bool forcePatternMatching = false, bool loadRa
728735
_techSets = iw4.TechSets;
729736
if (loadMenuFiles && iw4.MenuLists.Count > 0)
730737
_menuLists = iw4.MenuLists;
738+
_structuredDataDefs = iw4.StructuredDataDefs;
731739
}
732740
}
733741

@@ -770,6 +778,7 @@ private void LoadZoneDataToUI()
770778
PopulateWeapons();
771779
PopulateImages();
772780
PopulateStringTables();
781+
PopulateStructuredDataDefs();
773782
PopulateCollision_Map_Asset_StringData();
774783

775784
// Handle tags tab based on loadTags flag (set by LoadAssetRecordsData)
@@ -3012,6 +3021,70 @@ private void stringTablesListView_DoubleClick(object? sender, EventArgs e)
30123021
}
30133022
}
30143023

3024+
// Code-built "Struct Data" tab (StructuredDataDef layout dumps). Built lazily so it only
3025+
// appears for zones that actually have structureddatadef assets (MW2 PS3, via the IW4 walk).
3026+
private TabPage? _structDataTabPage;
3027+
private ListView? _structDataListView;
3028+
3029+
/// <summary>
3030+
/// Populates the Struct Data tab with the parsed StructuredDataDef layout dumps. Each row is a
3031+
/// DefSet; double-click opens the full layout dump (enums / structs / arrays / root type).
3032+
/// </summary>
3033+
private void PopulateStructuredDataDefs()
3034+
{
3035+
if (_structuredDataDefs == null || _structuredDataDefs.Count == 0)
3036+
{
3037+
if (_structDataTabPage != null && mainTabControl.TabPages.Contains(_structDataTabPage))
3038+
mainTabControl.TabPages.Remove(_structDataTabPage);
3039+
return;
3040+
}
3041+
3042+
if (_structDataTabPage == null)
3043+
{
3044+
_structDataTabPage = new TabPage("Struct Data");
3045+
_structDataListView = new ListView
3046+
{
3047+
Dock = DockStyle.Fill,
3048+
View = View.Details,
3049+
FullRowSelect = true,
3050+
GridLines = true,
3051+
};
3052+
_structDataListView.Columns.Add("Name", 340);
3053+
_structDataListView.Columns.Add("Defs", 60);
3054+
_structDataListView.Columns.Add("Enums", 70);
3055+
_structDataListView.Columns.Add("Structs", 70);
3056+
_structDataListView.Columns.Add("Offset", 100);
3057+
_structDataListView.DoubleClick += structDataListView_DoubleClick;
3058+
_structDataTabPage.Controls.Add(_structDataListView);
3059+
}
3060+
3061+
if (!mainTabControl.TabPages.Contains(_structDataTabPage))
3062+
mainTabControl.TabPages.Add(_structDataTabPage);
3063+
3064+
_structDataListView!.Items.Clear();
3065+
foreach (var def in _structuredDataDefs)
3066+
{
3067+
var lvi = new ListViewItem(def.Name);
3068+
lvi.SubItems.Add(def.DefCount.ToString());
3069+
lvi.SubItems.Add(def.EnumCount.ToString());
3070+
lvi.SubItems.Add(def.StructCount.ToString());
3071+
lvi.SubItems.Add($"0x{def.Offset:X}");
3072+
lvi.Tag = def;
3073+
_structDataListView.Items.Add(lvi);
3074+
}
3075+
}
3076+
3077+
private void structDataListView_DoubleClick(object? sender, EventArgs e)
3078+
{
3079+
if (_structDataListView == null || _structDataListView.SelectedItems.Count == 0)
3080+
return;
3081+
if (_structDataListView.SelectedItems[0].Tag is StructuredDataDefAsset def)
3082+
{
3083+
using var viewer = new StructuredDataDefViewerForm(def);
3084+
viewer.ShowDialog(this);
3085+
}
3086+
}
3087+
30153088
/// <summary>
30163089
/// Stores the parsed collision map data for the viewer.
30173090
/// </summary>
@@ -4380,6 +4453,7 @@ private void LoadAssetPoolIntoListView()
43804453
int techSetIndex = 0;
43814454
int xanimIndex = 0;
43824455
int stringTableIndex = 0;
4456+
int structDataIndex = 0;
43834457
int weaponIndex = 0;
43844458
int imageIndex = 0;
43854459

@@ -4399,6 +4473,7 @@ private void LoadAssetPoolIntoListView()
43994473
bool isStringTable = gameDefinition.IsStringTableType(assetTypeValue);
44004474
bool isWeapon = gameDefinition.IsWeaponType(assetTypeValue);
44014475
bool isImage = gameDefinition.IsImageType(assetTypeValue);
4476+
bool isStructuredData = gameDefinition.IsStructuredDataDefType(assetTypeValue);
44024477

44034478
var lvi = new ListViewItem((i + 1).ToString());
44044479
lvi.SubItems.Add(assetTypeName);
@@ -4419,7 +4494,7 @@ private void LoadAssetPoolIntoListView()
44194494
// texture data, not external references. We just don't have a
44204495
// parser for the IW6 image struct yet.
44214496
string status;
4422-
bool isSupportedType = isRawFile || isLocalize || isMenuFile || isTechSet || isXAnim || isStringTable || isWeapon || isImage;
4497+
bool isSupportedType = isRawFile || isLocalize || isMenuFile || isTechSet || isXAnim || isStringTable || isWeapon || isImage || isStructuredData;
44234498
bool isGhosts = _openedFastFile.IsGhostsFile;
44244499
if (isGhosts && isImage)
44254500
{
@@ -4539,6 +4614,15 @@ private void LoadAssetPoolIntoListView()
45394614
status = $"{parseMethod} ({image.Resolution}, {image.FormattedSize})";
45404615
imageIndex++;
45414616
}
4617+
else if (isStructuredData && _structuredDataDefs != null && structDataIndex < _structuredDataDefs.Count)
4618+
{
4619+
var sdd = _structuredDataDefs[structDataIndex];
4620+
isParsed = true;
4621+
name = string.IsNullOrEmpty(sdd.Name) ? "-" : sdd.Name;
4622+
dataStart = $"0x{sdd.Offset:X}";
4623+
status = $"IW4 pointer-walk ({sdd.DefCount} defs, {sdd.EnumCount} enums, {sdd.StructCount} structs)";
4624+
structDataIndex++;
4625+
}
45424626

45434627
// Generic fallback: if no typed parser claimed this record but the
45444628
// walker resolved its name + body offsets (Ghosts non-rawfile types

0 commit comments

Comments
 (0)