@@ -28,30 +28,35 @@ public static WalkResult Walk(byte[] zoneData, List<ZoneAssetRecord>? records, i
2828 var result = new WalkResult ( ) ;
2929 if ( zoneData == null || zoneData . Length == 0 ) return result ;
3030
31- var headers = GhostsZoneLayout . LocateAllHeaders ( zoneData , scanStart < 0 ? 0 : scanStart ) ;
31+ int scan = scanStart < 0 ? 0 : scanStart ;
32+ var headers = GhostsZoneLayout . LocateAllHeaders ( zoneData , scan ) ;
33+ var luaFiles = GhostsZoneLayout . LocateAllLuaFiles ( zoneData , scan ) ;
3234
3335 // Base zones: no pool to pair against. Surface every located header
34- // as a rawfile (per docs/Ghosts_FastFile_Format.md, "almost all"
35- // wrapped assets in base zones are rawfiles).
36+ // (wrapped + luafile) as a rawfile-like node so the editor's rawfile
37+ // tree still populates. Per docs/Ghosts_FastFile_Format.md, "almost
38+ // all" wrapped assets in base zones are rawfiles.
3639 if ( records == null || records . Count == 0 )
3740 {
3841 foreach ( var h in headers )
39- result . RawFileNodes . Add ( BuildRawFileNode ( zoneData , h ) ) ;
40- result . UnpairedHeaders = headers . Count ;
41- Debug . WriteLine ( $ "[GhostsAssetWalker] No pool — surfaced { headers . Count } located headers as rawfiles") ;
42+ result . RawFileNodes . Add ( BuildWrappedNode ( zoneData , h ) ) ;
43+ foreach ( var lua in luaFiles )
44+ result . RawFileNodes . Add ( BuildLuaFileNode ( zoneData , lua ) ) ;
45+ result . UnpairedHeaders = headers . Count + luaFiles . Count ;
46+ Debug . WriteLine ( $ "[GhostsAssetWalker] No pool — surfaced { headers . Count } wrapped + { luaFiles . Count } luafile headers as rawfiles") ;
4247 return result ;
4348 }
4449
45- // Lift to library DTOs for pairing.
50+ // Pair wrapped types (rawfile/scriptfile/mptype/aitype) with the
51+ // located zlib-wrapped headers.
4652 var poolDtos = new List < GhostsPoolEntry > ( records . Count ) ;
4753 foreach ( var r in records )
4854 {
4955 poolDtos . Add ( new GhostsPoolEntry (
5056 recordOffset : r . AssetPoolRecordOffset ,
5157 type : r . AssetType_Ghosts ,
52- pointerKind : GhostsPointerKind . Placeholder ) ) ; // pointer kind isn't used during pairing
58+ pointerKind : GhostsPointerKind . Placeholder ) ) ;
5359 }
54-
5560 var pairing = GhostsZoneLayout . PairPoolWithHeaders ( poolDtos , headers ) ;
5661
5762 for ( int i = 0 ; i < records . Count ; i ++ )
@@ -72,18 +77,44 @@ public static WalkResult Walk(byte[] zoneData, List<ZoneAssetRecord>? records, i
7277 records [ i ] = record ;
7378
7479 if ( record . AssetType_Ghosts == GhostsAssetTypePS3 . rawfile )
75- result . RawFileNodes . Add ( BuildRawFileNode ( zoneData , h ) ) ;
80+ result . RawFileNodes . Add ( BuildWrappedNode ( zoneData , h ) ) ;
81+ }
82+
83+ // Pair luafile pool entries with located luafiles — separate pass
84+ // because the on-disk format is different (flat 16-byte header, no
85+ // zlib wrapper). Same positional rule: i-th luafile pool entry ↔
86+ // i-th located luafile body.
87+ int luaIdx = 0 ;
88+ int luaPaired = 0 ;
89+ for ( int i = 0 ; i < records . Count && luaIdx < luaFiles . Count ; i ++ )
90+ {
91+ if ( records [ i ] . AssetType_Ghosts != GhostsAssetTypePS3 . luafile ) continue ;
92+ var lua = luaFiles [ luaIdx ++ ] ;
93+ var record = records [ i ] ;
94+
95+ record . HeaderStartOffset = lua . HeaderOffset ;
96+ record . HeaderEndOffset = lua . BodyStart ;
97+ record . AssetDataStartPosition = lua . BodyStart ;
98+ record . AssetDataEndOffset = lua . BodyEnd ;
99+ record . AssetRecordEndOffset = lua . BodyEnd ;
100+ record . Name = lua . Name ;
101+ record . Size = lua . ByteCodeLen ;
102+ record . AdditionalData = "Ghosts luafile (flat header)" ;
103+ records [ i ] = record ;
104+ luaPaired ++ ;
105+
106+ result . RawFileNodes . Add ( BuildLuaFileNode ( zoneData , lua ) ) ;
76107 }
77108
78- result . Resolved = pairing . PairedCount ;
79- result . Unresolved = Math . Max ( 0 , records . Count - pairing . PairedCount ) ;
80- result . UnpairedHeaders = pairing . UnpairedHeaders ;
109+ result . Resolved = pairing . PairedCount + luaPaired ;
110+ result . Unresolved = Math . Max ( 0 , records . Count - result . Resolved ) ;
111+ result . UnpairedHeaders = pairing . UnpairedHeaders + Math . Max ( 0 , luaFiles . Count - luaIdx ) ;
81112
82- Debug . WriteLine ( $ "[GhostsAssetWalker] Paired { result . Resolved } / { records . Count } pool entries with { headers . Count } located headers ( { result . UnpairedHeaders } unpaired) ; { result . RawFileNodes . Count } rawfiles ") ;
113+ Debug . WriteLine ( $ "[GhostsAssetWalker] Paired { pairing . PairedCount } wrapped + { luaPaired } luafile pool entries ; { result . RawFileNodes . Count } rawfile-like nodes ") ;
83114 return result ;
84115 }
85116
86- private static RawFileNode BuildRawFileNode ( byte [ ] zone , GhostsAssetHeader h )
117+ private static RawFileNode BuildWrappedNode ( byte [ ] zone , GhostsAssetHeader h )
87118 {
88119 byte [ ] body = new byte [ h . DecompressedLen ] ;
89120 Buffer . BlockCopy ( zone , h . BodyStart , body , 0 , h . DecompressedLen ) ;
@@ -102,6 +133,30 @@ private static RawFileNode BuildRawFileNode(byte[] zone, GhostsAssetHeader h)
102133 } ;
103134 }
104135
136+ private static RawFileNode BuildLuaFileNode ( byte [ ] zone , GhostsLuaFile lua )
137+ {
138+ byte [ ] body = new byte [ lua . ByteCodeLen ] ;
139+ Buffer . BlockCopy ( zone , lua . BodyStart , body , 0 , lua . ByteCodeLen ) ;
140+ // The body is Lua 5.1 bytecode (compiled, not source). Surface a
141+ // header + extracted-strings summary in the text viewer so the
142+ // user gets useful signal about each file's content; the original
143+ // .lua source isn't recoverable without a decompiler.
144+ var summary = LuaBytecodeInspector . Inspect ( body ) ;
145+ string content = LuaBytecodeInspector . FormatSummaryText ( lua . Name , summary ) ;
146+ return new RawFileNode
147+ {
148+ FileName = lua . Name ,
149+ StartOfFileHeader = lua . HeaderOffset ,
150+ HeaderSize = lua . BodyStart - lua . HeaderOffset ,
151+ MaxSize = lua . ByteCodeLen ,
152+ CompressedSize = lua . ByteCodeLen ,
153+ IsCompressed = false ,
154+ RawFileBytes = body ,
155+ RawFileContent = content ,
156+ AdditionalData = "Ghosts luafile (Lua 5.1 bytecode)" ,
157+ } ;
158+ }
159+
105160 private static bool LooksTextual ( byte [ ] body )
106161 {
107162 if ( body . Length == 0 ) return true ;
0 commit comments