Skip to content

Commit 621eccd

Browse files
committed
Add IW4 multi-block techset/material reader
Implement block-aware IW4 zone reading to correctly parse PS3 techsets, materials and images. Added ZoneReadStreamBlocks and extended ZoneReadContext to track per-block cursors, alignments and block-scoped pointer resolution (ResolvePointerInBlock / ResolvePointerAlignedInBlock). Ported/added Techset.cs (technique/pass/shader models and reader) and an ImageReader that understands the PS3 EBOOT GfxImage root and block-targeted pixel/shader payloads. MaterialReader updated to queue/reserve pointer resolves per-block (TEMP/LARGE) and to resolve images/state/loadbits correctly; MaterialModels updated for EBOOT image root fields. Iw4ZoneReader now creates stream-block state and activates the LARGE block while reading asset bodies. UI/MainWindowForm updated to enumerate parsed materials and display technique/texture/constant details, and Iw4AssetBridge links materials to their single techset when applicable. Docs updated to reflect the block-aware material reader and limitations (map zones still blocked by XModel). These changes prevent misalignment when reading standalone techsets/materials and make self-contained PS3 zones' materials parse correctly.
1 parent 535b98a commit 621eccd

11 files changed

Lines changed: 709 additions & 88 deletions

File tree

Call of Duty FastFile Editor/Services/Iw4AssetBridge.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ public static class Iw4AssetBridge
8585
// stream order, so this sorted set lets us bound each menu's data without re-scanning.
8686
var bodyOffsets = new SortedSet<int>();
8787

88+
// Editor material ↔ IW4 material pairs, so we can fill in the technique-set link after all
89+
// techsets are read (materials usually precede techsets in pool order).
90+
var materialRefs = new List<(MaterialAsset Editor, FastFileLib.Iw4.Material Iw4)>();
91+
8892
foreach (var asset in walk.AssetList.Assets)
8993
{
9094
var body = asset.XAssetPtr?.Result;
@@ -109,7 +113,9 @@ public static class Iw4AssetBridge
109113
result.TechSets.Add(ToTechSet(ts));
110114
break;
111115
case FastFileLib.Iw4.Material mat:
112-
result.Materials.Add(ToMaterial(mat));
116+
var editorMat = ToMaterial(mat);
117+
result.Materials.Add(editorMat);
118+
materialRefs.Add((editorMat, mat));
113119
break;
114120
case FastFileLib.Iw4.MenuList ml:
115121
iw4MenuLists.Add(ml);
@@ -120,6 +126,20 @@ public static class Iw4AssetBridge
120126
}
121127
}
122128

129+
// Technique-set link. A material points at its techset via an ALIAS offset pointer — the
130+
// decoded offset lands on a 4-byte alias cell that the engine fills at load time, and that
131+
// cell is empty in the serialized zone, so the link can't be followed from the bytes. But
132+
// when the walk read exactly one techset, every material that references a techset uses it,
133+
// so attribute it (only to materials whose TechniqueSet is actually a non-null reference).
134+
if (result.TechSets.Count == 1 && !string.IsNullOrEmpty(result.TechSets[0].Name))
135+
{
136+
string only = result.TechSets[0].Name;
137+
foreach (var (editorMat, iw4Mat) in materialRefs)
138+
if (string.IsNullOrEmpty(editorMat.TechniqueSetName)
139+
&& iw4Mat.TechniqueSet is { Kind: FastFileLib.Iw4.PointerKind.Offset })
140+
editorMat.TechniqueSetName = only;
141+
}
142+
123143
// Add every menuDef's offset to the boundary set so consecutive menus inside one list
124144
// bound each other (the last menu in a list is bounded by the next top-level asset body).
125145
foreach (var ml in iw4MenuLists)
@@ -591,9 +611,6 @@ private static MaterialAsset ToMaterial(FastFileLib.Iw4.Material m)
591611
AdditionalData = "IW4 pointer-walk",
592612
};
593613

594-
// Technique set / texture table / constants resolve only when stored inline; shared
595-
// ones are offset (block) pointers the reader doesn't dereference, so they stay empty
596-
// and only the root counts above are shown.
597614
var ts = m.TechniqueSet is { IsResolved: true, Result: not null } ? m.TechniqueSet.Result : null;
598615
mat.TechniqueSetName = ts?.Name ?? string.Empty;
599616
if (ts?.Techniques != null)

Call of Duty FastFile Editor/UI/MainWindowForm.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,8 +781,9 @@ private void LoadAssetRecordsData(bool forcePatternMatching = false, bool loadRa
781781
_localizedEntries = iw4.LocalizedEntries;
782782
}
783783

784-
// Materials surface from a full read of the material pool (complete walk, or a
785-
// partial walk that still resolved every declared material).
784+
// Materials surface from a full read of the material pool — a complete walk, or a
785+
// partial walk that still resolved every declared material (the multi-block reader
786+
// reads standalone materials/techsets/shaders correctly, e.g. mp_favela_load).
786787
if (iw4.Materials.Count > 0
787788
&& (iw4.Complete || iw4.Materials.Count == iw4.DeclaredMaterialCount))
788789
_materials = iw4.Materials;
@@ -4701,6 +4702,7 @@ private void LoadAssetPoolIntoListView()
47014702
int structDataIndex = 0;
47024703
int weaponIndex = 0;
47034704
int imageIndex = 0;
4705+
int materialIndex = 0;
47044706

47054707
// Iterate through ALL asset records from the asset pool
47064708
for (int i = 0; i < _zoneAssetRecords.Count; i++)
@@ -4869,6 +4871,19 @@ private void LoadAssetPoolIntoListView()
48694871
status = $"IW4 pointer-walk ({sdd.DefCount} defs, {sdd.EnumCount} enums, {sdd.StructCount} structs)";
48704872
structDataIndex++;
48714873
}
4874+
else if (isMaterial && _materials != null && materialIndex < _materials.Count)
4875+
{
4876+
// Materials parsed in pool order (IW4 walk for MW2 PS3, MaterialParser scan otherwise).
4877+
var mat = _materials[materialIndex];
4878+
isParsed = true;
4879+
name = string.IsNullOrEmpty(mat.Name) ? "-" : mat.Name;
4880+
dataStart = $"0x{mat.StartOfFileHeader:X}";
4881+
string parseMethod = !string.IsNullOrEmpty(mat.AdditionalData) ? mat.AdditionalData : "Parsed";
4882+
status = mat.IsStructuredView
4883+
? $"{parseMethod} (tex {mat.TextureCount}, const {mat.ConstantCount}, techset {(string.IsNullOrEmpty(mat.TechniqueSetName) ? "—" : mat.TechniqueSetName)})"
4884+
: parseMethod;
4885+
materialIndex++;
4886+
}
48724887

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

FastFileLib/Iw4/Iw4ZoneReader.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public sealed class Iw4ZoneReader
3434
{
3535
private readonly byte[] _buffer;
3636
private int _position;
37+
private ZoneReadStreamBlocks? _streamBlocks;
3738

3839
public Iw4ZoneReader(byte[] buffer)
3940
{
@@ -44,6 +45,8 @@ public Iw4ZoneReadResult Read()
4445
{
4546
_position = 0;
4647
var header = ParseHeader();
48+
// Per-block stream positions (IW4 XFILE_BLOCK model) — created from the header block sizes.
49+
_streamBlocks = new ZoneReadStreamBlocks(header.BlockSize);
4750
var (assetList, stoppedType, stoppedIndex, error) = ParseXAssetList();
4851

4952
return new Iw4ZoneReadResult
@@ -79,13 +82,19 @@ private XFile ParseHeader()
7982

8083
private (XAssetList list, XAssetType? stoppedType, int stoppedIndex, string? error) ParseXAssetList()
8184
{
82-
var context = new ZoneReadContext(_buffer, _position);
85+
var context = new ZoneReadContext(_buffer, _position, _streamBlocks);
8386
var assetList = ReadXAssetList(ref context);
8487

8588
XAssetType? stoppedType = null;
8689
int stoppedIndex = -1;
8790
string? error = null;
8891

92+
// Asset bodies are allocated from the LARGE block (the primary serialized asset-data block);
93+
// resolve them with LARGE active so block-targeted inline data (e.g. shader bytecode that a
94+
// pass reads from TEMP) aligns against the correct per-block cursor. The XAssetList header
95+
// (counts + the two array pointers) was already read above with TEMP active, matching the
96+
// reference — that 16-byte TEMP advance is what later TEMP-block alignment is relative to.
97+
context.PushStreamBlock(ZoneStreamBlock.Large);
8998
try
9099
{
91100
context.ResolveQueued();
@@ -106,6 +115,10 @@ private XFile ParseHeader()
106115
error = inner.Message;
107116
}
108117
}
118+
finally
119+
{
120+
context.PopStreamBlock();
121+
}
109122

110123
_position = context.Position;
111124
return (assetList, stoppedType, stoppedIndex, error);

FastFileLib/Iw4/MaterialModels.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,17 @@ public class MaterialInfo
110110

111111
public class GfxImage : BaseAsset
112112
{
113+
// PS3 EBOOT GfxImage root is 0x50: a 0x28 prefix (width/height/format/resourceSize/…), the
114+
// LoadDef pointer @0x28, a 0x20 suffix, and the name pointer @0x4C.
115+
public const int EBOOT_ROOT_SIZE = 0x50;
116+
public const int EBOOT_LOAD_DEF_POINTER_OFFSET = 0x28;
117+
public const int EBOOT_NAME_POINTER_OFFSET = 0x4C;
118+
113119
public GfxImage() : base(XAssetType.Image) { }
120+
121+
public byte[] EbootRootPrefix { get; set; } = new byte[EBOOT_LOAD_DEF_POINTER_OFFSET];
114122
public ZonePointer<GfxImageLoadDef>? LoadDef { get; set; }
123+
public byte[] EbootRootSuffix { get; set; } = new byte[EBOOT_NAME_POINTER_OFFSET - EBOOT_LOAD_DEF_POINTER_OFFSET - 4];
115124
public byte MapType, Semantic, Category, UseSrgbReads;
116125
public byte[] Picmip { get; set; } = new byte[2];
117126
public byte NoPicmip, Track;

0 commit comments

Comments
 (0)