From bbd29b1f1070d87940eb825ff80574d017d62b37 Mon Sep 17 00:00:00 2001 From: MaddoScientisto Date: Tue, 7 Apr 2026 00:15:44 +0200 Subject: [PATCH] PSX Decompilation --- .github/copilot-instructions.md | 1 + .../idata/01/~00000015.db/change.data.gbf | Bin 147456 -> 147456 bytes .../idata/01/~00000015.db/change.map.gbf | Bin 32768 -> 32768 bytes .../01/~00000015.db/{db.68.gbf => db.77.gbf} | Bin 24461312 -> 24461312 bytes .../01/~00000015.db/{db.67.gbf => db.78.gbf} | Bin 24461312 -> 24461312 bytes .../01/~0000001b.db/{db.7.gbf => db.11.gbf} | Bin 7454720 -> 7503872 bytes .../01/~0000001b.db/{db.8.gbf => db.12.gbf} | Bin 7454720 -> 7536640 bytes Crusader.rep/projectState | 356 +---------- .../00/~00000008.db/{db.44.gbf => db.48.gbf} | Bin 81920 -> 81920 bytes .../00/~00000008.db/{db.45.gbf => db.49.gbf} | Bin 81920 -> 81920 bytes .../00/~0000000d.db/{db.3.gbf => db.4.gbf} | Bin 163840 -> 163840 bytes .../00/~0000000d.db/{db.2.gbf => db.5.gbf} | Bin 131072 -> 163840 bytes _tmp_inspect_psx_banks.js | 197 ++++++ _tmp_probe_psx_section16.js | 132 ++++ _tmp_probe_sections.js | 111 ++++ crusader_decompilation_notes.md | 2 + ...entity-vm-runtime-owner-resource-layout.md | 100 ++- docs/psx/map-viewer-plan.md | 164 +++++ docs/psx/psx.md | 294 ++++++++- docs/remorse-class-lift-index.md | 29 + docs/usecode-debugger-break-state-layout.md | 221 +++++++ ghidra_mcp_wishlist.md | 375 ++--------- plan-mid.md | 20 +- tools/psx_export_map_type_probe.py | 586 ++++++++++++++++++ tools/psx_extract_wdl.py | 34 +- 25 files changed, 1921 insertions(+), 701 deletions(-) rename Crusader.rep/idata/01/~00000015.db/{db.68.gbf => db.77.gbf} (98%) rename Crusader.rep/idata/01/~00000015.db/{db.67.gbf => db.78.gbf} (98%) rename Crusader.rep/idata/01/~0000001b.db/{db.7.gbf => db.11.gbf} (98%) rename Crusader.rep/idata/01/~0000001b.db/{db.8.gbf => db.12.gbf} (97%) rename Crusader.rep/user/00/~00000008.db/{db.44.gbf => db.48.gbf} (99%) rename Crusader.rep/user/00/~00000008.db/{db.45.gbf => db.49.gbf} (98%) rename Crusader.rep/user/00/~0000000d.db/{db.3.gbf => db.4.gbf} (99%) rename Crusader.rep/user/00/~0000000d.db/{db.2.gbf => db.5.gbf} (79%) create mode 100644 _tmp_inspect_psx_banks.js create mode 100644 _tmp_probe_psx_section16.js create mode 100644 _tmp_probe_sections.js create mode 100644 docs/psx/map-viewer-plan.md create mode 100644 docs/usecode-debugger-break-state-layout.md create mode 100644 tools/psx_export_map_type_probe.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7443b44..135c749 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -17,4 +17,5 @@ applyTo: '**' - Validate reverse-engineering and tooling changes with the narrowest relevant check. - Keep read-only analysis separate from any explicit writable workflow. - For authored map-placement or link investigation in Crusader-Map-Viewer, prefer decompressed `.cache/scene-cache//map-*//scene.json` over `site/data`; the cache scenes preserve direct item objects and world coordinates. +- Never write documentation into `k:\ghidra\crusader_map_viewer` unless the user explicitly asks for documentation changes in that repo. - If the shell becomes stuck on multiline input or otherwise unhealthy, you could try to press esc in the shell to see if it gets unstuck, otherwise immediately use `vscode_askQuestions` to ask the user to fix the terminal state, then continue the task once the user confirms it is fixed. \ No newline at end of file diff --git a/Crusader.rep/idata/01/~00000015.db/change.data.gbf b/Crusader.rep/idata/01/~00000015.db/change.data.gbf index 72ec249a07e3294bde2434f6af8f21e3c904ac7e..0a90d03fb0e6748410b45750ceaa707f94bc923e 100644 GIT binary patch literal 147456 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 delta 133 zcmZo@U}|V!63{m=)G^W2(K4xud3aALD1M@V5{pK^tK3AhP!=9*DWi@35A8$U1h|6) z83Y&-J`@%(Fm$jf3MevgFt9OmFgZ0f2(TOwsojAZ?C@Av(~QJQ(OB* zU@d%9MrKxOZc=LU#gS7N|NK;|Lz<@PnpUE@^mXTsb$2X}sPJ3ctR!H9p0i}@v$0FE zo{LjkZI@I%x2;JwV&|;w{d}5_K5ogh7jvP{E8VR$?4?hejL@{c3H_(=FD_-Obx@NMDS!N44(p@3ZDj_4xa&^37-YeHsrwP!so$vhR=sD zfG>nEg6{&~6}}k08+>@B`ro!CwJC7=8%+Q21f+!{JB3UkQH|{7Cq#;je)o z1%EC4X!z^kuZO<@z6`z`ehfV8QUN~>zS4E#o$=9k=xrU%OD5FYi(z1f3x_>?Di8r! zCeR8nQUKOgY!Qfry_jjD05lopq5)k6+5kEVV0v`*6>#0!W5VJh*Gb!iSRY5i+zA8P z<`y6@pU z68KU4&_D4Qp!TgOv-o)v&!v7$)UtiuzG4a zXDIu%me5VyX5!Zg_{C=XCoJ3hgSThEhtQNu^jR?Cj+8l%teg6fQ@}?62gf zC>+;h6rm+grm)A8`%^e%$?*_-ayU2HYf1H#-4u3v5_>J_Im*M7+bPT@X%wY5g?*od zHBn2vK;d{NK2MoL=|M@Rw50?=5?J4a;}i~0!dAvfSWj6)VXYJHqu404v4m?VYfcm>=fsxFtr9 zaPEeF@3>*gx6SL8zkh>Xp}XSSZ+I!PPw2EsRnyBRP8nBuYuWgTlcrbJAT3-9t)O{B zpxUXG(whFdx7@KQZ$omgQV#iFLQ`l1f7xsK#@t&uWqQpl5M1fCuK3Oy4&J3tcQs$P zVO$dqF`VsPP4m6rSoy)s*ab^J-T=SRwe;ivRL|Box|)}4x|r0A)&)0su*>JBO{;zU z;(dHHhhFtZjAPxNxBML6t$sPe_2;BbEyDFLUCnDZ<)>I{%2f&tXAX3MbIa9VlWSes zFKvqPaTNbDHekVmS2np8ys~-qqxRW|WlT-RPaZ*`NhTLkxI`q!P}Ee{v6aNBp0tyK zR%l5yqokWDqbVH7q%;agGAR&}NE=B!Na4^YzD99U?xxiy(qI$ID1#_0II$Cj>p~*D zmT-*19!~g_!WPveT`=}@%K^Kjq$Yjc=lj?cM2^oo-05+ywd6V)iKFUVQ8Uau zUFJI1Z_E9you^ww=~0f@@-1yHA#HWdc>)oBaQThda<_lTm|H5###K(6jtt-|pVh^N zN9ol*u85nroQcr4IRb09q}$oQ-Cm`zYrEA@hEuXBVUXfel$P}otO7gMHCuArn+xccXD3CMe&@)U)ZmUk0{i$)&f<+Xz3o~5wTxo^=Pa_^^1 zq%dM`EF|XwWgq1|3a3!c49azszLbuXmXPdYlns>UD03-Xt+KmO*oJIcRn{H~J1Xl@ z3M|>KWsTux5rvh@AY;rONZpxBeCTt(CNQJ$x;Q_?t!X{>)*FeH`pDwSi9%DhrJ2B}jiw3*Zl zNXmH%yEWw<$_mQul);n;Nb+t9Z6Wz?$}P0NWG*7fB@|XB=>p|2WebH?k#rBGg3^V; zj!OK4@-c;jlQ^F;j>0I377DF1;d{yk$`h0cltC04ggt?6?{tL1ygEHexr0(hVFz@I zqcnrWvlrsOpm1E`885zyGJ;Y-;eny!uavJS9PN%vDYGe8Q}Q+6)95tDbY}mLPouDZi?5<^3SGG*bh$`jwYz*v;m~z?i$dcxR;xMO8c!KUDWq5^tVhu)%09|w%Da>o zDGmxvvWTWzbRDH1B?oeIQG0GOheEcbkj7fb(hFUbrzlG(%%N}^g(g`zg3_Hriz*C< z6wrtZexmH4unGmNLct@H`A&U)q#kb1A;eOeL-K#8(DL)Yq|hGo*@^iy%KQf?wUjB8 z(G*rbpZ%BLfx_-HR!eqYXLeubO_a41w!ZUxN;TzL_IT%B+)Sa+96Qrw@@NQo2PoSq z>nW=!PRjk1+bEMLtViAe3P&L?krGL13dvK*OD*v$Ep z!Vb&%g7P7S5p$MPSZ0omGJ!IJ!XD3I&2v~>jxQwpBxOHk8-+E`W-i&RdG>u2=8`>; zay6y5=GZVlqJ^r2$L#kU7GO*<=>RKl(AX~v=29vDJ>z!75=<%F7fEL#Kt5 zTPWiw!zm>cc5$b66b@nhS;`NTos;>y7$}Y+~l*cGM09oxE zO6wJrB1#8J2*h%pLc_FhW?J5*JV#-DEnH44>@v#;N;gUpB?1zAk;1hhmYp8US#8{6 zc$ix>*4Qx=Wq+}m+-y$?g0w$JIZXMI@)3p8qWvS3J1A2qw7>Qx6!uYj8cusZNX!Y! zca--i9O#&Z6c!wF6J-=-AjCh0&5lWO^;x*}aD-$2<6EbX=!ZSwRWqkl)|5@Fno(0x zSvG!5P4)B|?1n4t4si$9RJtQftFGjPf-8Lp5@4jBI8N=-2KRRbKC$&tYk2d?RpXG` zq%jkw86l;8UCZmXPAt(Eyxx4yiM6cUh|MVm zS30R&{ndnTcg%0OZHV6AwY}vwdlS9X)iYvScOSj4qo(z?_kBuxxZ)zW1?zfGS6tM# z^pFM16SujRCvLyVp}*(~T(|vq|8jO9DjVb{noH% z6Q@j^Ue+)|f%9C|ySFbL>!Y7=1(xi1rK&>1UR7MCPC|9rp@V;j#P0f?)d ze<6S)Pv?)hkfj^jntvA@1-}*O3V2MACKn+%yXId4;?hFr-vfZU zE9c)6$IMd#y#QYe^ajx2==}QtxYE%1_XW5ondXlXEAo!FrpHo>0yo9hDv{FciQAoX&q34%|nLZOtEspT?z; z&L68_8V_`I{#ODXQ00@Z0?kwbfRO;?qMZNL0E~UxFfUxsssi$;j%Uk*4VfMr;nc#!?a0$!33bal!D0+{LQghWNF z1fVeG{Ko@I1u*m}Np7(F-v|nppousm`BeaGaI$GyHv`s72&P8za{?HvrzZ%e8U@9Pm#1 zk8!O!yyJwQPDb=es1DF5xazoSL{jv0KZII1MX!QKsRXC+CFfkv>s=j&(;% zqI{zMYt)ueetf*|XViC&a}r93`ox&NejnYXLp1;)}Gw)zGLH2U`h41CeGVXYUqD2 z6@{NXx+9u>5#`9~y0MjG)%hJcj_yD0XyH8mQiR@u-PgvES-5euqvXuyrjF|4oBbVI zFKlk%Jn?d}_R+Sul;; z^59Kpc^QBm%2{3k$kE8M5`ZPZ2w4TdI&Z*gK%~GLO>1*XU@c&iz-xf#1?+EtFk_Un zxB$Zi-UOrxyakZ;+VZxhMIVrmcL1{b^OQ4ssf1v9Mq|z@XF-(c5`lGqAO)C17OW&u zKT6OC0I_JxhXBkN^|gZ z7W8{$s03}(wAMchYzKTQuoLjGz!w0l^U7Ji1Xu;W*0ffC3hV-WCGZVERtd|u0Q+PK z`VQ1jU^k$Hz#dJDxFE0>fGm}>>;qsp4cHG@B=9`|OSTbm0MJ|DAiyGU2>l;^TnK{@ zj=ogRf({LTS^%q0IHDRMM*#x`&{5%?1dai80c0D7Mk!~h2f(@vI01Oj*tTnylOVGf zV6tImFPsLLz3_{sg_^zaE5Pi9GXS#}egl}ja28uG<;ldSU3! znZAzONA8SheY&ac+ob7|M_FE|zk6l8)w%x7{(7Wq!j(I_>5eH^?+j`mVdNcr!X2N- zpa_X?a~{o_sJCeqOi~CouNAE0eY$-?THz)&07i8xH8>@tFFH)Jss(C(gEv)!vxhS65d&(9rTa|fh)2h4E?qNcRYcRNHoU zNTwO$dhp&ZQ*#1*Z}Gbw`)XegY_-tGwZy0E^gPG?wYvj`$(k$GoNJ~uHg5CcIbLk@ z;@Ph9v%6nhpaU@xwfv^>uOdgJtadSJt{*cLumGlJO4me z?Yn#L_HEQ7u2nzpjr5C_E(!AL+(6g56MJva!~B}M1B|RRzb1~5Q+p#PN>!SRRY+6a zEL98eVt*MlcYHrLyVp|RKiup#Yu}3w%c;Gw`gunt|NQAF;hzhxp{MrZiu03h*N6D{ zX$vyX?R90I+xJaxb!Tt?uOY6X!}o6v^RZPpLMrzU_OadQ7+SeMJtk8Of$UFH5gEqrWa9E+#z-|S;M z;21i6f4YzDdq=hUyWg=`{oUtSHGO|KAKPBXKK1ukoK-O19!KU3?*GM6K7$$Vc2ukV z-#Hem{olIQ&DeiMbNv@r%i8__^0Q~liKTTk?iR|4rS&?%YNKwN^?HDuSn?&oZ1cpj z3}BvEmIGoW%@|BeoZl+PS9P-FzJ#>`@T!E^$ANI0z=&4~zzzd&A1JH407}eiBXA>b z{NWT^IqO8gdI6kGXFeuyGhn8`B*1WiDFEY$2`jZ$;bx!N3)KL#7p4N1OCk(ahI^iB z)@gu|5;7eSCx8;u&kNiN_*!5VV66aVc>4WvfydV`)6o#+teBkXy#z3G(}M(RaYye* zfq4L)KG9im{jJ{WRj?3%#w%w<=ce@$xCam>0Gmv;?-4>}Qr{9-1b9ebF#vs}ob>^~ z0D%Vq@d8T#?rqJA#Ogf|?yx=#cv+%70zjjbvpUfK>fI0`-$XTU_F0z#{ETRt_20Pb zwpU;o;B5hnb~5r;&iXh218Be#fPn%|fLsS!mjm3ZfnB{;mh_#(dlL87Tmnx4&_v~| zPXlo3V8AngLV;%i?vrTE`W)_z)l10pfDHmK0FX{O>x+O|Y@+>W_)DPC+=8>d0!T2n zHS0>;G&?P@3b0uKvoitRqMUUNV1@u}EMcg?Yk(Ai*8xoh-oOLR{i=NIv7*tP-j$#? z0Wc!vtT2jBn6n0;-_;8-BtH4;6m0}(RwPz$F{+UF09z#FeE{04oV5;cr@%V2KfYWD zvpBws04zJcwZMmX=6O=!Bfx(IJ_ex6m9u^Vm@n`t;Cg}200jb{1H#>4w_IQ5An4N_|Q13l5$clD$$TMPT)~$dLfo*tLdQ@OL02Nfux&yF+o6OR> z6EsH!0KNcRE$}5ETi`1|3jwSu*24n30O%v-tlt1&`v!aqm?Z!!w_Yi*8;~LQl&x3> ztj#58FYY-U64(b=C$Jx|OyGOKEdmDs!vqcjQU#Ec#b4k@Jd;J^m1F(wv9AjKgrK_w zjsV69pbula3;YadBXA6-;=c-D6x*Z!mE%k9?Ozf&0r-~y45|GX0sARXSE18@NP%DQ z@DicQ@qN@7n1BIi0M85j2Dn||EZ_!#-vNaJ=Kv7`f8enuDhP-1xBdx&6)I;v4|qo4 z0-#plB4D%|;_$a!X8|1$D&T|Ltmq@<;(P&^2nP58o)l;TxXrLXm{n8IH4=ouYMUbv z0B9-D47XV^Ov>>!fHofq1Ol7_L4er;u-GU7S{tX zU!W&ous|swNdQd?gRv?X*Bdu4Hw*LuJRrcgMM5VD^+OO^r5s;M4>fZbfcvN4NXS6I z^8$kara7W6=55wE*iHxrS~Mj`NH52RZx~?{DqOTfF~ZT=L)D#|+VV z*X#rJmKbiGrmfv?-o`Y7a2xX=vNdnL-sV_;7PsQ;YxmzBq%XkjlKn%PH@nuT$KYYE z;T!hPO$saRS8nWa4^!#Qt`AP{f5dOW!n6Bb3(tN(udluY_aF{@-X!>ud;3KM_|b0Q z{4-qJdmk9)=REMEu7|pM4LDHc7o6JVVuVFjGyndsHG>a4fZK2H%t8XpNxHxp-#I@@ z#fF+J+R-;ReIHd`fyjzi3dJyt%u{r*a2tjX8V`|2ar{+?T;L&*InBlJ@8q8 zW7diT7D~v1r7I4&maaHB^#=Wp*8>l|8|liAIuzMF%ywP5N~gISYa8t8Ddb& zTBWp1=@6yem1Zc7RT`}HVm9($R*$=z%l7h~=N7WFPU%XePNj>K+Lcx*EmJx~X?LX= z)MZ0YHE-PbRLnz%T8I)K` zFvPhfjd3nULFy@cDO(^;J42jRO3RcEQQBQ;hSFH-GCR{$L0t0^j=XSx3=<%z`cg65Sy0$ESL9+jFofV#srWSs@dNx@0LS{`=>j-YDt0d_ zb}a;_O2s7-6aeTXfMZ8-kN^&B>R)rnAvjcb{aQj0rR)0wEdWmopvvlfd2&cFpjtxE zqOKzWXg`8*+&2FUKLn>rUE?GkPL;Z%PRfNu;LZ5&1#qg=Wu-uCz{3JKRZ{;4L=K4p z$RR!?8jvB;+5lRq@)-}ON=5Y&gaI!4LI9^qMP@-bK`6RQLSg|G0u}%cxXOj#RH?`; z2#ry%<1;9v1758^V8qfwI->oBp9{q!$SeqhP-wOYXHL2!KqFxGMoYB^t&wU#3SKEGged zLMGt-|HA^40Wv+}rT}Dm#8m+vGVBjkm<@tgE)HGYxm@5LfZ6c-0cOL|p`E4S9S7rI z1*G8}Hv**Lyw1;)h9}+tkW;tBO0+)@dz4E=Rr68=Fjw<1D~u4FX5}6gI0)G0hNKn% zmjLEe?&AV~0B#q!0GQ~86!bza21B_Ny8{FhFbG4E+g1Q3lB)~s#J?u|D6k8#NdT40 zSu3y)uvB0_K;{?Ut;<1sl}q^vFi>qGJmm-|OM*@TFoBgz#h7NF7w`qh%t@UAkcpED zbIE>6qTL3#OJFVl1Fc*t%F2e>7*Gpc?(0qW*oK*B-76w|m)H?v53fu{J zMF5i~Yl*-Dz$}4x>O*LMri^&%!vGoa)JJeb_*-`lX)%EHZs?5x z&UnrZePCJ{sGxFvF$prJ37|yvpM2y2s{lFfkimU%KN#ao=UTSlNJWC9dtob&cgHdu zORMpY+~T?0u5hgTxg^)MZ{?9+f<508!^>j$zp*3l^|TkBGcwWwF>HnV1TY?jZwsLP zh06ud{z8N*7x)lhk^q`tI9%Z0fLMW*SmJ&c*bCSpV8`wPv4SAAAXX66 z9w1f_gnSFc3W6*EF@hl2p!$DO<`5KzRqd$Ci(i@nd@Rrr;8b8oP(0{P3F-v6Ss(#m z7KGI@-z+EzAl4R?jD<|BEeKIMi?sz|t?T@f6XXSOI8U={@P%rhI*8GwS63(5zWgZ_X~0s{c(Fy(><0-}v=yB0JE%hg2*x&p9E0MjwM zP5=$aUMesYfT}AOgbvNF6u<@i(hZ;$01RM&KHN zih~_NXib(`(6sAb)VfUg9~0PmT3mxG>?pfP~E1jYiY z1S$ZiyK+I}02u#U{D6JSHH&Li^P}5h93cruasF^>bAlrz>FAA)eMv`q zIF_F~p3C>fTX5fC$GQx>OFlFWZ?DgNkMEXeW^c@Ol>f51hhx>Tvxs=%cn`4Fcs*4*^-&=w)~O~c;{S={7fW4BL&!|5hpKiB&P;R7-F#cFR^x}`b_;WAb?8Q61_}j*8 z4Q#}|nst=x$$s^V^sJ7J0<@HPFHZ8}G)LTk`oUqTC5`u|_H>OMP`}8}=?wAHLqanf zhxW*Fd_TCp`><|}cbAUx;xS%4#f$Ir;w6r_A@x7XgZaT%xP#qOJaC#9&vW?yy&S_2h{9G9PRA#^I&+dhtUJpON)-`V7~ak@Z7-^jBOy zqw0g3=(o9iZm2H{am<@iZwdXvohC|C&hm}p$tm?!`n4{fs`_qvSW0r^@RW2{c6EJK zfc478`%BBcc(UWcTkD57PTpESRPX2LIji2`>OZG`-G{nAEbv5bODfZ$bEABnKU;BZXmKfv|m@Do|SjssVnC<*>%!x`+8 zftu%v8+oF<&4TUKCtTaBPZk90b*{kLlR+I_uBT6a)zZgy%vHPgJG@xB*QlwlX1ig*)^|?ApMpOP{|o%D@Mm0G-}$ZZBm5g} z^V+iu;#}*WJX`MLn)>Y72ZEi87q!=eor|C$|2L>hf8*?yaCskD3*LuUJigKl7mR~Q zr}qTD2RtQk0DzMP<$@0asss)Jt`fk3r&ET&j{x@|Yr%){x<|c){DdQ;`os!%1Rnvs zBSA+2D+GQ9ED$&bm@IG{FhT(N#&;4p0SFQ}i8n!hP)Eg(!zDO-$Kyx~!a`h|H=NCC z<5<;oV<-HhRdve7XvY=;2i#kWAlV_nWhA}n%{VCR1=4D^0_G2x0BDs z4@ZY;zDIQEJ^0cp4q8pJunm{`D(=Z|Ft*iqeY)cmNx9Tj9{2H6R|E1TWDOu#U@dO1 zVvzS=WT#lAJ{DcFj7K31;h({hMU&E2z(CsP?e88sc2de&To}V-3S;X@E<^?z$V;5xhSw10Iyu? z7Qo{ITLF^=N3E`y|f`pr;CE3SgNx?^CCK1(4~P`ZaEOU|K7e zik$K>>IQrRm@n`xV3@#n0E-*!yk6-n6FhYf0OO=w>R!Mj0{Z|{1oi{^2w=VG++G0d zP2L#+RLr~?l8W^v?;#2K0Z>6;|EI!A)wgQ7BNYoy9_IsHDn>gOeW_gP5r9(w>rJj% zpPvE65`y(ceWaG0dK`BZ(0Jui(af9|1x^5F$)y%Qp_S85f=&S<1y1930jjH9DvT!^ zv&Vp60e1+X(b=-F@q#crPD0M&${*P)m-;(koxnM?-~2cJ)ISh(g9QBvNE0}Zt9a~C zF7*Nc_Gti2G4tPUV082LXd1dOvp_<80R95LSWdqb-~lD$8KEYC=>kmwr2=0mE*Zl*KY){R&{+_00XYR zen#fGQP-;^Lo7J%cTa=}=u3hoj(2k0+=rK$i%rCcyprh;bJMEj8g)~fuUQ~&^T zJpXfni+~s05Q4w?cM0eKw>_x0vGQde@Y#RoJrWIr&{>v-kS2gTB?L=V=iaJ(?BTQj z&dnq!0Ed&G1h7|qEu9>KwJPsw3Bg*GmoI>|Di>8(E(8OYYgPeERqp!|0z1upLLda~ z&z&HIwJNux0Om?A>aJV}>@vs90c%x`nFFS4&S;5-wMu=%k{l8VFf+hXr9PsrLaKqoZ06r720%R5A+jChrNk|94 z00R3T5C65SWC@DLVFw1PTnN^x%%=sgR%PBHfVyOk6G#HUD;I*CGBIWbq~H*APykC+ zMx6jwr;PjEOSu+;5BxgI;ER!#p;ya zTS8zi=@x+kv_B2gLb;GatXE$O6aiKXbOGEe&=p{sOfjIRgmeR7n3N0YjzbkDrU4~@ z9RfW7uL{_aI`uB0Qh?d1y#UBXxezoU6_#Q^9~{K6HW<(suvMTR;3a|nfPV>Kv{S|i z3z7Jw;h0Onxg&jQy0J{7ng@T350 znRuH(8DNNMbmbselyV{HsYEOr28_kw?I(c>z()cYs|2S&C1AGeUH|iAV;xQ6&j&k( zCY<+ko_ckV9_AaA-qfMTpAYvE7otRlb!t(HPm@ls98yy{sd7w>1sOKnHyS*=iSE-R z2=|g4PZypani@X55|O4=j=)#bOZlO$;g#c!6!KqjgL^T>ri@3vam5y$5A%un+b?oC zdUQP>;Wr2OUmc^mo*!xt={;r4*h!TmCQg|EonBe3G7s>JMy=omk6`B8#+SW^S68E( zTCj^7_!Cw-X40f-)5lcYJaopi8+-GU>PD#VDulurNf(|U-ZUb>ca=M-3T)kzpJuJD z!k5E3_MbAXvS#{-s+#GQ<19!O5#WBe*PZ^85AU1oy0iGa%?GB7FRSR#C$Is7^t}_? za28locbUl+SW|bI$rg-ucbUojV0HHiMiR{eYcgN0v%s3V%S^Vwn!25pXs{;r?N>1F zZor(>r=}TdfjM=PnQSQs$V|4tn!0so95@TCshiAX3#_SFX0iqGi)AKTU`@p`lP%)` zGLtQ+axrHz+poUyS8PmWz(fSeOtxSFG+&dm+zgPJ%+Gaqm6>dr43L>@nF5fRY^eei zvJf~+H9%&vWh%~%>Q(s+ssUjtD`%Moct&74pjKc803E9w|I?+5%w)?n0F1f;j{*i6_J=B<_|6Fu^lyNVz%rbl?G``}vs}&xP$9@GPK@z;ghr0A_>wU@qD6 z0^lnNc@bdR^GkqQ39-KnG7b6_K%4}RP1hA294$)dbPYHPq5F_vg&dAJ3 z-~yPF09}}c#=~L!EpLHf?#l5?O>`G23pnuFcFlq zz8rnDdQ7~_Za{tshs6=!1V$f00jb@06qenaZ0vTU<&}f ztQRS)ZGbTX+X42jLOVgt1-`&p7;;gLUusG-ZTM?|X~Vk!gCrUZDJ??aTbycr zFYp}z9j2URH{el$J$|5R4(IKoqU}ktb3gWBzj792lCoC-^EU+pWQ4#rQZUyHI0UE= z_z}=e;4mOs07I7ii@;I9I)R^I3CWmi%2|#9ZV@;R7$#5;NEJ8%@E16VQz^5_rvPS? zPXo**qxht0lIB;yU;}U@JOeU&;WvQU3ukdEW%k1F0J9g)0nA?b15hn_{|Oie+``&5z3~_m^`+!rW_2{+V~UP zLZy`dG;@SzeG%vv$!+M zQ!lKm7uEBgBHAci#qzie*vWmC@-NDGN*_umN>fPA50p0|P8>yHgEKo&*cy8VmVGUQxn?*iw^6R4u$mdIA?aNE(`irXjFMhU8AItw zVP5HekTe`lv^2Ij?K#Rq%0$WlN(vSVX4^Spk7%?>ll7fX?OJSUp zXDIhlZlv_0bfPqYB!5YHmvR@SoRUFl0WrRDzlmE0+Mis@->A=kc7u5b1CJN9uzBuHR|*YWfkQ?%2di#lq?E|GoFo8pH+yr zui>VH!iefwcYG1I7}5Cc5jL^oTFU*Dn<&F5X%vp4`tCu8o!nvtJ1nRCi!zCFHKi*h zp3)2wcaXA$_7~^k=A)FE6b^n|DTT4shYGCh04qDd%7z)gG~9N>^O^)Z;D>XI^{{aKVlQa#Xs%OtTnKIJJX>?UZmIj^v!41M-@5nz(^H@B z>w4t53wr|`i&tGZ(z->zG1D3@)R7j>Kh=>jc0;sl@#+h^{49}uE2o?HC5BF~=`nqX z$4A8d2RLtdr9_WhaN8RfT(`Y(@%Co4JKqaBbTq#9G`Qg_VEnnY?zB5P+k1dz#1>|^*rjx~4S;sWwr1OiD`pM_o$WsW&Tl#!=0aDV_^3>5XlGZm zMOy%7i?#wW(({FL<{V}wJRF0oDJ~{+}*9&_5s>T$bPgxAHA%c z4PBi-L*M{_Q;*JuNzxfRl(QWItQPnIaEAbv(9S~y4g=x@e!@WmUOC$l!0T?X+m3=3 zO3=>$GlydUv#Q5&IDuErhA}lSHf<*WW(Fq#qb1rYK)S$b0PUR4_6v?Y$`;uFwqHSW zQ~&@|Jjbj81}i64LeAnSgp!rB{SJ6b;2hvCfj?E>I6!~Rg} zMbJEV5UY|kSO9faKcYpR_NFXzJR9$`~Xz~O#nRwngZaJo8ymT*%pBS9`_y- zYKEYR0?h%L0)aTNp^ucC69m8tU;q}o^t%OG0rI}Tmt+#wKOrH zIpKhT5)=Ut3!Kvm$3x5t<@n>_sn57p9DICysH^qJ*B)veQLX~?tI*_HzV0|;A(QQs| zxVU?J+QiwFsw&1Xp`=S?qZ@_$;zbSih-ZvS!>^z=R3h(kl`uxCVR)RAm}#s>rtX1hG#bvSzav&h zk66YyT%zxt^(PUvl}FSFwkDWg!F4`x6k$;fVft)_c~6TQF5X;@-EgWS{?}>IkWBT< zKbaN{N!$Go)1o1E8&=6BldEp|OY-<~B!hFW7eN&RCwyP7 z_IU2v^w(XFHQv=531_=^MX@UGXVRnt$92lCV)NdY~3Jc;*;s z9GO(VRv16wSjIlly!tfBoya|~e#j@uxh=U`Z`WRaW2n&>G-(_^6!Du_kSV{+!`An8 zCbyfRrxwzv+&>+uo^9e%=PKOA8T=R19Pnz5JAF3O=Q#hJUaDI&ReC<&6Co=<)ewNc+lCL@?hU#BIl)IB7Z|7FihrdJ=gPg9lX;4BTFp-0+TMP7N* zA#*MIs%e!K_!+QXm1Ad2m{3`xDtO5On_SNLc=F9ksg=3W`HJ_Ck9kz6-;h_!nG*j@ z=DhVcoUgR1@Yu!dJl4rPk1#dG!#$>gd?z1CJo4}R!<)`(}>bWO$!<&F*VlGZp7ey;-e(`km&r`j3&ztdHuo%T_;% zlZ<TBjJmMTbbVOUV7&i=e~G%!*d2VLZtP}V9}^MmesjgWdw<;r-|Kq0Z(YwHbp5kk zdOW^G?xX({V803x+IOR*Qrb{(#j3^NI$Vq4lu;DcBbt{h(Qy>^LKN;(7{3D)#nwbIgQ&HX zrzlIVrLs9uH&R%IsGgKeN_$FkNaPvHLCWV8jz%O$BXTiiF6Cwl>k-+XQdqZfgO5Hf zsLuJ7k3O$yW;j<+{63-aODQ#2`fJ7^9-O+rZT=96vCp!}8MDVnzdFV9S&2J{gYnnh zfivo+BE`}b`h9hQwZ3{zLfsS3`RdDC51$iKZlqjf{F&q5m~9Vx@dI9brx(v#l=;4| z4)LQjp){rV>vfs$2k76x!eQP4`d)w6kp->8_0=73Z&P>ThX6gJsr`aGFWeEKv+XKZ ze>LF^Ui_&SyS(@{FJA4%D;u-j_HyGLwr9L}g%{88;uqG75-66PDL}&ZDT>aIAzw+WQym+S3L=%%y)NkOi17kPQgaTrYeOIW@%|<8`iTp0msOx{({y555&V^XIm5IWOOQ zeQ537k?NM?=&`%yubc`aQhTow8cx?yguP~#J16yr`3AzX44x(^=Po0{o%eV0*Q1QT zJ>!;qSZkkCAm+}s9}cFH^YX-8-;%riRkj0Oo$StjpJ(={>BQ-?Mou22R(khKvkmLK zkryl98lKsQ7`vEzW#-7`cXRjS6-o1PfHMBNb9b%}!xe<{)q0Gh9_?&%B2aJVR}t}H zU|kV5YXg|4UibdlK)q7^ii=*Ca6VA~-bc@QuX&Ju)~5vFbY4HMVKS^hISGd$G~#Sm zPv5B4MGpLGh<{yKaghEZvaV}hqVkryvq5kf(o=5El!0+`mKcMZk#}iS%c8oWw*~2M z_iS>AO@1$Uq<&j7*S()db&OJl+SMg4P701oDr4N+9P{Fzz4)k`v)u93KhvU?!aupg zGu`25z4$kS?V3b5;}zjoFaE`gPkZqxFFxtTC%m{`F&_40xNEq_9YJ`v7k}r)-+J*k zUcAd$av(@|j``ZXKh0gvS6=+37k}Z!JH2>^7jIt_axh4Tw4k)41XDsNp?Y1&!7%;z zZ=0RNMbi#Gt*r}N7p_0j1XGXB1`{>E2V;YMCX0>Quuvtt%fe@W@w9(~kwmlQ0+tHk zyfA5kKxaU{05+1`+nTKaw+B(aa<)QD{lsNLMF`?qBAu-Zz|5g5z`ayxwqm>*u-%BI z*}4Io0^I>TTcxv=01#Ch^adcIa<)EzPyw6> z$A2Tx5AccrPEF%y2n+zAFy(9m0cHk+@T$QZF|QdVrb14Vq@zBOzq~GlOz~{bmUo18OfY7H>4v3se9e6Bq}8 zttn?i)7pP6FdlH508FvHMF3V6^QOQ=z&wGQ(El;$a^-BOON?3XNw{G9Q9^J$YWKDP zY^7R$fDUJA(?KvVa76ONjV$LrA>~&Y+RY06|e!;3(Ns55J2(KutFYsG5m8utt1G$qP`cX1-vSNZi_%y$;ZFf3N;DW!s2V)>zwH@NGYNVY=S^P=p!krN1fB=LQk1j305B)~ zi-3+2g80F|3A_yWNZ=I!`d>L4tSY#|us>8`B`8yZRsnnkR%=?ztpaNRW{cJW%r1Ei zP$bb_2lxxTfuBMBM!*Fy`}9p5nOc}RyoI0=iT5_3g}^)bB-xG!toWf)Tx9!4s#|dF z)ZMj8{(F@BIe5cuxckbDl$XB^Co#Mqxc1>aH_z1VjJu!e9^zk0L@6G3Kue$R>P{Kv zoMzm>D}BC;ao@6S1vWkNHEs^7>Px*FVyIiAm$-e{5X1W(bWiX6|HpfurO&tiYn@v+ z)Y%O6L0d0bBZ=W%qY~6e!qhl;K7{l5IbG!I*@qKS{!*U_DMo$#DoSOu6*gPBBYGFi zXszUKaq0Nomx&fs(GV8p)PrW|@j(?m{upQ$KkN^64;H(mcy89#hwEv6jObjA%?x8x zKO3&+w(>~sT`9Xqn0hYde&nv~%A+^R#!sAtUyIiobxIFgjk{K=Bi$ycp5QgyV*dM- zKJqlSLq!kURCjJhMgCg4VUTeiF^zKnQkZdDJJutjnorR+GjOG7zLage%FxdvmU>F! z`Jm_0_YOVmqK9piyVCAk@*R<#>@u>uL>>+Glc<-kA4bS_jk^r>UWa>x)HA&Q_O4J5 zTR+b}{hv&|A&u%?XBr+s1j!?ahCp|FIc`#hQN6#`$#_AfokvVH!mT|zZ^1Pr z{conz5c9I8(-7ry{oWAc^8IdSl|B35pDc*oPCx8Us;cl07Q}9+A2L!pQ_x#&F4J4? zINE<(AkEw~qaK%CAR8k7eUCIQ*szNKX#wi?x_$Yb zJF8G<8sBBJU(%$^SgPs&X!O4Pz+HeU_rH!_tA^J(yO~Mdb&I@o-99iT!zDv=k1dz8 zgWO}YrKz0f9?$gsDiCHwYg>`~ki|2a=c$`|ORvBq#{a$y=04=(xeTJjhjgRb9WT`= zgj!I?2uo~7)^&h$aPSO0y@;>F^r)_$H0#Q$E5=N(xDlTl7&3E8WzF!anbQ!>;}Dv= zqI-vZlJGk^$9K)n$CyWZWg+3eH$uP>xLC{`TA}3LWddq3=w zVIs6#BHxLwl<>1|%~xa|~j5G$?%g@p2|YGD`D?WS}PYh6UUs zB6~5mA);Z=W$MExFmUC(OWF2WITFELsuYhx%y=OzB;2`s?L{UcZg^s|Z{6n_8{Ioqu2lo=$N`TU-I=8k_1Zu^{X{Q?3#s<>Wv< z)N&$DOU5}P7mVC=UobXA_r75C$V#8>Y+rSqZuP8T*s{0Hau%P6)Z4W5sD9Y8x11;I zBlV8nJ1=ax32!;A{54!}#r)qgvOxY@u`9;ehi@8t1fUIBr;xYPj?;kVY{Og5VCL&Q zj~CaQM>k}lsrei}7T?n;!vbnt@fitR$P^dMq01&V?mzBu_h0C;8|4z4o9;_&Zn`hA zx#{k)&}9?c4gYJ8HH7sxdd#!^q08Dh*P-%pjdw=>wWmUtMLQey6n41F?Csf8Y*dtU z&vE1_J;WW2dI&q*NiW?)OfU>@`lr@?jCY26eW6$3X?BGEf^YEV$4+pq(UkKaShwcG z2z{F#{P~i{@g<)T#_qs5b@M-t(Df!QXUrIfUG0tCrFHW*MCjl9)=k?Sq3`o^`c9dI z|3|!iM})pAA~vJMw;w*7lr*6p+j2fbz#4?*3T}~ufQuhk@`hlz5Z3u4G{|xD7?v;e}?iS zg||8LKc~D!!7WWK{}IX^lxoT~lFO`U+vWDWM z+(Vh}+*E48DSA6X03?h3lC_V*I%d63d7iSEGK*41=}%#|s2^rA{@40@VCH`)Y+B}W z%0fyFMm% zc2)XL3TvLunx`{uI(sRdHBaZDq_daOQz>k^Jv|7T_9ul^Nc)bmk-{pZ;hwtj{gSjh zxiy_qK^aadrqI07T2pw;NIgYif23}wd_Z}L@+gJlol5&my^hik?blLsDY2C15cOfX zlzrTyjitOsp|PYapj1(=rmz=MQYc}N>tvL3$AoAsvW>B&1LbdVswKYwU}?1M1@g7F@7*v7(O!>|GJ; zjo;@pvvXWd^m%^2*Z=jO*DIgQduMic%6De>c4ueyQg&0AyR<7QEY>uZOxg*Q36vbl zAPO^=`aNYo?msqQwv0%KHU9raJOG7ExsVI)g&74bE2w zIm+g;qi%M+VXfQ|E%r?E-+^$|QEsGf8Rd8iZK*h)l0@kXDPp52`jkS0QXS=8^j$+a zlhR08L}8s36;g&!A|QpoQFwsD*D1T%+6(Wdm$p<$b1rP5utgQlqLffrbE@C$17E=( zl+P&a%?h5T?4aC6VY8AS02i#Gk69|1PdSF-q0k5l*tGol-$C==rLbYf%lx~omA1EJD*nV@E z!<>64nc%M^Ke6z>MgN=i9}1?4TEBvbm){y4+8!70tvl2e?(-taIn9$K~PpIOSD36yLK zbK>a%asNX3obnds8Oj5c+bLI4&Y`TO)KV5uCQ*h%{O$~TV<|l#nZHmzr@TdZhVlUA zcFL8Mb0}*mwUh;vNtEG~3`#7e2PESc%IB1~D9=zHpxjQmk~Nlb4rMK+ma>2{i87p$ zL5ZdGfTaII`JD15F)PQp#zRCQ1cmI%N#SOBqaYLeg+3zmfJSy)@6XhbcTv z8m&o9Btj!sT{zobS`lR^r5^=k zc?_i~4^5-}4ebwa@?pxCl)V(Tu4En|`DzMFG?{%zGS8mO^d}FY;Lk|nZb@%ZSQ|fPOSJCn87r>o>ZhU6O`8N$VTsaBL)E2UtNg|^^atrI z5V~p80L&8Iw3)CAmL}b_S%4RnACr{=F4Mq}PH;Si-n1e#?gT5PTderqk5J9O8NsLBEStn}l~YpehU5eCeF40jDd!;ameK)36r6=0xW_84#uXh7&Em;D8zbSS+5U zvhmV6aj`5YRer zCg5S^H=JkTM0p*W&IUATK-ZGTOCz20TtK?=8_x3pT{N7J8-n*VTmX1L!-W9$lXOn> z7r70}Z#dBhtNF;;OFMBj&1HW_=e!uuRrw9)CAdLg*GI=Ca|7#WI4=XtRRM|Hv;-xETwb42;c>l&iNAH zB;_}pdjJzUfmQKTtav)HqxbyavHyLSoGF9iv!?9Ms=Xb*opcJ~swp88>>f!4b-QF!1cHtf zY)9Yyd!nuCA5mS;h)P_(;>3ckR`U~aVqBQ{*i&($+{}MEPUKk4&^=)hN3Q8O%(U6o z_~+t8Zg|An7f<9F@dxL-)M|PuP7DzdC5LVW$H=XQJJY&&Pn;+b5#4X`^F^=P?3iM` z@^YM5C3=c^YKw2QHS5(lu}MT0*Uh^IGc4rUF|(`>UW*ee#n=bsWbg5I^par@8ti^- z%l9oG_S)z#BR7{EP~lWPSY>(kaoY#k@s^L~t@45ADcR^E^C;>~l!HF!dtAT~Pv zjGyoW<#oyplywy5HGvsR7zl~~o`O!ph({N0#Ggu;OJOMu{*i+2${38xk1==+g%^s! zMU;M!K|fR8q@cet1~J~CX7*Bpj;A-Df|g>$eMv!AY{XqaX`oD`WK(EyvF}pQi5jt& zQP{7>&Y)yboRAoncMSSWBL-JTHEcU(Dt+v;q7PH{LHyC^`;2H@Rg7qMdeKWMJY94K zWFR}WfiF>TNiqhWNvWcgP*Ny8AW>gaUZ&hn;W?wEjW40kM@glyZy)eJ1>HUd1tw5g z-UFDp0X*k`t`s*V(MrD{L44e8`|%OuTwx<4k9@Tjm<0;3KT{B;OJyVg#7`T9)98=609WCO;?FSaT`hM5;uWwEekVIj9eX~mp zz6y7jJ;x0Z8w9G5&Vj7RXHF6l0W?PmF5L5dr2K|s2!NJC=STuvq5OseHIvVRqjSKl z@>#ER4wQ2~%ZJW^a?DRte!~HeKVN7{$Nk&e8c>$<89dpM2^g+JP_{YOXuxGXr?&<# z?vOs!0AmW=RykmN@;M9x9XYt$p`p+@=H%jL#3&Sn2dPQBuT1>hy!d11bqRDk+E}zs^8Y1HS zRVRLGr@za#;PhWQyR~vfZA%M=(zxnsSJk*yH(`b^Z*x^sZDWh8lA~<+vU&y5*Hl|k z$BEGX>Eq6(wVu#$SwMb{ZIC}iJ+6xSYUbV5+E|UFR=X;>j)zwkfn9nbxjEsplU31o zPWpuu#+N#G&EsMOKpoP)n-=36U}+YAjjO4lfu*f>Ev|CatY~am8=C0VzN3}&;(1LKjg2);I0tsGt6g4G zx7JmSfl5nJfjD!6Jo%E=x;n&J!il$(88xZmt*%+(uV}?kZ`6BJMJ*b_Qf1o8`u|dL zi_SV)BHBt?FcDzm5le;jRJ5#ZtZ_A?_F7!^HEY=HbQJ{(t)dC#;%D_$u4QU#DqzyA z(#o2pwe>266|Ui4hUyAnyY+IaZC7RMk|i}wA;on3qTi2}!b>_8QzPz_ak_?jlvRr# zm19@#kz+5Zt*NUXd6a3(T(zn??Glwu!>a}>bt>4FWtiy2g_!_hMbf@8WXUTnm-WD- z>$2^y=ZHLh)SoKp#=EL#=v6iMPkU1rjQUWW#oRyNRIxgDcD3`^IW)T}TufQ0wW*3% z$s7@!TbC@!#=2JKiYX3QL2!Ci2f$SO1IgB@p1WnON{_fXxbs@AF3d8zy*-%iN_5NyCRl|x#98Xn4Q_YGBv{YCvc~|HDsqyd4;UBC4&3Kk9UR2@HDxRG*GTd?$vk6VsSs8X!8^Rnmda5`WYjnCw z%stC#*Yb4%TPS);Z3-_vvT3{8)MZ;>Q}1i+9e>c9=6tb3Z4x)DO-rlhys=7dipkFj zuC%x0*giFo4}G;Mx?#J;*rP06(qQa2kgg-6jkAN^EW*mn85lhl$)E2X-0pGf@3c!M-Hm_LA|PdfET@K$QDa&ipLyCY=XV2 zhHN?A-qfTvg=5pS_JyrOgtf3WNo;vme2sg;WU;-s>E4kloK~-C$s$U0l=m#_Zq-gt z7C-m2Hk^Ol;NMK=%_H~M_aMw$zph>U*8Xv;QooLK1o-CyDQf)4Y<2<_1A#A ztHf_K;O;8%84b9*O1wz}?yeGf=R{Y6yQ{<#l;0@9VFTaNmJCOnL=>8IB_r^=hd)H< zN=5>nRDPpm6ySOdqXGH`uVf5ho(>rcKx(Bc83)k!m?b6n(e#-P84q|&f!`?cfv(d* zrGT{>CIGNOx{`^2L=DH_2iC6|js?7~VG>}QhRJ|74aWhF*DwWuLRJq9OK_V#xR(x^ zhTmlRcDQ6ZK;OTX%m7@gqs;`Y(J%`zTLV0UXp(d#a{xV*-zb@j-*O*mK&i!9fjXKG zx=IHv0O(uo64YEAN=&+vg@AYsC*pVC&l(m19@nrKU}`7>oUEZ7fMiHlQi1%(!tfMT zBFL$s3cngZ(NGQ8t)T`Gu-YYn)j9-8jzLLCSF#L%Uls~#0o^sg%A-HD!C$f*^oS0s z16-+L1)xPkJph#^MQMg}26W}2Y&49Bs;IIRcT~+@jtsoe+bS0|* zF&dDys6!f1+fgrSfRRSst^uVRb-D%^Toj62x)QXLs1X`Y0Yqsy6+dDQDEot%zzzam z9+#{K+@j-c0N_u$l8pe=x`NXH#Tsz>0sS)`RJ zqQqhO7N?5cj)VcM^)4zf&4bK$VyoYhRPl4H_z;icQpL$VI@b3Xx7kWLH&uL)U>?3T zRn%FI+f&6AJoHNKYT7ib5`3#L(;}-xWXF|7uO1UNZKl<u(*OfCz?ie1(tuW-70BL9 zK(!7*xyv!0WCuDHIm%PQ96aR;yy0`q1>LWALg(haOv60Di5lhuP|DId(7k#GYd8VV z$$reuzA9c0+4@b(vh}cT!7@4DKJKY6slYeVi|@ z?BW00C(SM#>&TlHwc>BS{r06>P+-5me#WLk^$2~n>mBx}eS23Kh{b$rTLK@y1+C_z z4iJw5q*DiQZJYN6W?OBt4^DOg!`laW{PbW zVsl1r?xkP5y?Xii$8wuGrwZ&NOExgYwhNJuyk=5w>-4K9y;~mQ^tB)T&d1`#_H5Z| zSN&>9?W$il*}KZX&Z{D+z4V(WwU-ZXquRUvm@gm<M}orI<2|GF(_)e;gsgpA}Q^ zu5P3_Cpu!*!}lO*b|c0psEhZG6dNLrjNw7xv{_caH%AI99K)Jkd1s_J)Y~6{&30Gs zY{FYXd0odgxRPveUwG{Bk{mFtQgbdi^Z{jp%Fk>T} zb3K;IV-I2<&~=AiI!^{0u5IAQgTn6vvk%qr9|gY{#E*er3gRcgdxH3x`PlGs(1sVm zuLSXH;8%nAZSWglRtuc#WAI1t*a;Yd5>!K#tgF5pDcExWv~yJSnBT2?SB@6*E)<7$_8cRk zdz;z+$`g@hqSWbcMZiB+YNFJ`Z*q$zb*ofUYP(dw)CQ?#Qm0EDD>YkcqSUST@+_v* zcBy`;4N}Xbj(wfyijaEvHTu7mx=-p$QZ2RR9v*G0^qW%KrTV2dD8G!e?^PbBO!}uw z9V^v*h1=Vq5msa57*P>swr}A<{ZbpGmPws1b*$8Esfkh}r0(0p10~iDfJR7t>24n9 z@G@@sR_Z>fFG;neZk1|EZI|ko+90(|>U61zQV%bcN0a)JR7>hssixF+seY*qQp==H zmpWEzw$wza5z2pQ0#go+ur^1H5i5K8Zz)%{5~lv;{Qt~J|NVsR2NTB#$XE(qut%*wVHxF}Mww5^qwvRJF7N+ycT%pSv{869$Q?&X zq4b30d`Wqo@(^V+e*xs2Pj4fIWu_M7WKQHe?d)&h%jPcGJ1CneOmub)1;31p>?laq z7nD~i%uUvfl=YN_ltRiti1#}R9oJhI(o-YXuRsftAEjV31uaP z2B*HN>J4A|*A&`9`aP60C_GB~YzlrM8tFqQJs@fCQfOOgcT!l-Y0PliB8We26upBe z_%l-XQ5ZXw)|$G3!jeg45vE2!)R1{rNeZhZSPFg-87bKmW@IRfdgv<@o_Hvu z3_X!DjKV0%-y{DgPkMZx5j-oUbJ-+u^s-l>{wZ@ct`XW!j5()j?Hk6 zNoM`Fe34)#b|eUg<@$EK@L(O!zqt#y_sKSQ_U*&-b87m z%%CJdJj{~kY06a;wr!6W;$|w{%&VLGy8W~gH@i4DQ|M-KxmzggJlw}qSY~eaRqAW4 zIQpCr)r)^kA1f^LCCV-eyOzuwC`>-OcStK`31uFI_J>clQ2z3fOy(dm%Q52*3T;`A zn9kTk-)_pi6k1cprIa%$t0+q;^C|4UGEl5W2Foa8Fr_yn{ZGobl>HRevwV=_S67bB zJ#yDxLwNG*pfn1 zO(~YGSS)_(B2vx$iLF@l=6knKxG}B`D`53AYoW2`YuoD3gFgrVQhWQ! zsjZ?f6VPp!IXxRkH=DOC7JbcnXz9$qj5S6?I2UXD@4~kHeVDV^+=}Ba88jMk`gC41 z6IpKyr;IVTRb@WZJiAN4Q}woS9fZd)wg6xLPwK5=%JzV+BB-*U)c!&hPU zFZPM7B)-}X4&Zz5PMmG#BjT7@vIPYl!JX1MqI+m(9zq)miZ3tU!Th$_j00CK3f^{K z$Ud~q;P5*-vvYk2vk3&ncQ#p#_b(PX-P^m2^9e|)FuRPKAi9Qi>4TRq6NHr&J082h z@q;t@U-NA9(fSGE)L9NTOt@*K>c4}g3>VANj$qS;)A1t?nByBJ2-82+-fo{b z;*ia=)qe*Y9UMP?qvQJxx&X6v1ao$0cJdw!PREzmiy%&l*eAmS5hC`7@cZot8zOZkM)!|esNp62 zcDq8uyMQzs#Lf7{_Mi=XTX<#zqKJ6 zHI@6A0zXFg;Oe^94jQ=nYzLod{Vce{pVb?2z8* zQZw$>n7M zVwbRdq8)#S9m1#I@w%|$FTlVMogofc89d64#;5Fq8f?Hv6MiFZi%qU^7^9J(fEOPl zwp9Tt2*V)kyyh54Li7(RWEexDr`RBHfgjjM!&ug*aPO<5QZEL$hD#CNr0XuGN5ACIR>9}b!-9&~hh;P|O#MaEQ-Fhfno$AO6)Cx)@tn0Zf)yj;!EU$q#>C%4(7 zjH~cI1>>1r%PN{NG7RHEF*5b!nx=-4uGVG_NM37JoH_*GEtO-|!NF0kB@IokDvVRB zZ*7zVM{8EqR&{I{Ixxn(HhHQT(dAT~juWbDsxVj(qXaqbvj$_yxJp=XnlP20Jh?m$ zMl07eA!;qA_rtI>mFm?H} z4zTtjbN4&^B8)$>5Q!t<5C!8RmybBw>e@0-ov-9B4!%VR*SUg6de+uAwzf2n3?9=L zx~6qDM%<~@u>JMR_56f9Sbf84S91*~3&n8aku00mrg{vNti^a#4BEs9KYPHQt7QI& z()q5MRj7ZA4{u$HaqK8$ImW%FzFL+9#$2yyX_cee?U8}COY19eL=+50Y1cHZs%Qzv zc*{DoZ{K*#rjq2DDi-#XW4TcgZ~py2#)d}PBE~qbs%>a(MjfglhbRP>T?L;1x6zIm zhFD|H!HCDc^Z#!~JYv4YMp<i>NN* z(8}sz9@&28H#f94RpD!d7TC}VZ#B|^IUm)iaTUXEC0;r9I5YwWd1ER+J>;A%T zg%wW~v0?sI!Bx1drVeA=M<5S@MjRL|>b2915xE!+YByo|A;+*BY4^$yl@$e5GY9JO zAoVq?F-*QueIt?;DC$8lD`&^1#_T@Uw*8aEXkjiHF;zUT3V+osY237=;3L|j?d_b0 z40vCghVDduY9h-!cnmv}BF8zSfOB$Cd`+%;j^dU*WOYp=2H=hw?aB!xuH)p~rOwJ~ zyxLTqS?6yH)S><|npzE^4*k@il3xqhh00;n>~&;Gmf^hS`|C!Cs2ZbslIjLIj=%F) zD2$FGsI6<3qj~M2-uQk4{SiMgKxieq8BXR2^Qf#rhs|-^YcYH|6E-rTTvJietLVP}p7HQa^yB;|$WpG=W4t98tOut`_CG}JQA9mstO@WBg4 zsjF{dupu`W$>=AqMW{wt4(g1_Gjnpv#lWRYXIE9!D;u*bS$;dC`Z8I!=y3T-3N2p_ zVds$N3jJ||90FY(G#Z~nd%9T*mJRI(r1^ zP^PK!fUks(lw2804x{=P=H}q*ELaXIrqVViBu}NfU}TWh>K*a}JK*aQWq*2y;wmw;mX+_J!4N+;nkrSHHYn$2G;%fqUUk;i2R7 zYnj`X-|*l@H1jMCR{`c}xEheA;Tqf}|D@qs0Pgms^Weuu#%2vB0C#xOc{bxNIxw2X zb3GtQ2i<^&DF-#&2zXS(O@NCv;8#t0rG{Goc^Ym7glV`9ckQoexE+9V(P3~Ae(}mj z{X|Yam&PX}be_8a(aLXl@H-?G_lVMYwgPr)*akRX!##k78tw(;Yq$^K)PN^zDIWrm zf1C(Eg;Ee#IuCw0q-@ZDN=TWZ0r$cwi5ecjPl|6eJO~J+=OI8KJr4r{=|PmCnR=f` z@XO;Dd46p1SfH16&@RAc4Z8si8c+$zV>CPl=&j*#{C0Uq1AZhWp(v&EJP9~i1B@cc z{=Ls_XC|^Xvg^ z(tvEb%53m+;z5`FL*MYcieF5B=y{G$p0Q`zm_3uHkCD_kC^B#VheV}0<02M8rXFuR#4etX2 z=|TMX@j3)H7Fg@c^AUc|eXc`32JBGwhw6uA4?ar=p%Mb$4R}5Sr05XrJ%|qp={#Qm zU;@&4z61o)^A(^%hkOml(Ey8$JFMYbNIYohC_g!8jzM) zl(KZ59{_0Q3VsBv*6?F~xOL4EKW8RajXHv$kyPb46a zp1y!UdintZ>4Ew7M=7fV3;;Z(0p;GmL&HFTKX8O-P_Yh*0U(Rgd1LWHZLfwnz^xhv z0aj}m3>dB<9)SI%<4cdeXb1`t0b4YhY=S>BirbE&Ii#4PJ0*T82pjhbqhBp)MAGHB_VHFY2svyJb0o<$s<`l7BLl$76 zhHOBf?9tL9P&3kbbMb`xD-C&or!?dPZUD^W22T6e=M)`O2$-({$L)i-(s@xmePA34 zhG7oR&;J4goaW0hMR7SeCc#i81DVc zR0l2`aX@!It5lAYyKH=?PaU^eQ^)E$L3^0*_f8No)}w9H#j&T0Yj-+lh#Q>S?>=wf8eygMo*|q)I%c$gcU{uD{`TQu zPju^gmdpQ!<}Oy_pcx_|tlzo;b_mRnZk^LwA3sApBK+(6mdh;$td(wE_j36^z@js^ z{T#y6L-@E5o*2U8JFzu7afbLVEN@*udox-P-MSv-^1q=utTVU$V!4uLh%I4Z>kMnx z&>3Q1SN|7w6uX(V-5A1V2wxMzmxl1B5WXOU&+W|qw$nRrXj>n`r-bmyA-p<-mxu7& z5T4VCXB%y^J8i%Mr6D{vghz&OQ3&T*n=)sJt$qC8+5zYW>DoRom;dz)Hf^7V@W&zi zSP1V5;hiD;U}yHX?dZIr?fwwHH-zsF;afuZh7i6kgs<+zSS|FDP8*E2_7FZlgwF}# z(?ZxE!s|kKZ7}1a(6%ag1GqVaPYU5_Av`&RCxmcG2#*Qk6ayz55ww9^7{a+$&hQ!H zKNH1Jd{{I?bm`)M8__(EQMOSoqHLfnri`KR$ewVB`xD9&l&dJ~Ddm)L6kN`Y%%3T* zQnpjBrmUwdp&UoS6A!_i{;_s{ekNa%ITB}ijTJv zE)TsdxWw-%uThw%#EU5{6c%tIub7DflJGI*5z2*>lPD7?iI8|^B%Y^>XXVS!l;fw+ z$84(EpkAi$M#|{*)DtPAC{DmzLDy3@QkGIiP;lF+J`aw2*n0Ae8RGW=*85k@ z5JUP6?mtU?pC5RsFsp{O1{ZO_I(!!{%Ckk@PUlQ~Yu*Fr`Vs+y-?n-kH(9*h*P7Sf zUpy7wuWe>Icfie-`qwt2ebY!4pUoCyBKoZxXot*Gd#>wkM*cioJQCLSo3#s`3&X?OPO!%PF z2Yqyz@*8e10DscCvoHks2Mru@H0nW3Xmz8`)Q}69t050CL_MVRpi?!#h(^##=-i_LU6kK&kHP!uK*q-c z?$ROS0Cvq8?h*jo51o5FV4(6FZnTzRw0z_rL8YL3bP$YxSRe`LBZe)|A;$oaU+FkY zb@BHaCIkMZ0S&BpwFWenVq{7>_f&xGH{8?k#ys#Xyc;D|befJg1AsG2$GItsx@wq( z_vgDb%m%F2Fb9AvO6Nw!6$T0m-A%zOIs~>^aD#^V$bUhTCgigqa0FOiK|dX`5HIRK z(17-xf3Jo`fEEpl0Vr7M+^{}5Fq-5p$NT&b<#s>?;65At?n+P~<5d9UN;-EnAX!5V z-uoZYutW|5u%n?f$-P8}ECbYOs0FYiqI08T%Z*Zg!@V4%16cjC{@vIi=YAcu0uZRZ zdO)B^8UXP+S|di~{j6an;8hJM&+IK4ngQo%KtW|AQ_}HM#q6=l{*by=pg0|b(`Wsl zVGUr9hP8k@HNecX0?oAzfP$6Iy$%q__$h#B9RkDl9@GHi_j_N^v>rjXY1jY=)XGKx z>Q*`q0rU>na5`XshBI(y@vR0VOimV0=1Lr%n{~+9fb|;uC>#&WOFH+t0CXD)&I9z- za6XRmm4*udPieRiaD#?+0P-uHdlO*3hKm6C8ZHL(k$@s_!+7MR^4#FQ6!5r?cNqY= zlFofOpj1N#Ze{{`y%O+{4!H`jUc=RZaT>0{J9l}p-3XYbqum4ulpM-E^<^D$3jie{9fx(M_6h7nnrMCX~(R?>+ z7Dnjx=~yAU^+-ePxTSbq7QEHk|H&M2+`wVImtayQwX7eeo-0>JzC-;@3-wrh2cB841@({6$t#uKgv=d zYrl@Q!(J8(L8pnPDQOu1-A6%sExAP`s$iB_DUkAzc zo&H)LM|9mJgaY?m^X^%cUM?mIMFmHJD;dzw#lm)_!K74{WDEja0xlp{( zD-83YzS3)y|E+qI#VegVR)s7eqOFY@@i6GM&P1wAf9dJr&bz zikSJLcPYCJ(f~T zVeNQWz3S1xA^6-MP*}?DU6ieq>nImc)=`#IPM}Pp45RQ+ZWfPPVe3Qsn3&9eQ?8+$ zZ7ha1(+iK0Igye@VSka?9g^__g-OVGiNZortFl~9-)WRa$|A~S3Nw;1l+xGCd@>AI z5S~nZ_0>RM8Ksosro>RfA!*-I-l9BBxtDS?2Wh zUhJ`)z#*mc%?BiCSb!_a zR~pa}=KMV9iqL(LhEoB^p>#e!@-IJSBl^}OC`kqYHsG4{ zhlY)SH#M9FxKG3BfD1L80YE8B=Q|Tns^Kg^qK30^wffZtzwaDSp!&}R1gam`po~EE zpAQIBKP)yQQ2jW4Mxgp(eR3%>9^SVJ5U75XKzgA1F9xvsW&Qgu0Ub+%^Pv*t*K86{ z+i4%j?FhL7uv5d80Bn%XcNG8?t>9|FG!54PGBjKZ2-k2Ou95qc{o&Y1nf%a=p}x(4 z%XGZ!0letb`A{~g$0@(zyAcp*<2L~UR(mt9weRR?w*am`E4m#) zfl{~wkgP-Q#9M$rHQWVwOT*m&M3v6B1ppIJuoZy*M8P&dp!MGa2-6|{d-3*Qk0zK^ zGRj3dA9|H!n7e}e0dq7UH_1aZ?7$m^-!(h{*sI||0Ln-@-$MY{zJiAV3#1qA&xblq zD$qfX03tM4cmeUIhFt(8P&(gk01QvTqkw?jKL!ZcJ*;m?qK@_i-co$2;Yk4gaDF%+ z8h{HGEuHUaz&Z`j0Oo0U7LcdmIY4g>&*RO;XBu7rAQ{s6UIYZn^d&%`Oi}EK{y?F= z49eC!y#nZ|;Z?jB`B=kifL$712f*B=^SuE;FQx#MkT6pNiZH>W;Vpne!+((fz(_A2 zy07>^1B3m>U!mi@3kWo@_W*$gwhs`eqwUA*kRLR>4+vNm8u4J5v2?x<0cR>du0J1v zDs&L?DPQA|eV+iLb;zf9adS`uih2+%MLOT-fGah80cg>H+8#7r!`Fa7N)O=i5>qPc z-}fCTkW`!`E|Ani06VFM?|VQXrPw=els@)PfIv!r#Ur0UN`C{u9;EaA4!BLjAAq&W z{*c!{K>=Gh3`nu#(UxOvzfmgis0x-MT`5{|47vjaT>xik2nSSZ=n5FE0aYIpqk(HX zMt|%3r$DVR$b2tp>#D`>ci$fXg+E1gzA6RQ8>s0XEhbnU}5r$;Yw!Uk{Vi+^4p|for<#$GwfNwB!@uWY5ZDFq$D55i zMhS;`KK!ZHx6pHSG;!bj%uE3*L8Kmb~(+^Eg zvHE7zij=M$_48iq6^J_7eh`X^40?2Geu-z%ae5rQLq@{0=>H&6jI>O6;^7Wk^H9Gl_VrA8R)0K@Dhq|6N~F*S<$`-oc6 z-8!6Ai!XFKXXaQtGqBgcWM-P(uQ+5RWadpo3S_3KJLtb;M(&8roP(&b=2bbhV#N%* zX*=YSl-HkpE{qn>_3Z|>jt6B%kM;!GUc^$(wWs%J>)5d3yJ)i>rQZ=KeHjx8-ispz z5?rGb9J1hQIU>QS0yG@sSt4ERX$W-%sP_$s)pJEdH5R(5t617x&b@F&Q~$VMOqZ?~ zEK6R)je19U(k|*ng&*A1m*>HS z;U6qSF2EHy04#(pIKI>AyJ3snj_g*XJ{v2?b~yCrbSec=*Bf&HYFW0xdlp%GXh-Ny zkJ!_xEO|Po{dHwCmfX-!&Mn49lnx_+0%^mp{GQonquid+0-vOvIJ-xEt zxvu^9{90lCo_~_~;CcTp9A)@s%Gs2YDD3Hm7g1QY!~USWL)lJYFE*@!GMh4t!g$4u zSImZ8%y`9DQ+VAeK7oS1(I_5D=>aMFhVm+}3q|+RyMb~d1%1C!)E`p#GvzhP1C;A1 z>^2LhQh1RoWTpzfqU@wFZ3Qiq<0-gU7zMo``Cn3=r);I1PN{_W^C!}aiSqj;6DIQ8sNEQ#HCKO}tvRLw2xMryL&fZVq^FBwpg3?N1@pxH+a=}jz zlc1)eSxw(!N-kv}?+e`@!RvmO!W^q9!+02ub1>Y66ht&Kf28cCJW9EV!t|=~YE$Sd zpzy@Dxh}Eo7{tR8Uyv8O&jZY#`~hob(SVPg9tVbk=q{i$A@MvYfJzGMU06 zOvinOAy<@?FLBep0H)FY(wLjH2PsUYTq{$4gOtW9PdkQ^Nr|HUrTz|Y>IamkD0fm= zV`>T*mUrqL%4kX|g_e@?8|5>~>y+J;dnn9o%0>!}Rn16NN?$%Di9#buiGf)2!k3Fj zUFN@rE*D8=Bea(_EoHe_(?1F8F#pXm%-9cZ+U$<^ivEZ--x;=C)S3^x(t?Ry>aVR5 z!v-B?o?o?#<2dvB=((7G+&N;ou$&|6#GhToqnJpfPRtLB)C0j)T+iiJ<@h@BUAS3w zOr6*odG;T(x?y!i)(u?5eyiWHbzHnruMMiUJI}Okm{ccriwL{JSDSsuSe3`siHh#l ztF!CG{w~?}wa*SM(-(G|>pISRh!$#J{9SKY_s)|CwO8m=(VVAPO$+KUQ+$NIKB&Ff zZ{Ay8C-(NZ!#>&x)(sVPqD%Kh^15Pg^x-=52K=l9ICqx5EGqWR0n>i%a9=r?wxxN+ zZ4*yje}rBC!Q#+$;$_jEUsp$2L8%v3eqDojqrd2B^<3W|=DEz3n;XP@>jCJ@eO=dJ zO19NIBaRn~ds!>aZxsD|S+1)Z#gmSndPZi4>$(Ap#lSdD96{$ubU`_}G6|eu9k{+x zm;wt4uD`KSbQ5u$w?hUcyYO#-oX11*5HknID(cDEK;(u(bK6agVs(#EHYb^`n;Q{9 z&#df7>)6xseuCdo;6jWemG8(8;oK0;3E}Jz&J5vH#d^_MM^Pu9Z8$ugHee}3o7qXg zInrIwBOf+6+#x(vvAnb+rX$Ooft+UbbL80p*huHdXm3OSWTmhgZ*CG-o*)MA?Aa`0 z!d?2~ZWVLdtPq}UC51PO%e);?C+~^0KFw_wNz=r+sP<-&+|~aK9{2nt@8IwY8r@S; zqXzWdL;lc!E5VSnHM}4n>V(Pj@jEWp{W|0&z;PP(;2Fw34KD*)HNf6643!5!Hy8IR zZi~uv&}+EGd0hkgulT@DXcY1NbqKoq!S`uEa~M2R!&|sT+p7T$W6(kkZ{u9AX?O>K zl=3nM-@9niZ|k7<02?%X4w$Op3qXQ~FVW_{(C`&thlZ~KXKMHcFh|3;fMg8^@YB8a zTTS1A9?)U7&0=G3 zzmVBB^6;;(@)~vc-$$HqcMc5?oQ zbNtgORIMK+%l~Tz{^{VVbpBeLIJQnU&;NI26MSepKKspeUDR>qu1BUbus;k}H5IlB zae4&pbh6sVsKNh;BRf@xbMggq=JsaMKY<5FcP|~|^=@vdX)IHQu&ma+=ld!wcg;vV zI9+I@x(0Q2X>Hwt&fcox8_V&(e={aSsKb~s9=xWW>)ut{yK#@MLA$jC?dFsp&6gEc zG_B|qKRhUYlO3Pw>a>)$dC@1eqW?UmJ=`w6Q{nIT*AMaSl<=-7Ea=Fphf_I@4BAW1 z#$mpHe=oaau$y$UWLC+~{_PoLf=WjI^hUJ*Z`&MNlC)VIn9WqDo+s&nKf1qKVuhV7 z?jh{_au2?R|359R^Or%m=S4ZkG?@{9hk88@mkPesJ=zaxNh? zTo0SvJ~`Wx9K9!3)07ieV3j?5I#9)V9XPnpp7>-O;?HHh{qRS}L-#x~5D+we zHutQpRx3AhW{*x2Gx@U+hezuZbhJn?9go&u&bm{klG|LfloK;y^Q`H7(xq4aQxhYx z;bN8^6$eu}*i&W5xluTOi(ZpZPc+4Cewi`3zfMW-pp^7sGT@k!e!(eGb1Y%&%)d>D zit=C5;jgH+XDtfMd8O0QD<~cO{ZGeq^y1_yWk-r)%9PrA%<1HbsOss70!1Mw z#9F4s+EF2-4vS$3ZAE4vx>j2TJl>}gW$g3t+%F>lA-nMK$;(?_HJ=e<=j z_0G}eKI|+`bMOiSCfqYzH{djW4<-% zyinbi*vUkw2fAD(SI#$sP4mWM-Whx0OK#`Vv9NVaj@GcE66J!0Pvx8|GO1p<0;a33 zrhaM5GR*AMDwkM3>Lg<<1NL6p?3k3E_@iS|`l_VJ!P eO57dlU)xV1{CEiO4&g^a_#tb?pDkir$^QWbNnFtY delta 53190 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

aQenr_> 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 G;Qs?I^x_f# diff --git a/Crusader.rep/idata/01/~00000015.db/db.67.gbf b/Crusader.rep/idata/01/~00000015.db/db.78.gbf similarity index 98% rename from Crusader.rep/idata/01/~00000015.db/db.67.gbf rename to Crusader.rep/idata/01/~00000015.db/db.78.gbf index 2670c9815ae66cc73769f0d098787ca1843b8986..e072259e0a2737ab9a4906e190c82f259d1edad1 100644 GIT binary patch delta 90853 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 H>?->|=QUU> delta 56554 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�KJhf@3K41>!M+*SFM4q95Q)Wvj?f{iSk) z#?OCTLMi1aQBSasQ&tnN5YG^kh+zcJ&iEE)_K^ILU-BO)B{vW|30@(Sd2-2fiARZ% zME43Zn}{U>futJ*hgcGam;v^S@deBeuvAWD5E1Sr&xDC*WA#tmBXIqRe6s0hKcuE*(@W+4WBY)EJF^dTUYm_&%#7qFDIYj z6f>xcpCqaYhJx|6jcm5!R3e=40*ZLdMeKqi#**)e49>AjMEO9SIZ1^G$jiMBy z9l^PwkQ-3bm=i??h4I3a?N&IGU|1D0tP0uig~bF%n7or&=#5fA6T#Lm*h{#H)r3yW zBd~Q-K{ZiH^da&IhE_paz?si^CjS`0dB&LKIKuK55l;||g8V9?FVThIz{=+!F@DeJ zDoS~$2#zxOS#0^XI^z}aOI*hG&ErrrzHXPzlUK$g%*!I834b8>AL2A|h}cfJhBJ6~XKsKc}6GwY?2qKV8$>#}%U-H)kx01|Y zN`9JP5GOl_lYNQK1Y0SYeV5D&ND^oMq@x7;F3C-NNbq2c`Eoi-qY1`j5+_{wS?44U zt0XI6{7};GEFB;=6RU{DjK9PuSe!r%C2l1$h)^Jb0iAG`;F%A$GyjGB-k5quepc%hKuJTG~1(lE$-tE zltbNO^*!PRU$<9zxUkfS8+b($E?T}tu(#W`Gh776xDTET7qfij%SvpAsT-%KmelPf zch3vq;!WZCwmwvJV-vTeZsS!6%g6nv{E&X+Vz`(d!LVpa-Ktax>sIQwUkVok<@b%M z`fmTJFZ)lqllvvCJI!BA{?ac(yz1lhZmI0k3lX|oNpA>Slc~?`kSbY(pgmKv@H)Q` z3yj~w_zR`>`w4b}@xp!*OYFP$B}5ny*+gt2UMHpzL*0q3BE-LT_x&*ggtx$kP3gxg9p=ecXlfgs=u8QAs!H3dYjY;QJiDOS~q`zf0gmb zp#N^*V9SRC-6eR~v*n_;LA1~n|@M?6d9I$wZ6{AwVFAb+Z=!PPOpTnT2G{Ps$4{mMI{q!v5M<0xRV&Vi@^ zus#c6k1|;?Ha+k1wBnHSl8p+J^#ur9pUL_nF1o)OWs`L-uE=c}15T55 z0Ss{{6W@oOo2mp4$Zf3zPax!P6VmxU>>RYOOjaDSd=*2pVp`3~HY!ZkB@jO)XsGPt zN?w7;X@?7}%U~@BWU{^rVE{8(UxNf2Ws?;l?D=jKs}9+rYL-LZQnCVqc9qGBon&!L zFj+BEW)&J`lNIY{**xpLjjQw-RrL&-M#*YS`x#lv)}YGM+-q^6KBj6uhPaiigP{3jvVH=Yrer;&pOQ}@=}KIX zR!R^QX^k!7v~GZLHfOSa4nYsfWJO%2aaLfmVwE&ZLo!*vgm?z(R}e2%gQHCK4Ae~! zY+feoW(Z=)kS&mE7TNyRuVKAq1>_q@qLQtUma@r;9+AHT#Rb-F5S*e+*6k34u^|YG zlyORSLV76K1&LF#8<+NL>Ydou?_fWwsyz_2vP{;!khw}Q@{`B9hi{7z55{WF=zlGG zVA(BVxi;pD#y0wm6A>atoA3JBqEA1~qJ8`Ui~Ft`iyqu>d7em zmx-CePharJ-J*T#C{?BJn|3$8%52uJ5%?;zXY`8t09dC9nKW(lFL+p&Mp>mDS=Jb( z%{^adF=K=H60msw{Vqm`uEw{4anaBNA$q^bnIgf0|KZIEdG&L>2`g;X6J}-# zn{aBgFV*3(PGd#+pGLh2{{sw)x8tBfe2pU``9HvgC+~TS%Uap7AjDVO_fuVn**Z%X zukk>uF7eoA@mtzLM27al-#0SDjr7g*n$0#jzMQlqez?j>d0p9cPK_8UICil%ux&-SiA15QscTVA3*0E+QTz z*dvjggCYeG(LiuMh~N|x!E0s20D{+x2+n-rM+wX>rf{wjK8fJ)4UZ?hfUwhmGmJeO z#_=EaJi*plPVTED_F@0E8>3KPq&MOKF)L4eEQD>CPh=ai{MGhuhUAeVQD=gN)le9A|{X$@>G~NWC#>PlnPH^%y{!h&9r41(JR|Tczu^3DE1F4MJR9@Fo_Yn*# z`Gr}jjG9!$Rm**|ds(`T=s@s@+lVJQLvdI!11P>wM8o{Vf;29_2`e;g6 zLo6qrCmtpkO9{6Vr9>JL0c0D$QFs}}_!9)5n8b54@#}~;2zFq64e^kEwufDGk7Y;3 zF|y-&5yeCz5ezu4=?sVrEpV%27_Fcs1#Jj{|;%S0UChQ}Lfdo(1 zo=@;(jh`a?6Q$TA#CHU@61$RMuf@(L*uAkluvpGyv4w14(NwB?*-!@#WFPIZ1!kzuNq%*-@iV7pVf%X@O!vq_#J%hIW+r&bG(cOL=F@(64 zVEeVV69GWvRpNKz0I`|)h*(B&Zo_LpY$uWd8`+oWM5Ge!2roc>>2bubEbSy*#9IX8 zB;s-6A!0DmonX*L#1b6m^2?LL8ExV9#3tfH;uXLdUd!TCVmQ%)Iwqv`c#52ScqKdeK=tv|JA%p-z z&l5ir+lloA$4%%wViqx$xQk$1hvpE`gdY%cnK(x5!@G@5Az!h$idaHCO>lUGR1zG; zAsvV$BA7rn1)p=Yl^Qxvfgu7~5oOo}ixQyy} z;R)j zn)5yEmZ<`*WPi#in<~(CcAh>{1>(uho?xm#kjvjDky3!+-Yl1+rVzrR!ce}WQ$-iZ<4VdP9FI&D z6`gQ)_68@*f>A>APyTTB(VLXc&uz$_!@7g|M6 zOd0``b~nJO>><-15|6_^&; z75yOJDCrN;l?;G9s$?K!kdiwg8~{ueSlZrdluZ?PV}>~)_s=REjGUt6dT7JQxoW+k zkg-aJLC{7rRiGOqW0l;C`R0t0`yk&csf4UhQU#f%WH`=00>>d!#Rya()(ja52~aW$ zlh8>eqaj}_xgYYnk};4ON*;jV zluUp;tfU%(_Lr$*A|yk}B#1@HWXxH=DVYNKNXb;lvlO;}#WWaBQKky)q+OYk>5xbz zk6;G-Qwh#9v`z`SK6I6mnUH6cps7QLD?#*yc2a^t6B=g39}WS>5pq#g)j+;ef`J?I zu97DpPbxtn1RX6?#Z!{K!b=kNJ`xeByLaE+>Z4uV*dsp5GE zCKN+nfV5TeBEH@1tP(_Q&^9IWAfBN&AL1E$3m_bNOcl)hS7Dn~)oTzekf{QZZ9@nc zf<4*>C|M54)Yk8PDM`HL_L?6rI@`3IyDkI@i(YXuKt$`!@5JM!f0jP!(Yr+geiUNT zq-*i>jxUcFK!R`|U!EuubWw>*N0R7hZS(pKY}ssVy;~1=*V~fBidNZpgE3ys|7t7_ zY`C}~p|fpM_$I^62QPjz$`Ul+9B+7=yWBwol0==i`{G?mqQVl`@}B0FM;~$L-IpY` zSoDw{5hB-Jcz=@kK0=(u%jhI=M{8|x>++%QkMW~KgJRtc?D01D)E-9O1u}V5^vI~WPMJrTbCr_MXk0XY*euR55y*BYn#J|$e^-fRH$Tq?DbEQ zM2|!@>amynKCRUc(1@cxToJ z`Jy(|xwYkR_!CW@?|tA;G!-JtWXZ-=WqkfdnbRB$0PfNk_hshV4Hmt2LEr3a+Hs_6*{R?-VE z<+dp44dG1VYT^xj<%Pw$}(JzpbW!5o-zOg&}@M9puOp^&#!4QAmC754mzrVLNp zV;>n|YBl~u)Bk=2sS-CVFv-bese&NOWWmKKJzB{Kykh%B$w-JA=9W)CDaff1)WBT_claEbcs9Rq`O@V|-x3TmmE^DPK^|t$`igvvWjZxhx*Zs|MeL{#mCah&$qtaM0da`Gu z__ovkEKAW>Vc#j?#v=7OedAvBWPEntJ-9hv{NRl{%xO{6L`JGy&+~C~W4+LR#z)kR z?HZL1t@iSbICj+k-VJK3y`|+HqH66ttdqb`+awxmtM{-QYj10LUwg~ivNBft^~w!9 zPyK&)qm8xGaEE%H&tLfhC?E=nBBGcmAvy@R&tDyd_fC@9G?0=PtSy>{m;7%fn)kp{|WB4 zNd1NimLb^>s4tW?+=2BE^6_mA?{i11m)ytBmWsVT`lXAdqF*3B(G=qOloLL7(DhQO zXbfzL5#vFY+x56VO2u!kf_s~D{pL-(Rw~@nu5}VGbrlhK_Rv|}o#dW$e`nFh>h*J# z$^G#pESW2AeCgLk{6CGIcYIXE`o_>`lNC!cB7o>$65H2bU zDyUeZ*RnP^^dcajR}mu~?4n5VS`Z6ZQL!Pm%N6)N&zyaax%~B;&nHjjduDddly}~< zdv?xjLMwCpzsmR+zSizqi?ya}jnaDQDdzb}>q)Igv|3tsX&v}s6!LV}ny&TIaUQX> z?$T;%ZP4n|I!Ehxtpl}o*IKMKUF)tVd3IB4gI1r`Ial>6E6gt(ea6f_H!=}M? zZ4T-3Bjp3i0m?SYKPm8?Mwj-KWJu?;6z1#9OIGJq6nICYGpa71rDJl}}@=mcsriJ4a#L^|pFtOBowa!J8eU z%tcWU{g`qo-;?M!Go|>rVU*rL!Nt=kWk;2kQW7C0mniJjlEai;d?TWFaMG&+O8AnJ zYDx~oUdjAEV{CEp{S`r;0k&6!U2>DN<75F0rZ@pJWrt+c-Uq3n&a+_MMB))QD|`bD|Yuh#_;~ka2HYV zXB50c;gA$uN4c6(PW#gfh3$24M{qD7y<+6wL8+(oqOcg}8M(mHUv_$)Nm)t3`vD_w zD1|M~qy5>>$$iQg4JUU!g@cnjj6#F(*}tp&1EM)cDeS_W2Pyxcu)v%GN(3aE#+Cgx zh31~kEZM^;m%`D=8cXp~c-hF}%*p&WJ2P`DWiEwNCo>w-j#kp{ zUlevly9ty|6b^aDdCDouYZSVU40?l%#guUrnsNqfp26~5zf=CAuw>T(%0svTVz~Gc z*C9MOXRQog6K*#(Cw7G*buZ7=vIgy+7brhcPEw9iSk=5I zD61)VQWjIDQASW$ULMQi$)U~Xd`WqSa)3g^&v}G$D}_brt&Z$RnXCOVE04DGy^N#TPf}R-?As~xDXeyO z4@xnGmXzHJlEr@28y;mp!`KE&1Lbr}~d1z8Pmm?eC>doAp^+^^89B9#6BlpaXKI-Fy>>r{rh5NT8Y-s+$=M;C!x3mNvux8fFv z@}82RKb~bs2}S0Icx{ogH$5=drY=-1nKkMZM1MBZ--83f3e8nGS-U{l&0fc`Ci@=5 zbN)r0G}oaGZS*RrHarsHGv_`rPQ_WXp?KDCRhd`a7OX~^^;KTgt#!so!0 zT=k1EORG_Ia)KQZe(Eyb^uL@`cNzD*jN|{86EFHMJMi&k{QPD7%w=5vzZ^6FGM;`J zkGYIvdtY{-8qZ4pGPkFKTtVqYxsuYG(udL)tFGquQ*EPyLc3Uz%lfJR`c$XY;r&%F zUwgx4@Y6LHhIRf~;0T~F03mbm0b!yI(a39beAWioKOiaq3I@{QqX2|Fjf--N0K{RQ zR(%qHHah_u1CTJp2U;AU&hr3Q+2CT?mD>W)@+7WvBLu#}#pZ~>&$xu#6@XT~aoMI_{P6gmJFO@8R5+2Ot_gqTqLd69B0~%p}Z(V}X>I z20;G+v|bDkc`yKR55vX17JxQYfH49K;mD_7N^*4;9aG5M+4xh22|UShbF=u*ib@nz2g%?*|^W*!oj!> zOu_;Z8x*b|X(a;x04RZH@l|V}c^!f<`%@nXqy*vWlR7y7!SR4h8>(4!%8~#C-G%n2 z3<*%^3?z91&#5XmpwDT7+Xq1t!bFoI>Le`Ui24P9)QMo7Ca z0O>)1KsQdC$XzDVMhB8IQAS!10gP)}dH}N80s{4!mem(6IwL7~r;r-3g=tw20QLq_ z@?C7WwIjH{`YXz6F}JO9HTS=mgOzVz$M#pxg_*Nb`m0XXN2&eQ(_!X>85!ypb7^*e z6>DwD>90nGoA00OgGb@#mG@Vzg3OeU`{1@iLREj&Jjhzzxxb1D@(&tQ+{*lIUWPj1 zA97=HY(v*>{S{<@vbuH~sNPcHDiZD%{yntRYF#~0EeZ9NvM&%kxK6w6DBvL&_t5}( zV6_En1s(y+6?ha-E$|p1S^yW|{8IvJ0Q&??0N(&Hc-F$+^Jm&%c-8?r3#pS5d^-SoUf_TSeoC)v zW1+|zOFTi7bD8*R-)LjS9 z0l+dlZg^e<3>0_?!1q!N9@M|x4|ZICKcOFX(kXDS`ctfi*8rm=+v{k5yL_QH;Enzi zI0(QMLd@#5~VgahsrhyY9u_#dON zC8%7IS^-ce9k@dl7vI?s1#s$vVT^O?69aJS(;DE^Cl-r9j!OY?fGq-T0KP>+ctNaF zLIMDPIuxRNTb~xduS!ZbK#Z;sHWq!2q~rp+3FH9+mGc>e`S{!f)760w zKSzlH6}kbBONs}uLICCzH9?>V&{?1u;PicoVMJo`=uilYjXWe!2G{}^%malO`pBCk zsRA%opc2qQpbCKD)S<9F_LIg0wgFjM9b_~v!aSB(rD9(gFi@RI^uK=Td>8A-YX_>b zkT}D|^yX?4^D*Gp7p?v?2dY-lX#sN!tF@n(ya(N71ckx;85a4E&b|=m52r+RgPXfdXID$GKM!r$T5{t!SU<(R;q zfb9Y>p%iBXU;ruOB?aCgrK146LyFx?{tYXK#`#{CgiG*!>jHo=Gm@_tfIo1S0T!Vz zk}--pa5K;376RyxwqFQ@0`?2Q9klfegaPIWG>3(^?JI<$+GY#DA0(kpIuyYlBz-6V zcaZdy0NjD|OR(JFBx$Ooz#Sy@5P&;K!W7h@CW#QpS=; z2TpVVC2C~RscT9*kw%%zhv)M|qfGn!cHKwWbq~~Ll&N209+;;~7-cup3>1jG!%%Z` zcfkzR+>E(lC{C^8YJ^?+AVhRm=yRfad&cx;9ya`M;;Yr5|E=plyXZhQ2I^|0nCI8^ zQxWDDV|%OQaAS0!3kI6^4$fC8;VN(;QUlGi>u0fLc8b%{IIcUI4UJF(VO=^*^$%<~ zYg&in2dU^F>tZ6df5#1(?st!L&(NVGO{?8vN$`lIODS(qAZ{cr@;9dS?Q2W7M00!+B+27!x&qbIgHW~6W++g zmCS!)%cRS z)eJu{R891qKn)n&+4$AVr|r1jWwaZIlz~s1bz}1x_#H$y_9KHkACO_k4L4fe^)CT8 z-~$1?pXmCe0Iad=G66mY-t|f$jCEH|N(Ofc)*1X{$MrnyLd$1xmmy`99XH(N0Gb?w z8%EHjogFvamAH{8?;6}y08S1DZa~%f7CVmRX|US@So9RSbWpH-~&_Wp8)woIdg^mJx0BDg6?iv8vZht6pdqD$q0+MP0Z3TMb z&ZbiZ^m_-|90Q*Q?6BI78}8nKu>ySnsRDg*H}sT1Kfp$T{($MM1A`liBN~g0hR+Z>i9o)mw{>lg;IEM;O z2nKE!R^gN|3UIxhV7NyE&?+6=V*tql=mEVK9oan&utQSD18xwQ06)^f?;1QUL8^nJJOar_pFdeW-U04pVBE&%P&flDGvx(Lh%BnZ^ww)8IoaO%Zx+xCYlECg+p zr0W1T3oHVlFLiJ)24ITV@DD(oz!Kc$cBU)Fs_0EgxdGrb{6@esNkI?ziZGly@Tyyc zX==kVK#ah0yo+{5ewDn;?mZUs_HyTds9tCWZ6r4UE#!CCcNmhfL z*}n#FIKGj5=z+W~0&4-&1l9qZ%3%<49n;zX*elsK0-Tu1ipw>P&x=x4_K*&a^|gqgULj!GTC?g-719P?*2J`lJr4#mC>eCi@H ztZ$RLh50jnJ(>^{*4NC~JzmB6fBy7%R8Uy&Ce#prX{dr+O&LZRP8mTNsm%CGqtrPS zvp;;adcURhbIZ|cLUU_Eo6+jwP^(4qXl%gTqNm-+8f=N&V~)y=Y#3QQT3I8D$EZC= z)$08bV^!Pc&0WY8lc+{nh0VvRFH-%p+r+!9=$x^tTT;X9?qd~X9A!LZg0g0JpQs+I z#;Tc!WfN6;xWBm1`S86J;nhzjSe?!%s)M2Ct}PSswd-lYBoFx_SQ|L7+p7j5I(@~F)>E#zGJVW^j${UntD3}j{ zb$~oq`E&}MNqIGeRV;5y34@gVK{-wNfbu$pGs|9O<7ezv$|A~CN`Fr2GWzJUY)UMo z^aABO3X3UaWlNu+u)j)gqs*raqp+(=+1yfgb15fI3H?q98)EO6`Xpm~ZOJOib(Bez zJ`i6CU2RD^h5cOoGlc_H{3_*X3LJq^`~YPIWg%q>=e&I3XRCKg)Y_eAfs%U2Ul6ca}9-Md3sP-mVQLS zeF-r)`_}yl1Seb%ENd862ca--i2Pj)9_fT%6%%BXXunPI5lr#!QDDO|oSG2#p zcNn!OJ1A_4y>_ymu?dv^lundfN-QLoRmeR}IY#02(%ur{KE_b6k;^LN4yAZ0oG-a9 ziY}lTVjXk-1I^*w%Q;D5kLsT>%b}Us3oWY{!(=vcXy4giQQp{lcJfUfn(bZHZ(5He zvv~6)EQLvf##>Q$C8+PBmHpcySuK$nA?h5&ezL*c0@1ME5c?^M(ok|gb6NT<71ywO z?<57ehBBElg>tR3R_~pvX5FT?V>gg#YJBrHF(v7j@WK^_3mQ}6QUrh8u_gT@onw5% zwZcCK$J7cdulF?dMv{NUnI+L`r~kn-OO~qz{>1N>j7nY;C_{hlcOU^T{>FdT_e;u= z{YB)9G$kK9y;T!P^!7alIYKPYeKN z3v>r?92mUS06Jp^Zx1*_8a9I$Eh}5AqW~CE87~(MUbM{l-6$_wR+eEW7~Y|Y(~cY7QE-oE1jYhh6Bq|rCompxgAG2zI|0N# zWAIJ{l-Y5^I|)G7#NdU)(Hm21m<)JXA4kd*z#0K~l;VE~Oa*XWF?go|=ouKi(*fa# zp#30BK>f2Q8fF3xFowY08H*mZ;|8~FEV@nr!&5X^U=F||Fc%OeFb_WVbAkDQ{Q~uX zhxGYz#0#fdh$A|97Xk(d;Eeo|!%kTQ2oYEepLrOHxH>gcpn5f)71~?Eh!HHvIHK6OTQ%W z2;hWme`p?TOz)%3RPSQ|)KCZSY5+{q2AFy76#^z8Ltri3{$B#?03Qgf2Rtc&?#MwO z>EPXn_UB9!+Jq#pz-B<2z!qFc{v)sz@UFl%z%GHu0T?D7yxReom^SPHR153`#0dDF zz|Gh1g;12fMN9VX2B78wg)+kh@alG72|N!tAb?ri4r8W+_XU9C7c4+`NjU&$EAS%j`u-;H65wdS z{}|qvLFh3ZysrQ-M{IZ%aFxJofI@-S0nG*8z@6eR1P%gFCmpC_{*xqM1eQ0H`~Q;z{)`#N~v0n8KlH=v&Y%q2Zn;5|T)!29Tbeb1T) zyfE1`42=%n;{a!ZeF$(S*a?6$!9D_H$!k8wt?Wwzn766Os)P4a0PMkrlK^-z23&tm zfyQV+6+Qzvru8|XwWOeTQ+^Qm5`Zq(!3zsX*&y&WV5z`qz(|2_0NDcH0vJd8an|nw zx7IbB0X)MPf){5@4pi0f{tR$xdKTc+^cR3r({s29@6_~HfKyXgUt6c9e*l*1^YcXK zK~7cw3#iZ)K+0c$D1i&Ow|`3DBEYc)^n22sfs|VOcfRMWRe%nXf}u}}6$nQA6Mqy6 z!MC0;T^(v+eTf?d!T?g!TFlPGQIZk?s1#@kh!$vtWg!?%9cm*1PYR$h2{#A8SBn=U zjFY4oKt};|XhNI->Ky-*0FK6EUg=QV2CzvW0kBMJ&YI|S-%6Wkrz5hFMLuJhmX>Cpu9nY`)vS;w`< zzlpUN{Wip0_Fa4I57Nhs={{X0o7G!`ur4;NkAJZFQA%Kkl|J@yzX0;bArGvY$7vx#VBA@RK%XWMrCR7TqavF8L{u3A#94 z2^Ongl#hL1{nf{gw`tL8Vf~bA=1iV4-oNg`@z`8DIWzE6^I@z#LNoqvhQEk~*UX(b zf8s22`KD$nwxuqs;<<#Ekz}_0wY|#V>F0eCsqz!-lHRV$_!NmwODCH@jB2LRU0$*XYr*E$MjiTT9R_$Yy zXV*XbTvf?fTS(<4%14v~luZ=Qpvs|?a!Mqm;vD4_3hSo7PSd-&RM2E9Vj<3a7YFZ`x8$f1!WAsq8<9m7SpMrQA!IO2M2q%HX{NKTz@kV;d=pDPt+{0fA?f zFCtd*IfX-L-%nh|*a!-ojZwm)ivRFYSugz+RWW;^nAi5=ath~g5sN8eF-2^h{^VKI zhp{wB;g6JeC?@3w3cH7^`V7xE6uy?L!VS+-$`ncmN-8fr?lXwGk5SlSH;2v5V)zB5 z;qF2~Mx)>YaXSgjww{3?2jpLP@bi*vDvp% zXsp>2D19lNDEX8`5o>tJO!bn>Jd!mNU%JeNW?S8|XR76~=9e92DxaC!wFMFiuxCl7 zxer=s-qUfW>R@h6xLRd5M0A>|tcXr^>SCyR7LRt-sS!ce{F*v-HY5rgZDV_n-*6^` zh?VA~UUgV1(eBDRZWsMh?>hCOvhM3!hp$?q0()u)vihuFhSaIgg00{Yb?N~2oE}-H zs>968iFNAzB(@sCJ8IpMe|~{TfL&i%L6hoKaP!pn|32t0qMQAjy+f@@GwReaJdJiV zPL-KGp@Uleg$!BvLug%NzJz1#8fxq66l4}pc4Qq|^FYv-t` z)s4kbH@(Vi9q9J9ui=B`34zxE8wFsF?Pdua z1SAW*i4TnT3%mu$5IBU7dpiW)26zQ9F|h84W8DrP1CD^^NYYV2umIdh+Qa@%_czuD zsSEyH_cta6sf+%D_cvCnHU97i8gp=Sw)6qePXGJ|8q2H>x7MqhTKRJ}G$u!G)Eyq3 z7c>l~S>YeJp)n`YzP*cM*WlP1|IIj-Y(H>@V{>urF?mrvV5|QXWKfU!&uqXc9`$=R zq6m8&k^Ld$xa6O(v9W)^5^!u6j$M!JRJvKKcDX)Jt{^7?B zT_^`H%0@o>fiFM-zL;t&4vj9%D=CNA-ijjVo|G<{UW=^jMFJ&X; z)VCm4OM#Uv%>Uz-#+)(E2_9_PrzUcLDY-WwthSn3eM4^=WlLRtgl3?aON zIpy$bblxBRcw^ES``$~{u`HJ3x%Sf{>Tpwbeo LaTgb;^*#O%9%bc3 diff --git a/Crusader.rep/idata/01/~0000001b.db/db.7.gbf b/Crusader.rep/idata/01/~0000001b.db/db.11.gbf similarity index 98% rename from Crusader.rep/idata/01/~0000001b.db/db.7.gbf rename to Crusader.rep/idata/01/~0000001b.db/db.11.gbf index 6d71f1b92bfa07674a9f7ad1a7c61ef3785c090a..c5c3cc6129b8434a56f44fc77f4a9046ff935ad9 100644 GIT binary patch delta 24967 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

dbyN7HVOBV{ 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 diff --git a/Crusader.rep/idata/01/~0000001b.db/db.8.gbf b/Crusader.rep/idata/01/~0000001b.db/db.12.gbf similarity index 97% rename from Crusader.rep/idata/01/~0000001b.db/db.8.gbf rename to Crusader.rep/idata/01/~0000001b.db/db.12.gbf index e77dba1a5013eeb4ce8327dc3fb0ec0f289fbf4d..313934ce42fe2d9bb7bd70bd3f03ec7745ea997c 100644 GIT binary patch delta 23162 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 delta 10279 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 + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Crusader.rep/user/00/~00000008.db/db.44.gbf b/Crusader.rep/user/00/~00000008.db/db.48.gbf similarity index 99% rename from Crusader.rep/user/00/~00000008.db/db.44.gbf rename to Crusader.rep/user/00/~00000008.db/db.48.gbf index 777b8c2364b97eea7ec1fe813368162442c71b16..a1bfedb16a407d8e8c1141306a6deaecbf04967b 100644 GIT binary patch delta 81 zcmZo@U~On%70@>@)G^W2(W>8=KS{Um$<;=IsRE3=j5Qk-eQl;^+c1hvue4#5VNBXS l&4#guNz65`B(tP4EY~?duOzjiL_tfTBqOtUx`RFA0RSiY8n^%e delta 70 zcmV-M0J;BwfCYen1rRSVF)TDGEGoUo+U6`HJnMlFl@I|90d%n;O+1%tJOLJ$i97)m c0cy9NJOPXXm;OEh7zS`(aB^>Bmq0%Oz#^6w_5c6? diff --git a/Crusader.rep/user/00/~00000008.db/db.45.gbf b/Crusader.rep/user/00/~00000008.db/db.49.gbf similarity index 98% rename from Crusader.rep/user/00/~00000008.db/db.45.gbf rename to Crusader.rep/user/00/~00000008.db/db.49.gbf index ee47c03ae62affa9cd68fdd8c6138d35b7ee206c..b1ad5b8b493cf6efab21cdb7907ed830a7c3def0 100644 GIT binary patch delta 264 zcmZo@U~On%70@>@)G^W2(fWQXEA;L{m1&IvQw11#m~8nsD*D-#B?6Sab`ELurdIG>58_D64QU!Fe<@BwgW|k8DTt#e6Ss(l5A*kYI1%` zs!M88X?l8UkyBA>Vs>yzVo9okma!2;-7-7IfNVVK6r6#YN>VlODJjXwEY?v-DbFuT mQOL~8EQv2JNKMvMQqZ^K<+9QDNh~gj_sLIAEXmBzvjYIi?NWgN delta 388 zcmZo@U~On%70@>@)G^W2(ceHJ~Uz zIkmW0LCe@^db}N@#B^7C#(;VpY80G{QWHy3-HP&agLCprJo8dgD>QJaEr>4wnysTy z3RIPrnU|Vkq)?Dpl$aZDghk94D3+O5qM)TvkXe#ipPQGjqX6PVbeKXo$vKI|#hEEO z3aNRexiAm7mZbvKl|pu=Dl7hY+FPDwJ TPa;ooNxV;fa$-qlex4lw1c!z= diff --git a/Crusader.rep/user/00/~0000000d.db/db.3.gbf b/Crusader.rep/user/00/~0000000d.db/db.4.gbf similarity index 99% rename from Crusader.rep/user/00/~0000000d.db/db.3.gbf rename to Crusader.rep/user/00/~0000000d.db/db.4.gbf index 1a381bfab87d084e74b02db6b99e84ffc05539c4..e0aa63150cd288ef6a728f838336ea8c2a4d629b 100644 GIT binary patch delta 141 zcmZo@;A&{#63{m=)G^W2(K>$eh?K$o19uw*S_K%l3NZ0HFa~W@w6UEYSIZ>H*fYJN zmdTaHG%YQ8`oUTz8!&4-V;z$yBV15ox=}sTT+@Q$iumNj|P* jHvn>yH`*S_K%l3NZ0HFotbZbg-QsRm&vF*fG7N zmdTaH#LUoS`hq$piRo8snN;9h$?0NsOn=~9iRpUvOmhu0a}(23f;d4`LUA;U(2pY@FFlerpVS=ksEY~OEG?6wt93Mk4i zNG&RP?w6U!h^>GXii4O?!cMo#( zjrZ|)b`0_K_XE1C$HphIxFp^uKRK}^Ge6IcmrDT%Y=T1^LtGX79DQAFmEs*;T!LJK zgOwCQA_IW@;*z4wymTdnFh`$IAlJgcz{E5yEm=uHAFFPs$Pm|huo+|;;OyZT6z}im z7VH`VGa)mt1a7|pE{mOkehvnCK}n$=tIN|fGE<5Y^$Lpe(~A;w^-4=JbM)Lw^O8XU z5tLd|T9g-3S&-_MnVORV3m=?z`TKWdLqdc6;z7QH*^L%E zrFki-X_@tTsVTUe9^@Ym(@Rva279{uIRdT4ZzIq`g@WRW_~gXojMRAT5@0~aCl-~& Xr=%7q7iAWdIH0 diff --git a/_tmp_inspect_psx_banks.js b/_tmp_inspect_psx_banks.js new file mode 100644 index 0000000..2314eef --- /dev/null +++ b/_tmp_inspect_psx_banks.js @@ -0,0 +1,197 @@ +const fs = require("fs"); + +function readU32LE(buffer, offset) { + return buffer.readUInt32LE(offset); +} + +function readU16LE(buffer, offset) { + return buffer.readUInt16LE(offset); +} + +function parseLsetWdl(data) { + const headerSize = readU32LE(data, 0); + if (headerSize !== 0x34 || headerSize > data.length) { + throw new Error(`unexpected header size ${headerSize}`); + } + + const headerWords = []; + for (let offset = 0; offset < headerSize; offset += 4) { + headerWords.push(readU32LE(data, offset)); + } + + const audioSize = headerWords[1]; + const sectionSizes = []; + for (let offset = 0x08; offset < 0x38; offset += 4) { + sectionSizes.push(readU32LE(data, offset)); + } + + const sections = []; + let cursor = headerSize + audioSize; + for (let index = 0; index < sectionSizes.length; index += 1) { + const size = sectionSizes[index]; + if (size <= 0 || cursor + size > data.length) { + break; + } + sections.push({ + index, + name: `post_audio_section_${String(index).padStart(2, "0")}`, + offset: cursor, + size + }); + cursor += size; + } + + return { headerWords, sections }; +} + +function readSectionBytes(data, section) { + return data.subarray(section.offset, section.offset + section.size); +} + +function parseTypedSection8(data, section) { + const bytes = readSectionBytes(data, section); + if (bytes.length < 8) { + return null; + } + const recordCount = readU32LE(bytes, 0); + const payloadBytes = readU32LE(bytes, 4); + const headerOffset = 8 + payloadBytes; + if (recordCount <= 0 || recordCount > 0x400) { + return null; + } + if (payloadBytes < 0 || headerOffset + recordCount * 8 > bytes.length) { + return null; + } + + let payloadCursor = 8; + const records = []; + for (let index = 0; index < recordCount; index += 1) { + const descriptorOffset = headerOffset + index * 8; + const blockSize = readU32LE(bytes, descriptorOffset); + const typeId = readU32LE(bytes, descriptorOffset + 4); + if (blockSize < 0 || payloadCursor + blockSize > headerOffset) { + return null; + } + const payload = bytes.subarray(payloadCursor, payloadCursor + blockSize); + const payloadDwords = []; + for (let offset = 0; offset + 4 <= payload.length; offset += 4) { + payloadDwords.push(readU32LE(payload, offset)); + } + records.push({ index, typeId, blockSize, payloadDwords }); + payloadCursor += blockSize; + } + + return { section, recordCount, payloadBytes, records }; +} + +function parseTypedSection16(data, section) { + const bytes = readSectionBytes(data, section); + if (bytes.length < 8) { + return null; + } + const recordCount = readU32LE(bytes, 0); + const payloadBytes = readU32LE(bytes, 4); + const headerOffset = 8 + payloadBytes; + if (recordCount <= 0 || recordCount > 0x400) { + return null; + } + if (payloadBytes < 0 || headerOffset + recordCount * 16 > bytes.length) { + return null; + } + + let payloadCursor = 8; + const records = []; + for (let index = 0; index < recordCount; index += 1) { + const descriptorOffset = headerOffset + index * 16; + const d4Size = readU32LE(bytes, descriptorOffset); + const ccSize = readU32LE(bytes, descriptorOffset + 4); + const d0Size = readU32LE(bytes, descriptorOffset + 8); + const typeId = readU16LE(bytes, descriptorOffset + 12); + const variantTypeId = readU16LE(bytes, descriptorOffset + 14); + const ccPayload = bytes.subarray(payloadCursor, payloadCursor + ccSize); + const d0Payload = bytes.subarray(payloadCursor + ccSize, payloadCursor + ccSize + d0Size); + const d4Payload = bytes.subarray(payloadCursor + ccSize + d0Size, payloadCursor + ccSize + d0Size + d4Size); + if (payloadCursor + ccSize + d0Size + d4Size > headerOffset) { + return null; + } + records.push({ + index, + typeId, + variantTypeId, + ccSize, + d0Size, + d4Size, + ccDwords: readDwords(ccPayload), + d0Dwords: readDwords(d0Payload), + d4Dwords: readDwords(d4Payload) + }); + payloadCursor += ccSize + d0Size + d4Size; + } + + return { section, recordCount, payloadBytes, records }; +} + +function readDwords(payload) { + const values = []; + for (let offset = 0; offset + 4 <= payload.length; offset += 4) { + values.push(readU32LE(payload, offset)); + } + return values; +} + +function main() { + const filePath = process.argv[2]; + const wantedTypes = new Set(process.argv.slice(3).map((value) => Number.parseInt(value, 16))); + const data = fs.readFileSync(filePath); + const parsed = parseLsetWdl(data); + + const section8 = parsed.sections + .map((section) => parseTypedSection8(data, section)) + .filter(Boolean) + .sort((left, right) => right.recordCount - left.recordCount || right.payloadBytes - left.payloadBytes)[0]; + const section16 = parsed.sections + .map((section) => parseTypedSection16(data, section)) + .filter(Boolean) + .sort((left, right) => right.recordCount - left.recordCount || right.payloadBytes - left.payloadBytes)[0]; + + const summary = { + filePath, + section8: section8 ? { + section: section8.section.name, + offset: `0x${section8.section.offset.toString(16)}`, + size: `0x${section8.section.size.toString(16)}`, + recordCount: section8.recordCount, + wanted: section8.records + .filter((record) => wantedTypes.has(record.typeId)) + .map((record) => ({ + index: record.index, + typeId: `0x${record.typeId.toString(16)}`, + blockSize: `0x${record.blockSize.toString(16)}`, + payloadDwords: record.payloadDwords.map((value) => `0x${value.toString(16)}`) + })) + } : null, + section16: section16 ? { + section: section16.section.name, + offset: `0x${section16.section.offset.toString(16)}`, + size: `0x${section16.section.size.toString(16)}`, + recordCount: section16.recordCount, + wanted: section16.records + .filter((record) => wantedTypes.has(record.typeId) || wantedTypes.has(record.variantTypeId)) + .map((record) => ({ + index: record.index, + typeId: `0x${record.typeId.toString(16)}`, + variantTypeId: `0x${record.variantTypeId.toString(16)}`, + ccSize: `0x${record.ccSize.toString(16)}`, + d0Size: `0x${record.d0Size.toString(16)}`, + d4Size: `0x${record.d4Size.toString(16)}`, + ccDwords: record.ccDwords.map((value) => `0x${value.toString(16)}`), + d0Dwords: record.d0Dwords.map((value) => `0x${value.toString(16)}`), + d4Dwords: record.d4Dwords.map((value) => `0x${value.toString(16)}`) + })) + } : null + }; + + console.log(JSON.stringify(summary, null, 2)); +} + +main(); \ No newline at end of file diff --git a/_tmp_probe_psx_section16.js b/_tmp_probe_psx_section16.js new file mode 100644 index 0000000..103494a --- /dev/null +++ b/_tmp_probe_psx_section16.js @@ -0,0 +1,132 @@ +const fs = require("fs"); + +function readU32LE(buffer, offset) { + return buffer.readUInt32LE(offset); +} + +function readU16LE(buffer, offset) { + return buffer.readUInt16LE(offset); +} + +function parseLsetWdl(data) { + const headerSize = readU32LE(data, 0); + if (headerSize !== 0x34 || headerSize > data.length) { + throw new Error(`unexpected header size ${headerSize}`); + } + + const headerWords = []; + for (let offset = 0; offset < headerSize; offset += 4) { + headerWords.push(readU32LE(data, offset)); + } + + const audioSize = headerWords[1]; + const sectionSizes = []; + for (let offset = 0x08; offset < 0x38; offset += 4) { + sectionSizes.push(readU32LE(data, offset)); + } + + const sections = []; + let cursor = headerSize + audioSize; + for (let index = 0; index < sectionSizes.length; index += 1) { + const size = sectionSizes[index]; + if (size <= 0 || cursor + size > data.length) { + break; + } + sections.push({ + index, + name: `post_audio_section_${String(index).padStart(2, "0")}`, + offset: cursor, + size + }); + cursor += size; + } + + return { sections }; +} + +function parseTypedSection16(data, section, startOffset) { + const bytes = data.subarray(section.offset + startOffset, section.offset + section.size); + if (bytes.length < 8) { + return null; + } + const recordCount = readU32LE(bytes, 0); + const payloadBytes = readU32LE(bytes, 4); + const headerOffset = 8 + payloadBytes; + if (recordCount <= 0 || recordCount > 0x400) { + return null; + } + if (payloadBytes < 0 || headerOffset + recordCount * 16 > bytes.length) { + return null; + } + + let payloadCursor = 8; + const records = []; + for (let index = 0; index < recordCount; index += 1) { + const descriptorOffset = headerOffset + index * 16; + const d4Size = readU32LE(bytes, descriptorOffset); + const ccSize = readU32LE(bytes, descriptorOffset + 4); + const d0Size = readU32LE(bytes, descriptorOffset + 8); + const typeId = readU16LE(bytes, descriptorOffset + 12); + const variantTypeId = readU16LE(bytes, descriptorOffset + 14); + const endOffset = payloadCursor + ccSize + d0Size + d4Size; + if (endOffset > headerOffset) { + return null; + } + records.push({ index, typeId, variantTypeId, ccSize, d0Size, d4Size, payloadCursor }); + payloadCursor = endOffset; + } + + return { + sectionName: section.name, + startOffset, + recordCount, + payloadBytes, + headerOffset, + records + }; +} + +function summarizeCandidate(candidate, wantedTypes) { + return { + sectionName: candidate.sectionName, + startOffset: `0x${candidate.startOffset.toString(16)}`, + recordCount: candidate.recordCount, + payloadBytes: `0x${candidate.payloadBytes.toString(16)}`, + wanted: candidate.records + .filter((record) => wantedTypes.has(record.typeId) || wantedTypes.has(record.variantTypeId)) + .map((record) => ({ + index: record.index, + typeId: `0x${record.typeId.toString(16)}`, + variantTypeId: `0x${record.variantTypeId.toString(16)}`, + ccSize: `0x${record.ccSize.toString(16)}`, + d0Size: `0x${record.d0Size.toString(16)}`, + d4Size: `0x${record.d4Size.toString(16)}`, + payloadCursor: `0x${record.payloadCursor.toString(16)}` + })) + }; +} + +function main() { + const filePath = process.argv[2]; + const wantedTypes = new Set(process.argv.slice(3).map((value) => Number.parseInt(value, 16))); + const data = fs.readFileSync(filePath); + const parsed = parseLsetWdl(data); + const candidates = []; + + for (const section of parsed.sections) { + for (let startOffset = 0; startOffset < Math.min(section.size, 0x800); startOffset += 4) { + const candidate = parseTypedSection16(data, section, startOffset); + if (!candidate) { + continue; + } + const summary = summarizeCandidate(candidate, wantedTypes); + if (summary.wanted.length > 0) { + candidates.push(summary); + } + } + } + + console.log(JSON.stringify(candidates, null, 2)); +} + +main(); \ No newline at end of file diff --git a/_tmp_probe_sections.js b/_tmp_probe_sections.js new file mode 100644 index 0000000..0358925 --- /dev/null +++ b/_tmp_probe_sections.js @@ -0,0 +1,111 @@ +const fs = require("fs"); + +const ALLOWED_U5 = new Set([0x20, 0x22, 0x30]); + +function readU32LE(buffer, offset) { + return buffer.readUInt32LE(offset); +} + +function readU16LE(buffer, offset) { + return buffer.readUInt16LE(offset); +} + +function isStructuredCandidate(record) { + if (record[0] >= 0x200) { + return false; + } + if (record[1] === 0 && record[2] === 0) { + return false; + } + if (record[1] >= 0x4000 || record[2] >= 0x4000) { + return false; + } + if (record[3] > 0x20 || record[4] > 0x04) { + return false; + } + return ALLOWED_U5.has(record[5]); +} + +function probeFile(filePath) { + const data = fs.readFileSync(filePath); + const headerSize = readU32LE(data, 0); + const audioSize = readU32LE(data, 4); + const sectionSizes = []; + for (let offset = 8; offset < 0x38; offset += 4) { + sectionSizes.push(readU32LE(data, offset)); + } + + let cursor = headerSize + audioSize; + const sections = []; + for (let index = 0; index < sectionSizes.length; index += 1) { + const size = sectionSizes[index]; + const start = cursor; + const end = start + size; + const bytes = data.subarray(start, end); + cursor = end; + + let rowCount = null; + let rootHits = 0; + let bulkHits = 0; + + if (bytes.length >= 4) { + rowCount = readU32LE(bytes, 0); + for (let rowIndex = 0; rowIndex < Math.min(rowCount, 5000); rowIndex += 1) { + const base = 4 + rowIndex * 24; + if (base + 24 > bytes.length) { + break; + } + const words = []; + for (let wordIndex = 0; wordIndex < 12; wordIndex += 1) { + words.push(readU16LE(bytes, base + wordIndex * 2)); + } + const left = [words[4], words[5], words[0], words[1], words[2], words[3]]; + const right = [words[10], words[11], words[6], words[7], words[8], words[9]]; + if (isStructuredCandidate(left)) { + rootHits += 1; + } + if (isStructuredCandidate(right)) { + rootHits += 1; + } + } + } + + const usableSize = bytes.length - (bytes.length % 24); + for (let offset = 0; offset < usableSize; offset += 24) { + for (const sideOffset of [0, 12]) { + const base = offset + sideOffset; + const record = [ + readU16LE(bytes, base), + readU16LE(bytes, base + 2), + readU16LE(bytes, base + 4), + readU16LE(bytes, base + 6), + readU16LE(bytes, base + 8), + readU16LE(bytes, base + 10) + ]; + if (isStructuredCandidate(record)) { + bulkHits += 1; + } + } + } + + sections.push({ + index, + start, + size, + rowCount, + rootHits, + bulkHits + }); + } + + return { + filePath, + headerSize, + audioSize, + sections + }; +} + +for (const filePath of process.argv.slice(2)) { + console.log(JSON.stringify(probeFile(filePath), null, 2)); +} diff --git a/crusader_decompilation_notes.md b/crusader_decompilation_notes.md index cb874b3..837baf1 100644 --- a/crusader_decompilation_notes.md +++ b/crusader_decompilation_notes.md @@ -4,6 +4,8 @@ This file is an index. Detailed notes have been split into the `docs/` folder by Active live analysis target is now `CRUSADER.EXE`. Existing `CRUSADER-RAW.EXE` notes remain in scope as cross-reference evidence and should be cited alongside live NE addresses when they support a rename, variable role, or behavior claim. +Recent verified PSX map-viewer batch: [docs/psx/psx.md](docs/psx/psx.md) and [docs/psx/map-viewer-plan.md](docs/psx/map-viewer-plan.md) now record the latest executable-backed correction to the PSX renderer model. Current best read is that the cache builder still exports executable-named section-0 visible families (`section0_dispatch_roots`, `section0_constructor_placements`), runtime/state layers for `DAT_800758d8`, `DAT_800758d0`, `DAT_800758cc`, `DAT_800758d4`, and one offline `FUN_8003b00c` decode candidate for `DAT_8006b5d8 -> DAT_8006769c`, and packs shared PSX art into `1925` shared atlases instead of the earlier one-atlas-per-shape spread. But the same batch also shows the current fallback art path is wrong at the root: early `section0_dispatch_roots` types such as `0x0042` and `0x0049` currently bind to portrait/talk-animation bundles, so the real remaining blocker is the multi-stage per-type template/state/variant selection path, not just a missing one-step bundle lookup. + Recent verified combat-data batch: [docs/combat-dat.md](docs/combat-dat.md) now documents the shipped `COMBAT.DAT` archive end to end. Current best read is that all local Remorse/Regret variants ship the same `14`-record combat-tactic archive, each record contains a `16`-byte name plus four block offsets and bytecode, and the tactic VM is now grounded both in the live `CRUSADER.EXE` helpers (`Attack_SetupForTacticNo`, `Attack_SetupForBlockNo`, `NPC_Get/SetNPCTacticNo`) and in ScummVM's readable Crusader attack-process implementation. The new note also promotes the per-tactic human-readable catalog, including the midpoint-pressure, marker-shuttle, step-out-shoot, and stationary-chaos families. Recent verified NE movement/collision batch: [docs/raw-0008-000c.md](docs/raw-0008-000c.md) now extends the live `AreaSearch_CollideMove` lane one helper layer deeper. Current best read is that the collision-storage queue is no longer only anchored at `StorageDataProcess_Create` / `Run` and the legal-move wrappers: the live database now also carries the step-aware seg029 sweep helpers `AreaSearch_SweepShapeBetweenPoints`, `AreaSearch_SweepItemToPointWithStepUp`, and `AreaSearch_SweepShapeBetweenPointsWithStepUp`, the seg031 release-side queue cleanup pair `StorageDataProcess_Release` and `storage_process_ref_list_terminate_item_matches`, and adjacent seg090 helper `ItemCache_PushAndPopToDirectionalOffset`. The practical remaining gap in this lane is now earlier caller policy rather than local helper identity. diff --git a/docs/entity-vm-runtime-owner-resource-layout.md b/docs/entity-vm-runtime-owner-resource-layout.md index b38f681..9a1c502 100644 --- a/docs/entity-vm-runtime-owner-resource-layout.md +++ b/docs/entity-vm-runtime-owner-resource-layout.md @@ -231,19 +231,106 @@ Verified first batch landed in the live `CRUSADER.EXE` session on 2026-04-05. - Exercised the new storage-aware prototype route against the two known 16-bit repair cases (`1000:42e2` and `1420:1499`) through the active MCP session. The checked-in source has the new route wiring, but the live GUI plugin still answered with legacy behavior: `/set_function_prototype_storage` returned the old `set_function_prototype` failure body, and `/set_storage_aware_prototype` returned `404 No context found for request`. That confirms the remaining issue is live deployment parity, not endpoint design. - Rechecked the direct callers of `CreateFromSlotIndex`: `Usecode_ItemCallEvent` plus two `Interpreter_NextUsecodeOp` call sites. The `Usecode_ItemCallEvent` path explicitly calls `CreateFromSlotIndex((EntityVmContext *)0x0,0,...)` as an allocate-and-return factory, and the current caller-side uses immediately consume only base `Process`-style fields such as `procid` and termination flags. The two interpreter call sites likewise just store the returned far pointer in `DX:AX` scratch pairs before later base-process handling. - That caller evidence is enough to keep the current conservative return type for now: `CreateFromSlotIndex` is clearly manufacturing an `EntityVmContext`, but promoting the return to `EntityVmContext *` before the inheritance/base-process datatype story is explicit would probably make current caller decompilation less clear rather than more clear. +- Verified seventh live batch landed on 2026-04-06 after the refreshed MCP build came up. +- Re-exercised `set_function_prototype_storage(...)` in-session on the two known 16-bit repair cases. The route now reaches the real storage-aware implementation and can preserve the explicit `AX:DX` return storage in-session, but two live issues remain: stack offsets at `10` and above currently need `0x` prefixes to avoid landing at `0x10`/`0x12`/`0x14`/`0x16`, and `calling_convention='__cdecl16far'` still normalizes the repaired functions to plain `__cdecl`. +- Updated `/Remorse/EntityVmSlotEntry` one step deeper from the `InitSlotOwnerBuffers` and `EnsureSlotChunkLoaded` evidence: + - `+0x00 match_key_farptr` + - `+0x0a owner_chunk_count` + - `+0x12 owner_data_base` + - retained the earlier `+0x1e..+0x24` owner-buffer and chunk-state pointer pairs +- Updated local variable typing so `AcquireSlotForEntity` now carries `EntityVmSlotEntry *` locals for the current slot cursor/free-slot candidate lane, and `InitSlotOwnerBuffers` now carries an `EntityVmSlotEntry *` local for the owner-metadata scratch object. +- The decompiler payoff is immediate: `InitSlotOwnerBuffers` now shows `owner_chunk_count`, `owner_buffer_*`, and `chunk_state_*` directly, and `EnsureSlotChunkLoaded` now shows `owner_data_base` where the slot metadata seeds the later owner-data window. +- Tried the stronger storage-aware `Create(this: /Remorse/EntityVmRuntime * @ stack:0x4:4, ...)` model through the new endpoint, but it still fails with `Storage size does not match data type size: 2`. That makes the remaining blocker more precise again: the live MCP route is now good enough to express the desired 4-byte storage, but the current `EntityVmRuntime *` datatype in this 16-bit NE session still resolves to a 2-byte pointer type. +- Verified eighth live batch landed on 2026-04-06. +- Reloaded the live plugin and re-verified `set_function_prototype_storage(...)` on the two known 16-bit proof cases. The route now works in-session and preserves explicit `AX:DX` return storage cleanly, but `calling_convention='__cdecl16far'` still normalizes both `1000:42e2` and `1420:1499` to plain `__cdecl`. +- Renamed `1420:1d72` to `entity_vm_runtime_get_slot_chunk_ptr_at_offset` after confirming from `CreateFromSlotIndex`, `Load`, and `FUN_1418_035f` that it is just a wrapper over `EnsureSlotChunkLoaded` plus a caller-supplied offset. +- Renamed `1420:1d8d` to `entity_vm_runtime_release_slot_chunk_ref` after confirming from the `Interpreter_NextUsecodeOp` caller that it decrements one live chunk-state refcount and asserts if the chunk was not retained. +- Renamed `1420:1e17` to `entity_vm_runtime_try_unload_slot_chunk` after confirming from `entity_vm_runtime_apply_to_matching_owner_rows` that it only unloads a chunk when the chunk-state count has reached zero, restoring the owner-buffer entry and freeing runtime budget during cleanup/eviction. +- Added short decompiler comments to those three helpers so the slot-entry ownership story stays visible in the live database. +- Verified ninth live batch landed on 2026-04-06. +- Created provisional datatype `/Remorse/EntityVmLoadedChunkRecord` with the current stable cleanup/iterator anchors: + - `+0x06 next_offset` + - `+0x08 next_segment` + - `+0x0e saved_chunk_offset` + - `+0x10 saved_chunk_segment` + - `+0x12 slot_index` + - `+0x14 chunk_index` +- Updated `1420:1e17 entity_vm_runtime_try_unload_slot_chunk` so the second parameter is now `EntityVmLoadedChunkRecord * loaded_chunk_record`, and then tightened the return to `byte __cdecl16far` with explicit `AL` storage after caller disassembly at `1420:1f50` and `1420:1fc1` showed both call sites consume only `AL`. +- Updated the iterator local `uStack_6` in `1420:1f24 entity_vm_runtime_apply_to_matching_owner_rows` to `EntityVmLoadedChunkRecord *`, so the owner-row cleanup walk now renders `next_*`, `slot_index`, and `chunk_index` directly instead of anonymous stack-pair traffic. +- Confirmed the interpreter-side release helper caller at `1418:3330` pushes the live chunk record's `slot_index` / `chunk_index` pair from `ES:[BX+0x32]` / `ES:[BX+0x34]` together with the runtime far pointer before calling `entity_vm_runtime_release_slot_chunk_ref`, which makes the loaded-chunk record a real shared runtime record rather than a one-off cleanup scratch blob. +- Verified tenth live batch landed on 2026-04-06. +- Renamed local helper `1418:003c` to `interpreter_pop_saved_farptr` after confirming from its only caller in `Interpreter_NextUsecodeOp` that it decrements a saved-farptr stack count at `+0x80` and returns the far pointer stored at the new top entry. +- Added short decompiler comments at `1418:003c` and `1418:3330` so the interpreter-side release/restore lane stays visible without overcommitting the restored far pointer to a stronger semantic than the current evidence supports. +- Verified eleventh live batch landed on 2026-04-06. +- Created class owner `Remorse::EntityVmSlotEntry` in the live database and moved `1420:2040` under it as `CreateOrClear`. +- Tightened `Remorse::EntityVmSlotEntry::CreateOrClear` so the single parameter is now named `this` and the explicit far return storage is restored to `AX` for the `EntityVmSlotEntry *` result. +- Moved the previously global runtime cleanup helpers under `Remorse::EntityVmRuntime` as real methods: + - `1420:1d72` -> `GetSlotChunkPtrAtOffset` + - `1420:1d8d` -> `ReleaseSlotChunkRef` + - `1420:1cca` -> `DebugDumpSlotMemory` + - `1420:1e17` -> `TryUnloadSlotChunk` + - `1420:1f24` -> `ApplyToMatchingOwnerRows` +- Tightened the `ReleaseSlotChunkRef` parameter names to `runtime_farptr`, `slot_index`, and `chunk_index`, and renamed the `DebugDumpSlotMemory` far-pointer argument to `runtime_farptr` so the runtime-owned chunk/refcount lane reads more like method code than detached helper code. +- Verified twelfth live batch landed on 2026-04-06. +- Tightened `Remorse::EntityVmRuntime::GetSlotChunkPtrAtOffset` to `dword __stdcall16far GetSlotChunkPtrAtOffset(dword runtime_farptr, int slot_index, int chunk_index, dword intra_chunk_offset)` after re-checking the `CreateFromSlotIndex` and `Load` callers. The current best read is: load/ensure one slot chunk through the runtime, then add a caller-supplied intra-chunk offset pair to the returned far pointer. +- Tightened `Remorse::EntityVmRuntime::ApplyToMatchingOwnerRows` to `byte __cdecl16far ApplyToMatchingOwnerRows(dword runtime_farptr, int slot_index_filter, int chunk_index_filter)` after re-checking the `AcquireSlotForEntity` and `EnsureSlotChunkLoaded` callers. The current best read is: iterate the runtime-owned loaded-chunk list either broadly (`-1/-1`) or for one current slot/chunk pair. +- Restored explicit return storage after the storage-aware retype pass so `GetSlotChunkPtrAtOffset` still returns its far pointer in `DX:AX` and `ApplyToMatchingOwnerRows` still returns its boolean result in `AL`. +- Verified thirteenth live batch landed on 2026-04-06. +- Lifted the grouped runtime methods from split-word `runtime_farptr` parameters to explicit 4-byte `EntityVmRuntime * this` storage using `/Remorse/EntityVmRuntime *32` in-session. The live signatures now read as real methods for: + - `Create` + - `InitSlots` + - `ReleaseSlots` + - `DebugDumpSlotMemory` + - `ReleaseSlotChunkRef` + - `GetSlotChunkPtrAtOffset` + - `TryUnloadSlotChunk` + - `ApplyToMatchingOwnerRows` + - `EnsureSlotChunkLoaded` +- `Remorse::EntityVmRuntime::Create` is the biggest change in that batch: it no longer needs the old split-word placeholder form and now holds `dword __cdecl16far Create(EntityVmRuntime * this, word owner_type, word owner_id)` with the original `AX:DX` return preserved. +- `EnsureSlotChunkLoaded` now also carries the clearer `EntityVmRuntime * this, short slot_index, short chunk_index` signature with the original far-pointer return preserved in `DX:AX`. +- `AcquireSlotForEntity` and `InitSlotOwnerBuffers` are now fully over that hurdle too: `AcquireSlotForEntity` returns `EntityVmSlotEntry *32` in `DX:AX`, and `InitSlotOwnerBuffers` now carries `EntityVmSlotEntry *32 slot_entry` as its third parameter. +- Verified fourteenth live batch landed on 2026-04-06. +- Finished the remaining straightforward VM pointer cleanup outside the hottest runtime helper cluster: + - `1430:0000 Remorse::EntityVmOwnerResource::Create` -> `byte __cdecl16far Create(EntityVmOwnerResource * this, dword owner_resource_spec)` + - `1430:00fd Remorse::EntityVmOwnerResource::Destroy` -> `Destroy(EntityVmOwnerResource * this, uint destroy_flags)` + - `1420:1601 Remorse::EntityVmRuntime::Destroy` -> `byte __cdecl16far Destroy(EntityVmRuntime * this, word destroy_flags)` + - `1420:10b6/10da/1162/118f/1278 Remorse::EntityVmContext::{FreeBuffer, SyncGlobalValueAndDispatch, Destroy, Save, Load}` now all carry explicit `EntityVmContext *32 this` +- That leaves `CreateFromSlotIndex` as the one clearly still-complex VM signature in this family cluster: the body still shows a far `this`, but the remaining argument pack needs a dedicated caller-side recovery pass rather than another pointer-only rewrite. +- Verified fifteenth live batch landed on 2026-04-06. +- Recovered the mixed caller pack on `1420:0eec Remorse::EntityVmContext::CreateFromSlotIndex` far enough to replace the old anonymous split arguments with caller-backed names: + - `dword owner_source_farptr` + - `dword pitemno_farptr` + - `word mode_flags` + - `word slot_index` + - `word value_add_offset` + - `word intra_chunk_offset` + - `dword ucparam_farptr` + - `uint ucparamsize` +- Restored explicit far return storage on `CreateFromSlotIndex` to `AX:DX` after the storage-aware apply briefly dropped it. +- The same live pass also made the remaining endpoint weakness more concrete again: once the caller-backed custom-storage pack is applied, the endpoint still textualizes the function as plain `dword __cdecl` instead of preserving the earlier higher-level `UsecodeProcess *` / `__stdcall16far` surface, even though the decompiler now keeps the correct argument boundaries and the return really is back in `AX:DX`. +- Current best caller-backed read for `CreateFromSlotIndex` is now narrower and more useful than the old placeholder form: + - `owner_source_farptr` is a real far-pointer input that is persisted to context `+0x11b/+0x11d` + - `ucparam_farptr` is a real far-pointer input copied into the backward-growing buffer at `+0x102` + - `slot_index`, `value_add_offset`, and `intra_chunk_offset` are distinct scalar inputs rather than one collapsed anonymous pack + - the conservative semantic story is still `factory/setup bridge that returns a far process/context pointer`, not `final inheritance-clean constructor signature` Current live datatype state: - `/Remorse/EntityVmOwnerResource` is the cleanest landed class in this lane so far. - `/Remorse/EntityVmRuntime` currently only freezes the stable tail fields and helper pointer, not the full slot-entry schema. -- `/Remorse/EntityVmSlotEntry` now exists as a bounded helper datatype, but only the stable tail buffer-pair fields are named so far. +- `/Remorse/EntityVmSlotEntry` now exists both as a bounded helper datatype and as a live `Remorse` class owner. Its current authored surface is intentionally small: one constructor/clear method plus the stable `match_key_farptr`, `owner_chunk_count`, `owner_data_base`, and owner-buffer / chunk-state pointer anchors. +- `/Remorse/EntityVmLoadedChunkRecord` now exists as the shared cleanup/iteration record for the chunk-release and conditional-unload lane, with the currently stable next-link, saved-owner-buffer, slot-index, and chunk-index fields named. - `/Remorse/EntityVmContext` now exists and matches the current owned lifecycle cluster, but it still only records the safest field anchors rather than the full embedded mini-VM layout. - `apply_class_layout` succeeded for `Remorse::EntityVmOwnerResource` but failed for `Remorse::EntityVmRuntime` when the binder tried to apply a `this` type, even though plain ownership moves worked. - The old `apply_class_layout` dry-run null failure for `Remorse::EntityVmContext` no longer reproduces on the current live server, but the actual write-side `this` typing path is still effectively old-build behavior: the real apply and direct `set_function_this_type` calls still fail on the existing `UsecodeProcess *` lifecycle signatures with `Storage size does not match data type size: 2`. - The `EntityVmContext` lifecycle signatures are now locally repaired through PyGhidra: `CreateFromSlotIndex` plus `FreeBuffer` / `SyncGlobalValueAndDispatch` / `Destroy` / `Save` / `Load` all carry `EntityVmContext * this` as their first parameter. -- `CreateFromSlotIndex` should still keep its conservative `UsecodeProcess *` return type for the moment. The allocate-and-return behavior is clear, but the known callers currently consume it through base-process fields, and the repo does not yet have an inheritance-aware `EntityVmContext : UsecodeProcess` datatype model that would make a promoted return cleaner across the call sites. +- `CreateFromSlotIndex` should still keep a conservative semantic return in the notes for the moment. The active live endpoint now textualizes it as `dword __cdecl` after the caller-packed custom-storage cleanup, but the allocate-and-return behavior is clear, the real return storage is back in `AX:DX`, and the known callers still consume the result through base-process fields rather than through an inheritance-aware `EntityVmContext : UsecodeProcess` datatype model. - The runtime lane is now split more accurately: `InitSlots` and `ReleaseSlots` can carry a direct `EntityVmRuntime * this`, while `Create` still needs the split-word custom-storage form to avoid hidden return-storage breakage. -- The first slot-entry prototype batch is tighter now that `EnsureSlotChunkLoaded` carries a real `EntityVmSlotEntry *` local on the acquired-slot path, but the wider slot-entry model is still improved rather than finished. +- The runtime lane is grouped more accurately too: the chunk-access, chunk-ref release, debug-dump, conditional-unload, and owner-row iterator helpers now sit under `Remorse::EntityVmRuntime` instead of remaining global free functions. +- The runtime lane is also typed more accurately now: the chunk accessor is no longer a five-word anonymous wrapper, and the owner-row iterator no longer pretends its runtime pointer is two independent split-word parameters. +- The authored VM lane is now much closer to a real class surface than a namespace grouping: `EntityVmRuntime`, `EntityVmOwnerResource`, `EntityVmContext`, `EntityVmSlotEntry`, and the helper `EntityVmLoadedChunkRecord` all now participate in a mostly far-pointer-correct live type model, with `CreateFromSlotIndex` as the main remaining signature outlier. +- The slot-entry model is tighter again: beyond the earlier `owner_buffer_*` and `chunk_state_*` tails, the datatype now also exposes `owner_chunk_count` and `owner_data_base`, which makes the allocator/count path in `InitSlotOwnerBuffers` and the owner-data window math in `EnsureSlotChunkLoaded` read as object state rather than anonymous offset pairs. +- The adjacent helper map is tighter too: the slot-entry consumer side now has one pointer-plus-offset accessor, one chunk-ref release helper, one conditional-unload helper, and one named loaded-chunk iterator record instead of a mix of anonymous `1420:` placeholders and anonymous stack-pair scratch state. Current scope of that batch stayed intentionally conservative: @@ -258,9 +345,14 @@ Best immediate next moves after this landed: - inspect `EnsureSlotChunkLoaded` and adjacent `1420:` helpers again now that `AcquireSlotForEntity` returns `EntityVmSlotEntry *`, and push the slot-entry type one step deeper only where the resulting local/object read is genuinely clearer - decide whether `CreateFromSlotIndex` can safely promote its return type from `UsecodeProcess *` to `EntityVmContext *`, or whether it should stay a factory-style bridge that only types `this` - if the context/base-process inheritance story becomes explicit in datatypes, revisit `CreateFromSlotIndex` return typing then; until that point, keep the current `UsecodeProcess *` return even though the body itself clearly builds an `EntityVmContext` -- recover a storage-aware `this`-typing path for `Create` specifically; `InitSlots` and `ReleaseSlots` no longer need to stay in the unresolved set +- decide whether `match_key_farptr` at `+0x00` should stay as a neutral far-pointer field or can now be promoted to a stronger entity/owner key name from caller-side evidence +- recover a storage-aware `this`-typing path for `Create` specifically; the live route now works well enough to test explicit 4-byte storage, but the remaining blocker is the 2-byte `EntityVmRuntime *` datatype itself rather than endpoint reachability +- inspect the broader `Interpreter_NextUsecodeOp` lane around `1418:3330` now that the release call and `interpreter_pop_saved_farptr` are anchored, and decide whether the loaded-chunk record can absorb any more of the surrounding save/restore stack traffic without overfitting transient interpreter locals - redeploy or otherwise verify the live storage-fallback `set_function_this_type` / `apply_class_layout` build, then retry the `EntityVmContext` lifecycle typing pass in-session before dropping back to local PyGhidra - identify one or two additional strongly owned runtime or owner-resource helpers if the live session exposes them cleanly +- decide whether `ApplyToMatchingOwnerRows` should keep its current generic split-word parameters under `Remorse::EntityVmRuntime` or whether the first argument pair is now well enough understood to collapse into a typed runtime `this` +- decide whether the newly clarified `runtime_farptr` argument on `GetSlotChunkPtrAtOffset` and `ApplyToMatchingOwnerRows` is enough to justify a safe typed-`this` experiment on those methods, or whether the current `EntityVmRuntime *` pointer-size issue still makes the explicit `dword runtime_farptr` form the least misleading representation +- use the now-recovered `CreateFromSlotIndex` caller pack as the baseline for any next cleanup, and only chase a prettier return type once the base-process inheritance story is explicit enough to make that promotion a real readability win - keep the masked-create hub and offset-specialized wrapper ladder outside the class until caller-side role recovery is tighter ## Source-Emission Guidance diff --git a/docs/psx/map-viewer-plan.md b/docs/psx/map-viewer-plan.md new file mode 100644 index 0000000..f12ae93 --- /dev/null +++ b/docs/psx/map-viewer-plan.md @@ -0,0 +1,164 @@ +# PSX Map Viewer And JL-9 Investigation Plan + +## Scope + +- Active target: retail PlayStation `SLUS_002.68` already loaded in Ghidra. +- Keep all PSX documentation in `docs/psx/`. +- Primary objective: get PSX maps loading into the existing map viewer coherently. +- Secondary objective: make PSX graphics export with the correct palette automatically instead of by partial heuristics. +- Tertiary objective: determine whether `JL-9` is a real weapon in the PSX build, how it is unlocked or granted, and which sprite/bundle represents it. + +## Current State + +- `docs/psx/psx.md` already closes the boot executable, the broad `LSET*.WDL` layout, and the likely split between map-like regions and graphics-like regions. +- The earlier `region00-first` viewer export is now known to be based on a bad assumption: the `~45..59` records it exposes per map are only the small top-level WDL descriptor stream, not the full level content. +- The stronger current model is a multi-section bundle layout: a top-level `0x18`-byte dispatch-record table, typed subordinate resource tables rooted at `DAT_800758cc/d0/d4/d8`, and at least one separate compressed level-state blob that is inflated into `DAT_8006769c` by `FUN_8003b00c(..., 0x3e00, 0x3e00)`. +- The strongest current graphics source remains `post_audio_region_04`. +- A first PSX debug scene has already been exported experimentally, but the active workflow is now the renderer-local `.cache` pipeline rather than `site` output. +- The active live probe now builds provisional real-art atlases in `map_renderer/src/build-psx-cache.js` from `map_renderer/STATIC_PSX` into `.cache/psx`, `.cache/reference-data/psx-remorse`, and `.cache/scene-cache/psx-remorse/...`. +- The current verified processed build exposes `62` PSX maps in the live renderer catalog under the runtime-record scene format (`4032` atlas-backed shapes, `1925` packed shared atlases after the latest atlas pass). +- The exporter root cause is now clearer: the old five-region post-audio carve was still masking the real visible payload. Loader-sized `post_audio_section_00` contains both the small `0x18` root descriptor rows and the dense 24-byte bulk placement rows, so the cache builder now recovers both visible families from that first real section instead of from the guessed `region00/region01` split. +- A verified full rebuild now carries `region00 + region01` across all `62` maps. `LSET1/L0.WDL` now emits `1189` items, `LSET1/L1.WDL` emits `754`, and every rebuilt map now reports `uniqueZCount > 1` instead of the earlier mostly-flat `z = 0` export. +- The next subordinate layers are now structurally split too: `DAT_800758d8` is the per-type art/template bank, `DAT_800758d0` feeds the simple constructor's local component payload, and `DAT_800758cc/d4` feed the compound constructor's state/variant tables. The executable model is solid, but the generic raw-file export for `DAT_800758cc/d0/d4` is not currently landing in the live scene cache, so that serialization path stays open work. +- The late LSET template bank is now less speculative too. The currently working map-local `DAT_800758d8` candidate is not the old "small typed section" guess; on retail `LSET1/L9.WDL` it decodes cleanly only when the parser treats the late large section as a bank with an embedded `+0x38` start, which is now enough to recover real bundle-backed mappings for a first subset of map types. +- The main visible bulk layer is no longer flat. The accepted `region01` placements now use the constructor-backed `+0x06` byte as provisional `z`, and `LSET1/L0.WDL` currently exports `11` distinct structured elevation levels instead of one forced `z = 0` plane. +- One renderer-side mismatch is now closed: PSX sprites use authored `item.screen` rectangles, and the bounding/highlight overlay path now uses those same authored rectangles instead of recomputing a DOS-style wireframe from provisional `world` coordinates. +- The executable now closes the last projection stage: authored object coordinates land in object fields `+0x3c/+0x40/+0x44` as `16.16` fixed-point values, and `FUN_80040d44` / `FUN_80040f78` project them with `screen_x = y - x` and `screen_y = 2*z - (x + y)/2` before writing the final screen rectangle at `+0x20..+0x2e`. +- Palette handling is partially grounded by runtime VRAM evidence, but the per-placement override rule is still missing. +- The scene/cache naming now uses executable-backed family names (`section0_dispatch_roots`, `section0_constructor_placements`) with the old `region00/region01` labels kept only as legacy aliases. +- The offline `FUN_8003b00c` path now exists in the renderer-local exporter and serializes one candidate on-disk compressed source plus the decoded `0x3e00` state buffer into the cache for each map. +- The type-to-art pass is still open. The exporter now scans parsed per-type template-bank payloads for bundle references, and it no longer promotes the disproven scan-order bundle fallback into visible map art. Unverified types stay on placeholders until the executable state/type path yields a real art binding. +- That loader-shaped bank selection is now already paying off in the live cache: map `9` moved from `0` resolved bundle-mapped items to `111` after the template pass switched to the embedded late-section parse, even though unresolved root-dispatch families such as `0x0042` and `0x0049` still need the downstream state/variant path before they can stop using placeholders. +- The old fallback art binding is now positively disproven for map rendering, not just "still unverified": in the live cache, early `section0_dispatch_roots` types `0x0042` and `0x0049` repeatedly bind to portrait/talk-animation bundles (for example map `0` offsets `0x000B2970` and `0x000D84F4`), which confirms the section-0 dispatch rows are generic runtime-object descriptors whose visible art still depends on downstream per-type state/variant selection. +- The executable-side type path is now clearer and named in the live PSX Ghidra database. `psx_object_create_simple_record` and `psx_object_create_compound_record` both index the same per-type banks rooted at `DAT_800758d8/d0/cc/d4`; `psx_object_select_state_script` selects an active state script from `DAT_800758cc`, `psx_object_advance_state_script` at `0x80025d68` interprets sentinel-driven script records, `psx_object_lookup_variant_entry` resolves a companion entry from `DAT_800758d4`, and `psx_reset_type_runtime_banks_from` at `0x80025ce8` is the nearby bank-reset helper that had been misnamed earlier. So the missing map-render rule is not one flat `type -> bundle` table but a multi-stage runtime selection path. +- The visible render pass is less opaque now too. `FUN_80041378` draws in three stages: the sorted visible-object list through `FUN_80041458`, a second special-visible list through `FUN_80041144`, and then HUD/overlay/icon primitives through `FUN_800416cc`. That means the remaining map-viewer gap is still mainly in world-object and special-object families, not in the HUD pass. +- The stage-2 path is now strong enough to affect renderer planning directly. `FUN_80040f78` is the queue-builder for the `FUN_80041144` pass: it projects an object just like the main `FUN_80040d44` path but appends it to `DAT_80078b70` / `DAT_80067472` instead of the main `DAT_8006ad5c` visible list. So a renderer that only models the stage-1 visible list will still miss a real world-facing object lane. +- Palette override provenance is tighter too: object field `+0xa0` is the original authored source-record pointer written by both constructors, so the current override path in `FUN_80041458` is reading authored record bytes directly rather than a hidden runtime side table. +- One narrow renderer-side consequence is now verified in output, not just in notes: the cache builder now applies the executable-backed `0x0050` selector map (`0..3 -> frame 0..3`) as a temporary fallback, and retail map `9` now exports `type=80 state_selector=1 chosen_frame=1` instead of forcing frame `0`. +- `JL-9` already appears in recovered PSX weapon-name tables, but gameplay availability and sprite identity are not yet closed. + +## Success Criteria + +### Map-viewer success + +- At least one PSX map loads in the existing viewer with stable world placement, defensible draw order, and recognizable room/layout structure. +- The PSX path reuses the existing viewer pipeline instead of creating a separate one-off viewer. +- Exported scene data preserves enough raw metadata to keep later decomp passes reversible. + +### Palette success + +- Bundle export chooses the same palette family the runtime uses for that placement class. +- At least one tile-heavy scene and one object-heavy scene render with mostly correct colors without manual palette swapping. +- Palette selection logic is encoded in exporter metadata or viewer-side decode rules, not only in prose notes. + +### JL-9 success + +- `JL-9` is classified as one of: fully usable weapon, cut/incomplete leftover, menu-only string, or debug-only grant. +- The unlock or acquisition path is identified from executable logic, data tables, or authored content. +- The weapon's sprite or best candidate art bundle is identified and documented. + +## Workstreams + +## 1. Close the PSX map record format + +Purpose: replace the invalid `small top-level record stream == whole level` assumption with a renderer-fed scene that includes the real bulk map substrate. + +Tasks: + +1. Revisit the executable loader chain around the `LSET*.WDL` stream consumer and name the section families loaded into `DAT_800678f4`, `DAT_80067720`, `DAT_800758cc/d0/d4/d8`, `DAT_800675f8`, and `DAT_8006769c`. +2. Prove which loaded section is the small top-level object/dispatch list and which section holds the actual bulk map substrate. +3. Recover the format and semantics of the compressed blob that `FUN_8003b00c` inflates into the `0x3e00` level buffer. +4. Tie one concrete subordinate record family to the constructor inputs that feed object `+0x3c/+0x40/+0x44` as `16.16` fixed-point coordinates. +5. Recover the bundle/frame binding rule for map placements well enough to stop relying on broad candidate pairing. +6. Recover the draw-order or layer rule used when multiple map records overlap. +7. Validate the corrected multi-section schema on at least `L0.WDL` and `L1.WDL` so the decode is not overfit to one level. + +Expected output: + +- a stable PSX placement schema recorded in `docs/psx/` +- one exporter that emits scene JSON in the same broad shape as the existing viewer pipeline +- one known-good reference map whose structure is visually recognizable + +## 2. Close palette selection instead of guessing it + +Purpose: make exported graphics match the runtime palette path automatically. + +Tasks: + +1. Continue from the already identified texture draw helpers and the caller path that reads palette override metadata from the object field currently described as `+0xa0` in the notes. +2. Determine whether the placement record itself, a second-stage runtime header, or a side table supplies the override palette index. +3. Reconcile the live VRAM `row 0xF0 / x=0` success case against the on-disk palette blob so the export path can reproduce the runtime source instead of only matching dumps. +4. Identify whether different bundle modes or resource classes use different CLUT selection rules. +5. Add exporter-side palette metadata that preserves both bundle default palette and resolved placement palette. +6. Validate against at least three anchor assets: one wall/floor-heavy tile set, one object sprite with obvious color identity, and one UI or portrait-like asset. + +Expected output: + +- a documented palette-selection rule in `docs/psx/` +- exported PSX atlases or frame PNGs that no longer require manual palette picking for the common solved families +- a short unresolved list only for genuinely exceptional palette cases + +## 3. Integrate the PSX decode into the existing map viewer + +Purpose: stop treating PSX as a disconnected experiment and make it a first-class renderer source. + +Tasks: + +1. Define one PSX scene format version that keeps raw decode fields visible while still fitting the current viewer's atlas-plus-scene model. +2. Export one minimal but real PSX map scene from the solved map schema and load it through the existing viewer path. +3. Compare the rendered result against in-game screenshots, captured VRAM/framebuffer evidence, or clearly identifiable room geometry. +4. Tighten the exporter until one map reads coherently before trying to bulk-export the entire disc. +5. Only after a coherent single-map success, generalize to more `LSET` maps and add any PSX-specific catalog or loader toggles the viewer needs. + +Expected output: + +- one coherent PSX map visible in the existing viewer +- one stable exporter path that can be iterated on without forking the viewer architecture + +## 4. Investigate JL-9 as data, logic, and art + +Purpose: close the question of whether `JL-9` is real and what it corresponds to visually. + +Tasks: + +1. Locate the PSX weapon-name table and the code/data structure that indexes into it. +2. Identify the item or weapon definition row for `JL-9`, including ammo type, flags, and any inventory/equipability markers. +3. Trace all code and data references to that row: mission rewards, cheats, debug grants, pickups, shop/loadout flow, or scripted usecode equivalents if present. +4. Check whether `JL-9` appears in the pre-alpha build under the same index and whether its surrounding data differs from retail. +5. Identify the sprite by following the weapon/item definition to the bundle/frame or icon resource it uses. +6. Classify the result clearly: shipped and obtainable, shipped but gated/unused, or string/data leftover only. + +Expected output: + +- a short `docs/psx/` note or section that states whether `JL-9` is real +- the acquisition or unlock path if one exists +- the best supported sprite or bundle match + +## Recommended Execution Order + +1. Finish map-record closure enough to bind placements to the right art. +2. Replace the current `.cache` runtime-record probe premise with the corrected multi-section WDL model, then recover the runtime type/resource lookup that can replace the still-provisional `u0 -> bundle index` rule with real art binding. +3. Get one map loading coherently in the existing viewer. +4. After the viewer path is grounded, use the now-stronger bundle identification flow to close `JL-9` sprite identity and availability. + +## Immediate Next Batch + +1. In Ghidra, tighten the section-family naming around `DAT_800678f4`, `DAT_80067720`, and the candidate `DAT_8006b5d8` source so the current `section0_*` labels can be promoted from exporter-safe names to exact loader names. +2. Record which helpers read `DAT_80067720` versus which helpers read the decompressed `DAT_8006769c` buffer now that the offline decode path is present in the cache. +3. Compare the rebuilt all-map exports against recognizable rooms and decide whether the remaining missing structure now lives mainly in the decoded `DAT_8006769c` buffer or in still-unrendered subordinate tables. +4. Tighten the raw file mappings for the newly exported runtime-bank layers (`DAT_800758d8`, `DAT_800758d0`, `DAT_800758cc`, `DAT_800758d4`) so their current section selection is proven rather than heuristic. +5. Recover an actual bundle/frame reference from the per-type template payloads or their consumers so the exporter can replace the now-disproven scan-order bundle fallback with a verified type-to-art rule. +Current delta: the template bank selection is now stronger and already recovers real art for a first subset, but the still-missing families need the stage-1/stage-2 object draw path plus `DAT_800758cc/d4` state interpretation, not more HUD/overlay decoding. +Current delta: stage 2 is no longer hypothetical. The next renderer-improvement candidate is to expose/export the queued-object lane that feeds `FUN_80041144`, because the executable now clearly maintains it separately from the main visible list. +6. Split section-0 placements into at least three executable-backed render classes: world-facing geometry/object placements, animated runtime-only objects, and clearly non-map-facing UI/talk assets such as the portrait bundles currently surfacing through fallback art matching. +7. Decode the `psx_object_advance_state_script` sentinel opcodes (`ffff`, `fffe`, `fffd`, `fffc`, `fffb`) well enough to tell when a placement loops, jumps into a subsidiary script, or fires a side-effect helper, because that state-machine branch is now the main discriminator between map-facing art and non-map runtime assets. +Current delta: `fffe` is now closed as an audio/effect dispatch through `FUN_8004061c`, so the next sentinel work should focus on the remaining control-flow opcodes. +8. In parallel with the map pass, trace the palette-override read path from the known draw helper caller and document which source field feeds the resolved CLUT. +9. Locate the `JL-9` weapon entry in the PSX executable tables and log its table index, surrounding weapon names, and all code/data xrefs. +10. Create a short follow-up note in `docs/psx/` after the batch rather than burying the result only in Ghidra comments. + +## Documentation Rule For This Track + +- Keep long-form findings in `docs/psx/psx.md` or another dedicated file under `docs/psx/`. +- Keep this file as the active plan and update it when a major blocker closes or the execution order changes. +- When `JL-9` closes cleanly, give it its own short note under `docs/psx/` instead of leaving it as one bullet in a larger map note. \ No newline at end of file diff --git a/docs/psx/psx.md b/docs/psx/psx.md index ec7ac49..bbeaef5 100644 --- a/docs/psx/psx.md +++ b/docs/psx/psx.md @@ -380,6 +380,9 @@ Current color blocker: - both main texture draw helpers (`FUN_80044bdc` and `FUN_80044e9c`) fall back to the bundle default palette index only when no override is present - the important caller path at `FUN_80041458` ORs in a high-byte palette override from object/tile metadata pointed to by object field `+0xa0` +- that `+0xa0` pointer is now tighter too: both object constructors store the original authored source-record pointer there, so the override is not coming from a hidden runtime side table. For current solved families the draw helper reads the override straight from the authored record bytes: + - type `0x003e..0x00ab`: high byte of source word at record `+0x06` + - type `>= 0x00ac`: high byte of source word at record `+0x0c` - that means standalone bundle previews can still be wrong even when the bundle parser and raw CLUT table are both correct - the extractor now emits wider `u16x12` raw CSV views for `post_audio_region_01` and `post_audio_region_02` because the relevant override state appears to live beyond the first 6 words of those candidate placement records - the current top-ranked portrait bundle (`bundle_00064478`, default palette index `106`) is a useful color-validation anchor because the grayscale frame is obviously correct while all raw-palette candidates remain visibly wrong @@ -497,27 +500,30 @@ Current evidence-backed next step: Current renderer-compatibility result: -- a first PSX-compatible static real-art probe scene is now exported for the public map renderer -- exporter script: - - `tools/psx_export_map_debug_scene.py` -- current generated public-report outputs: - - `k:\ghidra\Crusader_Decomp_Public\map_renderer\site\data\maps\psx-remorse\map-0\scene.json` - - multiple copied frame atlases such as `k:\ghidra\Crusader_Decomp_Public\map_renderer\site\data\maps\psx-remorse\map-0\bundle_0003917C_frame_000.png` - - `k:\ghidra\Crusader_Decomp_Public\map_renderer\site\data\catalog.json` - - `k:\ghidra\Crusader_Decomp_Public\map_renderer\site\data\catalogs\psx-remorse.csv` -- current scene characteristics: - - source: filtered `LSET1/L0.WDL` `post_audio_region_01` paired-record candidates - - rendered items: `1050` - - unique bundle-backed shape definitions: `49` - - copied atlas/frame PNGs: `62` - - bounds: `3896 x 8431` - - scene format version: `psx-region01-bundle-probe-v1` - - current probe stats: `u0` span `62..111`, fallback frame count `187` +- the old Python/site real-art probe remains useful as discarded negative evidence, but it is no longer the active viewer workflow +- the active integration path now lives inside `k:\ghidra\crusader_map_viewer\map_renderer` and builds live data into `.cache` from `STATIC_PSX` +- active renderer-local scripts: + - `src/build-psx-cache.js` + - `src/lib/psx-cache.js` +- build entrypoint: + - `npm run build-psx-cache` +- current generated live-cache outputs: + - `k:\ghidra\crusader_map_viewer\map_renderer\.cache\psx\catalog.json` + - `k:\ghidra\crusader_map_viewer\map_renderer\.cache\reference-data\psx-remorse\reference-data.json` + - per-map scene files under `k:\ghidra\crusader_map_viewer\map_renderer\.cache\scene-cache\psx-remorse\map-*\\scene.json` + - `k:\ghidra\crusader_map_viewer\map_renderer\Catalogs\psx_shape_catalog_remorse.csv` +- current processed-cache characteristics from the verified build: + - source: `k:\ghidra\crusader_map_viewer\map_renderer\STATIC_PSX` + - scene format version: `psx-region01-provisional-art-probe-v2` + - processed maps: `23` + - shared shape definitions: `313` + - shared atlases: `313` + - largest currently useful placement-heavy maps: `LSET1/L0` (`1050` items), `LSET4/L33` (`942` items), `LSET5/L48` (`851` items), `LSET6/L51` (`463` items), `LSET7/L63` (`315` items) Current art-binding hypothesis used by this probe: - region-01 `u0` is treated as a provisional direct bundle index into the extracted `sprite_bundles/` set -- region-01 `u4` is treated as a provisional frame index within that bundle, clamped to the highest available frame when out of range +- region-01 `u4` was originally treated as a provisional frame index within that bundle, but that interpretation is now considered wrong; the constructor chain instead points to `u4` as a state/script selector candidate - this is evidence-backed enough to render real PSX art in the existing map renderer, but not strong enough yet to call the binding solved - the strongest negative check so far is that the region-01 `u5` values (`0x20`, `0x22`, `0x30`) do not match the bundle default palette indexes, so the palette-selection/control path is still missing @@ -540,13 +546,19 @@ New loader/data evidence from this pass: - little-endian words: `0x004A, 0x1603, 0x0EE7, 0x0000, 0x0001, 0x0020` - that record family is a better next target than the invalidated direct bundle probe because it already exposes a small type-like word (`0x004A`) plus coordinate-like words without forcing an arbitrary raw-bundle index -What this first public renderer pass means: +What this renderer pass means now: -- the existing renderer app can now load a PSX scene bundle from the static report without any PC `FIXED.DAT` dependency -- this is currently a real-art probe of filtered placement candidates, not a final decoded PSX map -- the renderer now displays extracted bundle art from `post_audio_region_04` instead of synthetic colored stand-ins -- the current output is still useful because it shows that filtered region-01 records can drive recognizable, repeatedly used PSX art through the existing renderer pipeline -- one bad extracted origin (`1x6` sprite with `xoff=65535`) initially blew out the fit bounds; the exporter now sanitizes implausible origins before writing scene metadata +- the live renderer can expose PSX as an optional game only after the processed cache exists; it is no longer tied to ad hoc `site` exports +- the current active output is now a provisional real-art probe rather than a placeholder-only type/lane scene +- the processed-cache path is now compatible with the existing shared reference-data pipeline and PC-style catalog grouping, which keeps PSX integration inside the normal viewer architecture instead of forking it +- the old real-art probe is still valuable as negative evidence because it proved that direct raw bundle ordering produces obviously wrong scene content + +New renderer-grounded improvement from this pass: + +- `src/lib/psx-cache.js` now scans `post_audio_region_04` directly from `STATIC_PSX`, parses bundle headers in JavaScript, colorizes the extracted frames with the currently available default/heuristic palette path, and writes per-map bundle atlases into `.cache/reference-data/psx-remorse` +- the live cache no longer uses only synthetic placeholder shapes for map `0`; the current `LSET1/L0.WDL` scene references `49` real atlases and `62` real sprite frames under the still-provisional direct `u0 -> bundle index` hypothesis +- extracted bundle origins are now sanitized on import so bad `0xFFFF` offsets do not blow out the scene bounds; `LSET1/L0.WDL` is back to a sane `3896 x 8431` footprint instead of the broken `67k`-pixel-wide intermediate result +- PSX shape definitions now use a `1x1x1` footprint and the scene items synthesize viewer-compatible `world.x/world.y/world.z` from the final screen anchors; this keeps bounding-box and preview overlays aligned with the PSX art probe instead of projecting nonsense from the raw `u1/u2/u3` words Current app compatibility notes: @@ -562,6 +574,242 @@ Immediate implications for the next decode pass: - the palette override path is still the main blocker to correct final color selection even when the bundle/frame choice is plausible - once the bundle key and palette control path are recovered, the same scene-export path can graduate from `real-art probe` to actual PSX map rendering +## PSX Provisional Real-Art Probe + +The live renderer now prefers a smaller loader-backed record family when it can normalize that family into structured placement rows, while still preserving the older dense region-01 probe as a fallback/debugging strategy. + +What changed in this pass: + +- the temporary Python probe established the scene structure, but the active implementation is now renderer-local JavaScript rather than a standalone exporter +- `src/lib/psx-cache.js` now reads `STATIC_PSX`, parses `LSET*.WDL`, prefers normalized `post_audio_region_00` count-prefixed records when they pass the existing structured-candidate filter, falls back to `post_audio_region_01` otherwise, scans `post_audio_region_04` for sprite bundles, and emits per-map atlases built from the extracted PSX frame data +- `src/build-psx-cache.js` writes the resulting processed data into the live cache tree: + - `k:\ghidra\crusader_map_viewer\map_renderer\.cache\psx\catalog.json` + - `k:\ghidra\crusader_map_viewer\map_renderer\.cache\reference-data\psx-remorse\reference-data.json` + - per-map scenes under `k:\ghidra\crusader_map_viewer\map_renderer\.cache\scene-cache\psx-remorse\...` + - `k:\ghidra\crusader_map_viewer\map_renderer\Catalogs\psx_shape_catalog_remorse.csv` +- the viewer now detects `psx-remorse` from the processed manifest instead of from a fake PC-style source-file heuristic +- scene items now keep the candidate PSX `x/y` words directly in `world`, use the executable-backed projection basis `screen_x = y - x`, `screen_y = 2*z - (x + y)/2` with provisional `z = 0`, and keep `1x1x1` shape footprints so overlay boxes remain usable without pretending the old PC-style world export is solved + +Current verified processed-cache result: + +- scene format version: `psx-runtime-record-probe-v1` +- processed maps: `61` +- atlas-backed shapes: `1112` +- atlases: `1112` +- `LSET1/L0.WDL` preferred source family: `post_audio_region_00` +- `LSET1/L0.WDL` rendered items from the preferred family: `59` +- `LSET1/L0.WDL` still has `1050` dense fallback `post_audio_region_01` records preserved in scene metadata for comparison +- `LSET1/L0.WDL` resolved real-art atlases for the preferred family: `18` +- `LSET1/L0.WDL` resolved sprite frames for the preferred family: `26` +- `LSET1/L0.WDL` unique `u0` types in the preferred family: `18` +- lane split: + - `0x0020`: `26` + - `0x0022`: `21` + - `0x0030`: `12` +- `LSET1/L0.WDL` current scene bounds after the runtime-record pass: `1313 x 438` +- `LSET1/L0.WDL` currently resolves all `59` preferred-family records to real extracted bundles with `0` placeholder fallbacks, but still clamps `15` frame requests down to the highest available extracted frame index +- one visible viewer mismatch is now separated from the remaining map-format problem: PSX sprites already draw from authored `item.screen`, but the old highlight/bounding overlay path was still recomputing DOS-style wireframes from provisional `item.world`; `scene-presentation.js` now falls back to authored screen rectangles for PSX items instead of drawing those incorrect projected boxes + +Why this matters: + +- this is the first live viewer path that prefers a loader-compatible, count-prefixed record family instead of treating the huge dense region-01 stream as the only scene source +- it keeps the strongest current working assumption narrower and more explicit: + - normalized `post_audio_region_00` rows are now the preferred placement family when they satisfy the same structural checks as the older region-01 records + - `post_audio_region_01` remains a dense fallback evidence source instead of being silently discarded + - the art lookup is still unresolved and must be recovered from the real runtime resource tables rather than inferred from raw bundle ordering +- it also moves the viewer one step closer to the executable model by applying the recovered PSX projection basis directly in the cache builder instead of plotting raw `u1/u2` values on a pseudo-screen plane + +Immediate next consequence: + +- the next map-format batch should treat the processed `.cache` runtime-record probe as the baseline renderer target and focus on proving exactly how the normalized `post_audio_region_00` words line up with the constructor-fed `x/y/z` fields +- the old dense region-01 path should stay available as evidence, but it should no longer be the default scene family unless the loader-backed family fails to normalize on a given map +- that means the remaining visual corruption should now be treated primarily as a placement/schema problem again, not as a box-overlay problem; the next pass needs to recover the authoritative height lane and the exact constructor-fed field mapping instead of spending more time on DOS-style overlay math + +## PSX Map-System Correction + +The current live viewer export was built on the wrong premise. The `~45..59` records currently exported per PSX map are not enough to represent a whole Crusader level, and executable tracing now shows why. + +What the loader actually does: + +- `wdl_resource_bundle_load_by_index` reads the selected `LSET*.WDL` into multiple section pointers, not one flat placement stream. +- The first runtime section is a top-level table at `DAT_800678f4` whose record stride is `0x18` bytes. +- The loader iterates that first section with: + - `for each 0x18-byte top-level record` + - `type = record[+0x08]` + - `dispatch through PTR_PTR_80063118[type]` +- Those dispatch handlers do not behave like a terrain-tile walker. They construct one runtime object or a tiny object cluster at a time through `FUN_800249f4`, `FUN_80024eec`, `FUN_8003c314`, `FUN_8003c714`, and `FUN_8003cc08`. + +Why the current export is incoherent: + +- the current `region00`-first exporter is effectively treating that small top-level descriptor family as if it were the whole level +- those records are only the root nodes of the level bundle's object/resource system +- they are too few because the bulk level content lives elsewhere in the loaded bundle state + +New executable-backed evidence for the missing bulk content: + +- `level_resource_stream_load` and `FUN_8003917c` populate the typed runtime resource tables rooted at `DAT_800758cc/d0/d4/d8` +- `DAT_80067720` is a small top-level `0x18` record list used by object/event-style helpers such as `FUN_80031044` and `FUN_8002b1a8`; it is not a whole-map terrain stream +- during bundle load, `FUN_8003b00c(DAT_8006769c, &DAT_8006b5d8, 0x3e00, 0x3e00)` inflates a separate compressed blob into a dedicated level buffer +- that decompressed buffer is carried through save/load helpers (`FUN_8003a0f4`, `FUN_80049890`) independently of the tiny top-level descriptor list, which is exactly what a real map substrate would do +- the two `DAT_80067720` helpers are now clearer about role too: + - `FUN_80031044` scans the `0x18`-stride rows for `0xAAAA`-tagged entries and low-6-bit selector matches, then caches a pointer to the matched row payload + - `FUN_8002b1a8` mutates matching rows by type/id and flag bits in place + - both behaviors fit a small event/marker/control list and do not look like whole-map geometry submission +- the decompressed lane is more clearly persistent substrate/state than before: + - `FUN_8003a0f4` hands `DAT_8006769c` plus `DAT_80067528` to the save helper path + - `FUN_80049890` repacks the `DAT_8006b5d8` / `0x3e00` state lane into the `0x4000` memory-card save block + - this strengthens the read that `DAT_8006769c` is the saved/restored map-state substrate while `DAT_80067720` stays the tiny top-level control list + +Current safest read: + +- the `~59` exported records are top-level WDL nodes, not the entire PSX map +- the real PSX level is split across: + - a small top-level descriptor stream + - typed subordinate resource tables + - at least one separate decompressed level-state blob +- the viewer looks nonsensical because it is rendering only one small layer of that system and mistaking it for the full map + +Immediate consequence for the exporter: + +- stop treating `post_audio_region_00` as the default whole-map scene source +- keep `post_audio_region_00` and `post_audio_region_01` as evidence sources, but pivot the next decode pass toward the multi-section WDL model recovered from the executable +- the next map-export target must include the decompressed bundle state and/or the subordinate placement/tile resources behind the top-level `0x18` records, not just the root records themselves + +Exporter status after the next renderer pass: + +- the earlier five-region post-audio carve was still wrong for visible-map recovery. The corrected loader-sized section probe shows that the first post-audio section already contains both the count-prefixed top-level descriptor rows and the dense 24-byte bulk placement rows that the flat maps were missing. +- `map_renderer/src/lib/psx-cache.js` now recovers visible families from loader-sized `post_audio_section_00` instead of treating the old guessed `post_audio_region_01` carve as the default bulk source. +- the exported scene metadata now records those visible families under executable-backed names instead of the old provisional labels: + - `section0_dispatch_roots` for the top-level dispatch/root records + - `section0_constructor_placements` for the dense constructor-fed placement records +- a verified full rebuild now exports all `62` PSX maps with large scene volumes and non-flat `z` stats. `LSET1/L0.WDL` now emits `1189` items, `LSET1/L1.WDL` jumps from `53` items to `754`, and the rebuilt catalog reports `62/62` maps with `section0_dispatch_roots + section0_constructor_placements` coverage and `uniqueZCount > 1`. +- the renderer-side reference payload no longer emits one atlas per resolved PSX shape. The new packed-atlas pass reduces the shared PSX reference cache from the old `4032` one-shape atlases to `1925` shared packed atlases across the same `4032` shape definitions, and a spot-check on `LSET1/L0.WDL` now exports the map scene itself with `atlasCount = 1` instead of a long per-bundle atlas list. +- the cache export still carries the parsed `DAT_800758d8` candidate section and an offline `FUN_8003b00c` decode candidate for the compressed source feeding `DAT_8006b5d8 -> DAT_8006769c`, but the generic raw-file `DAT_800758cc/d0/d4` serialization is not currently landing in the live scene cache and should be treated as an open exporter gap rather than a closed layer. +- this still does not mean the PSX map decode is fully solved: the viewer now has enough volume to represent whole-level candidates across the disc, but the remaining blocker is semantic decoding of the subordinate runtime banks and the separate decompressed `0x3e00` buffer, not record-count starvation. +- the type-to-art path is only partially improved. The cache builder now scans the parsed per-type art-template payloads for bundle references, and the renderer no longer treats the disproven scan-order `u0 -> bundle` mapping as trustworthy visible art. Unverified types now stay on placeholder art instead of surfacing known-bad portrait/talk bundles as map geometry. +- the scan-order fallback is now known to be wrong at the root, not merely incomplete. In the live `.cache` output, `section0_dispatch_roots` types `0x0042` and `0x0049` repeatedly bind to portrait/talk-animation bundles such as map `0` type `0042` -> offset `0x000B2970` and map `0` type `0049` -> offset `0x000D84F4`, with the same failure pattern continuing through early maps. Those portrait bundles are useful negative evidence: they show the top-level dispatch rows are generic object/state descriptors, not a direct map-graphics stream that can be paired to bundle order. + +Next decoded runtime layers from the constructor pass: + +- `DAT_800758d8` is the per-type art/template bank, not the missing whole-map substrate. `wdl_resource_bundle_load_by_index` populates it from an `8`-byte descriptor table, and both `FUN_800249f4` and `FUN_80024eec` consume it before calling `FUN_80044434` through the loader-side helper path. +- `DAT_800758d0` is a per-type companion/component bank for the simpler constructor family. `FUN_800249f4` copies the resolved pointer from that bank into the local object payload at `obj->8->[0,4]`, so this looks like a per-type component/template block rather than a top-level placement stream. +- `DAT_800758cc` is a per-type offset-table bank for the compound constructor family. `FUN_80024eec` stores it at `obj+0x88`, and `FUN_800260e8` later indexes it with the placement byte at `record+0x08` to resolve a state/offset subrecord into `obj+0x8c/0x90`. +- `DAT_800758d4` is another per-type companion bank for the compound constructor family. `FUN_80024eec` stores it at `obj+0x84`, and `FUN_8002841c` queries it later using the object's `+0x94` selector, so it behaves like a variant table or companion lookup rather than raw map geometry. +- The key functions in that chain are now renamed in the live PSX Ghidra database: + - `FUN_800249f4` -> `psx_object_create_simple_record` + - `FUN_80024eec` -> `psx_object_create_compound_record` + - `FUN_80025ce8` -> `psx_reset_type_runtime_banks_from` + - `FUN_80025d68` -> `psx_object_advance_state_script` + - `FUN_800260e8` -> `psx_object_select_state_script` + - `FUN_8002841c` -> `psx_object_lookup_variant_entry` + - `FUN_8003917c` -> `psx_load_type_state_banks` + - `FUN_80044434` -> `psx_create_image_resource_from_descriptor` + - `FUN_80045ffc` -> `psx_cache_type_art_descriptor` +- the constructor/runtime chain is now clearer too: + - `psx_reset_type_runtime_banks_from` is a bank reset helper used during init/recycle paths; it clears `DAT_800758c4/c8/cc/d0/d4/d8` from the requested type index upward and is not the state interpreter itself. + - `psx_object_create_simple_record` and `psx_object_create_compound_record` are two placement constructors for different section-0 row layouts, but both index the same per-type runtime banks by type id before any final render-facing selection is made. + - `psx_create_image_resource_from_descriptor` turns the `DAT_800758d8` per-type descriptor into a renderable resource/header object; this is why `DAT_800758d8` should be read as an art/template descriptor bank, not as a whole-map tile layer. + - `psx_object_select_state_script` selects a state or animation subrecord from `DAT_800758cc` using a placement byte (`record+0x08` in the compound family), storing the resolved script/state pointer at `obj+0x8c/0x90` and the selector at `obj+0x9e`. + - `psx_object_advance_state_script` then interprets the active state script with sentinel/control values such as `0xffff`, `0xfffe`, `0xfffd`, `0xfffc`, and `0xfffb`, so the visible frame path is explicitly state-driven rather than just "type id -> one bundle". + - The current renderer-side consequence is important: section-0 word `u4` is no longer treated as a verified sprite-frame index. It is now carried forward as a state-selector candidate in exported scene metadata until the `DAT_800758cc/d4` path is decoded far enough to pick the right animation frame from executable evidence. + - Current strongest sentinel read: + - `0xfffe` dispatches `FUN_8004061c`, which is an audio/effect helper rather than a visible-frame selector. + - `0xfffd` is an in-script jump/re-anchor control that rewrites `obj+0x90` relative to the current script base. + - `0xfffc` switches `obj+0x8c/0x90` to another subsidiary script selected through the `DAT_800758cc` offset table. + - `0xfffb` also switches into a subsidiary script, but first scans forward to an in-script `0xfffd` marker before choosing the destination entry. + - Current best read of those sentinels: + - `0xffff` marks a terminal or restart control that re-anchors the script at `obj+0x8c` and raises object-state flags. + - `0xfffe` dispatches a side-effect helper (`FUN_8004061c`) using the following word as a parameter before advancing. + - `0xfffd`, `0xfffc`, and `0xfffb` switch into subsidiary scripts through the `DAT_800758cc` offset table rooted at `obj+0x88`. + - `psx_object_lookup_variant_entry` finally uses `obj+0x94` to look up a companion entry in `DAT_800758d4`, which means even after construction the art-facing choice is still mediated by per-type variant/state tables. +- This means the next PSX layers are now at least structurally separated: + - visible root descriptors (`section0_dispatch_roots`, legacy alias `region00`) + - visible bulk placement candidates (`section0_constructor_placements`, legacy alias `region01`) + - per-type art/template descriptors (`DAT_800758d8`) + - per-type simple-object component blocks (`DAT_800758d0`) + - per-type compound state-offset tables (`DAT_800758cc`) + - per-type compound variant tables (`DAT_800758d4`) + - the still-separate decompressed `0x3e00` level-state buffer (`DAT_8006769c`) +- The current renderer pass now records those banks explicitly as exported scene/state layers, while still only rendering the first two as visible scene items. +- Immediate map-viewer consequence: the current fallback art probe should be treated only as a diagnostic overlay for candidate bundle families. A workable renderer will need to recover the per-type `DAT_800758d8` descriptor mapping and the downstream `DAT_800758cc/d4` state+variant selection path before it can decide whether a section-0 placement should show world geometry, an animated object, or something non-map-facing like a portrait/talk asset. +- The next loader-side correction is now verified in the live cache too: the effective late `LSET*.WDL` `DAT_800758d8` candidate is not the earlier small-section heuristic, but a large late section whose working descriptor stream begins at an embedded `+0x38` offset. On retail map `9` that correction alone lifts `bundleMappedItemCount` from `0` to `111`, which is enough to restore real bundle-backed art for a first subset of types without reintroducing the disproven scan-order fallback. + The still-unresolved root-dispatch families remain instructive rather than contradictory. `0x0042` and `0x0049` still stay on placeholders after the bank-selection fix, but the same pass now decodes their `DAT_800758cc` state rows more cleanly: type `0x0042` carries three selector-targeted scripts (`0`, `1`, `2`) that all terminate through `0xffff`, while type `0x0049` carries a single selector-`0` script. So the remaining blocker for those roots is no longer "find any late template bank at all"; it is the deeper `DAT_800758cc/d4` state-to-visible-art bridge. + A first renderer-safe bridge landed even with that exporter gap still open: the verified `0x0050` state-script mapping (`selector 0..3 -> frame 0..3`) is now applied as a narrow fallback in the cache builder, and the rebuilt live map-9 scene now shows `type=80 state_selector=1 chosen_frame=1` instead of the old forced `chosen_frame=0`. Unresolved fallback placeholders are also now clamped to `opacity=0.45` in live scene output so the still-missing families stop visually overpowering the recovered real art. This remains intentionally scoped: the fallback frame map only covers the one family with direct executable-backed frame evidence, and the opacity clamp is diagnostic relief rather than a decoding claim. + The current draw split is clearer too. `FUN_80041378` is a three-stage render pass: + - stage 2: a second special-visible list drawn by `FUN_80041144` + - stage 3: HUD/overlay/icon primitives from `FUN_800416cc` +- That split matters for the map-viewer target: stages 1 and 2 remain relevant to missing world-facing content, while stage 3 is mostly front-end or overlay material and should not be mistaken for the missing half of the map. +- Stage 2 is now materially better understood and is no longer just a read-side observation: + - `FUN_80040f78` is the queue-builder for that pass. It projects an object with the same fixed-point world-to-screen math as `FUN_80040d44`, writes the final screen rectangle to `+0x20..+0x2e`, then appends the object to `DAT_80078b70` and increments `DAT_80067472`. + - `FUN_80041144` consumes that queue directly, iterating `DAT_80078b70[0 .. DAT_80067472)` and submitting sprite primitives through the same texture draw helpers as the main object pass. + - `FUN_80044fec` resets the queue each frame by clearing `DAT_80067472` after the top-level draw pass. + - So the stage-2 list is not UI/HUD noise and not a duplicate of the main clipped visible list. It is a distinct world-facing queued-object lane, which is now a concrete candidate explanation for part of the still-missing map content in the viewer. +- The immediate caller-side consequence matters too: + - `FUN_80040d44` remains the main clipped visible-list toggle, calling the stage-1 add/remove helpers when an object enters or leaves the screen. + - The recovered post-state-advance updater family now splits into five visible call sites: `0x80012b44`, `0x80013524`, `0x80013564`, `0x80013650`, and `0x80013778` all call `psx_object_advance_state_script`. + - Three of those sites then feed the main stage-1 projector path through `FUN_80040d44` (`0x80012b60`, `0x8001357c`, `0x800136d4`), while two feed the stage-2 queue-builder path through `FUN_80040f78` (`0x8001352c`, `0x80013780`). + - That exact `3` versus `2` split matters because it tightens the earlier claim: stage-2 membership is tied to a narrower runtime object/state branch after state advance, not to the decompressed substrate buffer alone and not to all state-advanced objects indiscriminately. +- One state-script sentinel is now functionally closed too: `0xfffe` dispatches `FUN_8004061c`, which is an audio/effect helper rather than a visible-frame selector. That shrinks the unknown sentinel set for the remaining `DAT_800758cc` script work. +- The main visible-list helpers are now also separated cleanly enough to stop treating them as a blocker: + - `FUN_8002d240` adds an object to the stage-1 `DAT_8006ad5c` visible-list array. + - `FUN_8002d35c` removes an object from that same array. + - `FUN_8002d59c` returns the sorted slice that `FUN_80041378` iterates for the stage-1 world-object pass. + - `FUN_8002d6f8` and `FUN_8002d778` act as refresh/rebucket/sort helpers over that main list. +- This is an important scope reduction for renderer work: the remaining missing world content is now less likely to be caused by misunderstanding the main stage-1 visibility array itself, and more likely to live in the separate stage-2 queued-object pass plus the still-unresolved `DAT_800758cc/d4` state-to-art path. + +Recovered next visible layer from the bulk placement family: + +- The structured `section0_constructor_placements` rows are no longer height-agnostic. The `FUN_80024eec` constructor reads its authored elevation from byte `+0x06` of the input record, which corresponds to the low byte of the current exported `u3` word for the accepted bulk-placement records. +- That byte is not just random payload on the accepted rows. Under the corrected section-0 scan, the same ladder generalizes across the whole rebuilt catalog instead of only the earlier `L0` subset. `LSET1/L0.WDL` still collapses to `11` distinct height values (`0, 2, 4, 10, 12, 14, 18, 20, 22, 24, 26`), and `LSET1/L1.WDL` now exposes `9` distinct levels with a `z` range of `0..32`. +- The PSX cache builder now uses that recovered `z` byte for `section0_constructor_placements` projection instead of forcing the whole bulk layer onto `z = 0`, while the top-level `section0_dispatch_roots` descriptor stream stays at `z = 0` until its own constructor-backed height source is proven. +- This is now the first PSX export pass in the viewer pipeline that produces visibly multi-layer whole-map candidates across the rebuilt retail catalog from executable-backed height data rather than from a single flattened candidate layer. + +## PSX Coordinate Model From Executable + +The current coordinate problem is no longer a renderer-only guess. The executable now closes the last projection step well enough to treat PSX placement as its own map-space model instead of as a PC-style direct world export. + +Key function evidence: + +- `FUN_800249f4` and `FUN_80024eec` are constructor paths that load authored coordinates into object fields `+0x3c`, `+0x40`, and `+0x44` as `16.16` fixed-point values. +- For the first family, the source record shape is now strong enough to describe directly: + - `u16` word at record `+0x08` -> object `+0x3c` as `value << 16` + - `u16` word at record `+0x0a` -> object `+0x40` as `value << 16` + - `u8` byte at record `+0x0c` -> object `+0x44` as `value << 16` +- `FUN_80040d44` and `FUN_80040f78` are the projection helpers that turn those fixed-point object coordinates into the per-object screen rectangle stored at `+0x20..+0x2e`. +- `FUN_80041458` and `FUN_80041144` then consume that already-built rectangle directly during draw submission; they do not derive screen position on the fly. + +Recovered projection model: + +- `+0x3e` and `+0x42` are not separate authored fields. They are the high `16`-bit halves of the fixed-point `x` and `y` values stored at `+0x3c` and `+0x40`. +- The runtime builds an intermediate screen anchor in fixed-point at `+0x78/+0x7c` from those world coordinates: + - `screen_anchor_x = y - x` + - `screen_anchor_y = 2 * z - (x + y) / 2` +- `FUN_80040d44` computes that anchor with the exact writes: + - `obj+0x78 = ((y_hi - x_hi) << 16)` + - `obj+0x7c = (obj_z * 2) - ((x_hi + y_hi) << 15)` +- The projection helper then subtracts the current camera anchor from `DAT_800678d4 + 0x3c/+0x40`, subtracts sprite-frame origin/size metadata from `FUN_8004513c`, `FUN_800451d0`, `FUN_80045014`, and `FUN_800450a8`, and writes the final visible rectangle into `+0x20..+0x2e`. + +What this means for the viewer: + +- the PSX map does not want the PC viewer's current synthetic `world.x/world.y/world.z` guess based directly on raw candidate words +- the most defensible renderer-side export target is now the runtime's own projected anchor or the equivalent fixed-point world tuple that reproduces the same `screen_anchor_x/screen_anchor_y` formulas +- any importer that treats the raw authored coordinates as if they were already PC-style isometric world coordinates will bunch objects together or smear them across the map because PSX uses a different projection basis +- the current cache builder no longer synthesizes PC-style world coordinates from final screen anchors; it now keeps the candidate PSX `x/y` words directly in exported scene items and applies the runtime projection basis separately during anchor generation + +Open parts that still matter: + +- this closes the final world-to-screen math, but it does not yet prove which raw `post_audio_region_01` or `post_audio_region_00` record family feeds each constructor path +- it also does not close the type/resource lookup that selects the correct bundle/frame through `DAT_800758cc/d0/d4/d8` +- palette override remains a separate unresolved control path layered on top of the now-understood projection math + +Immediate consequence for the next pass: + +- the next executable-guided decode step should map candidate authored record words directly onto constructor inputs, not onto PC-style scene coordinates +- once the correct record family is tied to `FUN_800249f4` or `FUN_80024eec`, the renderer can export either: + - the raw fixed-point PSX world tuple, plus a viewer-side reproduction of the runtime projection, or + - the runtime-equivalent projected anchor/rectangle directly for debug rendering +- the cache builder now uses the recovered projection basis and prefers the loader-backed record family, but the exact record-to-constructor link and the authoritative height lane still need proof before this can be called a solved map export + ## PSX Script / Usecode Equivalent Current status: diff --git a/docs/remorse-class-lift-index.md b/docs/remorse-class-lift-index.md index 4a80aba..d918c73 100644 --- a/docs/remorse-class-lift-index.md +++ b/docs/remorse-class-lift-index.md @@ -43,6 +43,7 @@ That set gives the high-level target, the current candidate families, the rebuil - [docs/entity-class-family-split.md](docs/entity-class-family-split.md): conservative split of the large `Entity` lane into base, projectile, debris, corpse/actor, and adjacent non-entity families. - [docs/entity-vm-runtime-owner-resource-layout.md](docs/entity-vm-runtime-owner-resource-layout.md): current runtime/helper/context ownership model for the VM lane. - [docs/presentation-callback-broker-layout.md](docs/presentation-callback-broker-layout.md): current object/lifecycle/vtable evidence for the `0x4588` presentation-state callback broker family. +- [docs/usecode-debugger-break-state-layout.md](docs/usecode-debugger-break-state-layout.md): current object/lifecycle/layout evidence for the dormant seg1408 debugger-state family. ### 4. Execution Checklists @@ -87,6 +88,34 @@ The future MCP endpoint sequence should follow the spec note rather than ad hoc 3. Add one more dedicated note for the callback/object lane around `0x4588` only if later caller evidence supports a stronger subsystem name than `PresentationCallbackBroker`. 4. Turn the first-class-authoring checklist into a completed execution log once the first real MCP batch lands. +## Current Live Authoring Snapshot + +The live `CRUSADER.EXE` class-authoring lane is no longer just a plan. + +Current authored `Remorse` classes in the active database are: + +- `EntityVmOwnerResource` +- `EntityVmRuntime` +- `EntityVmContext` +- `EntityVmSlotEntry` + +The VM lane is still the furthest along in actual Ghidra authoring. Recent live batches added the bounded `EntityVmSlotEntry` class owner plus more owned `EntityVmRuntime` methods (`GetSlotChunkPtrAtOffset`, `ReleaseSlotChunkRef`, `TryUnloadSlotChunk`, `DebugDumpSlotMemory`, `ApplyToMatchingOwnerRows`) rather than stopping at free-function naming. + +The latest signature-recovery pass also tightened two of those runtime methods materially: + +- `GetSlotChunkPtrAtOffset(runtime_farptr, slot_index, chunk_index, intra_chunk_offset)` now reads as a real slot-chunk accessor instead of a five-word anonymous wrapper. +- `ApplyToMatchingOwnerRows(runtime_farptr, slot_index_filter, chunk_index_filter)` now reads as a real iterator/filter helper instead of a split-word scratch signature. + +The next live batch pushed that further still: most of the `EntityVmRuntime` method cluster now carries an explicit 4-byte `EntityVmRuntime * this` in-session, including `Create`. The main remaining type gap inside that class is no longer the runtime object itself, but the exact far slot-entry pointer positions on `AcquireSlotForEntity` and `InitSlotOwnerBuffers`. + +That VM-side gap is now closed too: `AcquireSlotForEntity` returns `EntityVmSlotEntry *32` in `DX:AX`, `InitSlotOwnerBuffers` now accepts `EntityVmSlotEntry *32`, `EntityVmOwnerResource::{Create,Destroy}` now carry explicit 4-byte `this`, and the simple `EntityVmContext` lifecycle methods now do the same. + +The next family switch has also landed in the live database: `Remorse::UsecodeDebuggerBreakState` now exists as a real class owner with a provisional `0x2f2` datatype and a stronger method batch (`Create`, `MaybeBreakOnCurrentLine`, `BreakpointInsertSorted`, `BreakpointRemove`, `HasBreakpoint`, `CallstackPushFrame`, `CallstackPushEntry`, `CallstackPopEntry`, `EnableSingleStep`, `ClearStepState`, `CurrentEntryGetUnitName`). + +That debugger family is no longer just a top-level shell. The internal record shapes are now recovered and applied live well enough to treat the two tables as real fixed-size arrays in-session: breakpoint entries are `0x0b` bytes with `unit_name_inline[9] + line_number`, and callstack entries are `0x15` bytes with `unit_name_inline[9]` plus the currently safest trailing fields `source_stream_target_farptr`, `current_frame_payload_farptr`, and still-neutral `aux_farptr`. + +The VM lane also advanced one more selective step without overpromoting inheritance: `Remorse::EntityVmContext::CreateFromSlotIndex` now has a caller-backed mixed parameter pack (`owner_source_farptr`, `pitemno_farptr`, `mode_flags`, `slot_index`, `value_add_offset`, `intra_chunk_offset`, `ucparam_farptr`, `ucparamsize`) and an explicit far return restored in `AX:DX`, even though the current live endpoint still textualizes that repaired signature conservatively as plain `dword __cdecl`. + ## Bottom Line The current prep work is now large enough that it should be treated as one coordinated lane rather than scattered notes. diff --git a/docs/usecode-debugger-break-state-layout.md b/docs/usecode-debugger-break-state-layout.md new file mode 100644 index 0000000..dfed4b7 --- /dev/null +++ b/docs/usecode-debugger-break-state-layout.md @@ -0,0 +1,221 @@ +# Usecode Debugger Break-State Layout + +## Purpose + +This note captures the current class-lift-relevant evidence for the dormant seg1408 debugger-state object. + +The retail binary still appears to leave this family orphaned at runtime, but the object model itself is strong enough to justify explicit class authoring in Ghidra. + +Current working family name: + +- `UsecodeDebuggerBreakState` + +## Current Best Class-Level Read + +`UsecodeDebuggerBreakState` is a retained debugger object that owns: + +- a small breakpoint table +- current interpreted-line state +- single-step / break-armed flags +- a callstack entry stack +- a small vtable-backed break/notify surface used by the interpreter callback lane + +The compiled interpreter still calls into this object when the global debugger-state pointer is non-null, even though the retail binary no longer seems to instantiate it during normal play. + +## Strongest Evidence Anchors + +### Constructor + +#### `1408:0000` `Create` + +Current verified behavior: + +- allocates `0x2f2` bytes when `this == null` +- writes retail vtable offset `0x65ab` at object `+0x00` +- fills the breakpoint-entry region starting at `+0x04` with `0xffff` +- clears `+0x02`, `+0x75`, and `+0x7a` +- returns the object far pointer in `DX:AX` + +This is a real constructor-style path, not just a helper wrapper. + +### Break gate + +#### `1408:0053` `MaybeBreakOnCurrentLine` + +Current verified behavior: + +- stores the incoming interpreted line minus one at `+0x72` +- resolves the current unit-name pointer through `CurrentEntryGetUnitName` +- checks file+line breakpoints through `HasBreakpoint` +- dispatches through the object vtable when a break condition is met + +This is the strongest proof that the hidden debugger lane is object-based and that the interpreter-side callback still expects a live debugger object. + +### Breakpoint table helpers + +#### `1408:00dd` `BreakpointInsertSorted` + +Current verified behavior: + +- enforces a maximum of ten breakpoint entries +- scans the `0x0b`-byte breakpoint-entry table rooted at `+0x04` +- compares unit-name strings via the common string helper +- inserts a new `(unit_name, line_number)` pair into the sorted table + +#### `1408:01a5` `BreakpointRemove` + +Current verified behavior: + +- scans the same `0x0b`-byte breakpoint-entry table for an exact `(unit_name, line_number)` match +- compares the stored inline name bytes first, then the stored line word at entry `+0x09` +- compacts the remaining entries downward when a match is found +- decrements `breakpoint_count` + +#### `1408:029e` `HasBreakpoint` + +Current verified behavior: + +- scans the same breakpoint-entry table +- compares the requested line number against entry `+0x09` +- compares the requested unit-name pair against the stored name bytes +- returns a boolean-style `uint` in `AX` + +### Callstack helpers + +#### `1408:02f5` `CallstackPushFrame` + +Current verified behavior: + +- computes the current callstack-entry base as `this + 0x7c + callstack_depth * 0x15` +- copies an inline unit-name buffer into entry `+0x00` +- enforces a maximum visible unit-name length of eight characters plus terminator +- stores three trailing far-pointer/state dwords at `+0x09`, `+0x0d`, and `+0x11` +- increments `callstack_depth` + +#### `1408:03b0` `CallstackPushEntry` + +Current verified behavior: + +- uses `+0x7a` as the current callstack depth +- acts as a thinner wrapper over `CallstackPushFrame` when only the inline unit-name payload matters +- increments the depth and asserts when it reaches `0x1e` + +#### `1408:03f7` `CallstackPopEntry` + +Current verified behavior: + +- decrements `+0x7a` +- asserts if the depth underflows below zero + +### Step-state helpers + +#### `1408:0419` `EnableSingleStep` + +- clears `+0x76/+0x78` +- sets `+0x75 = 1` + +#### `1408:0432` `ClearStepState` + +- clears `+0x74` +- clears `+0x75` + +### Current-entry name accessor + +#### `1408:0444` `CurrentEntryGetUnitName` + +Current verified behavior: + +- returns null when `callstack_depth <= 0` +- otherwise returns a far pointer to the current callstack entry's inline unit-name buffer + +## Recovered Entry Schemas + +The live debugger-state model is now strong enough to split the old table blobs into concrete fixed-size entry records. + +### `UsecodeDebuggerBreakpointEntry` (`0x0b` bytes) + +| Offset | Current name | Confidence | Current meaning | +|---|---|---|---| +| `+0x00..+0x08` | `unit_name_inline[9]` | High | Inline unit-name buffer, consistent with eight visible characters plus terminator. | +| `+0x09` | `line_number` | High | Breakpoint line number compared by `BreakpointInsertSorted`, `BreakpointRemove`, and `HasBreakpoint`. | + +### `UsecodeDebuggerCallstackEntry` (`0x15` bytes) + +| Offset | Current name | Confidence | Current meaning | +|---|---|---|---| +| `+0x00..+0x08` | `unit_name_inline[9]` | High | Inline unit-name buffer for the active frame. | +| `+0x09` | `source_stream_target_farptr` | Medium | Far pointer derived from the interpreter source-stream lane plus one fetched word in the only verified caller. | +| `+0x0d` | `current_frame_payload_farptr` | Medium | Far pointer to the current frame payload at `frame_base + 0x04` in the only verified caller. | +| `+0x11` | `aux_farptr` | Low | Trailing auxiliary far pointer slot; still zero in the only verified caller. | + +## Current Working Layout + +The live datatype `/Remorse/UsecodeDebuggerBreakState` now exists in-session with the currently safest anchors: + +| Offset | Current name | Confidence | Current meaning | +|---|---|---|---| +| `+0x00` | `vtable_offset` | High | Retail debugger-state vtable offset `0x65ab`. | +| `+0x02` | `breakpoint_count` | High | Count of `0x0b`-byte breakpoint entries. | +| `+0x04..+0x71` | `breakpoint_entries[10]` | High | Ten inline `UsecodeDebuggerBreakpointEntry` records. | +| `+0x72` | `current_line` | High | Current interpreted line minus one. | +| `+0x74` | `break_armed` | Medium | Break/armed flag cleared by `ClearStepState`. | +| `+0x75` | `single_step_enabled` | High | Single-step flag set by `EnableSingleStep`. | +| `+0x76/+0x78` | `step_state_lo/hi` | Medium | Step-state pair cleared by `EnableSingleStep`. | +| `+0x7a` | `callstack_depth` | High | Current callstack depth. | +| `+0x7c..+0x2f1` | `callstack_entries[30]` | High | Thirty inline `UsecodeDebuggerCallstackEntry` records. | + +## Live Ghidra Authoring Status + +Verified first live class batch landed on 2026-04-06. + +- Created class owner `Remorse::UsecodeDebuggerBreakState`. +- Created `/Remorse/UsecodeDebuggerBreakpointEntry` (`0x0b`) and `/Remorse/UsecodeDebuggerCallstackEntry` (`0x15`) in the live data-type manager. +- Rewrote `/Remorse/UsecodeDebuggerBreakState` in-session so the old blob regions are now explicit `UsecodeDebuggerBreakpointEntry[10]` and `UsecodeDebuggerCallstackEntry[30]` arrays at the recovered offsets. +- Moved the main seg1408 helpers under the class owner: + - `1408:0000` -> `Create` + - `1408:0053` -> `MaybeBreakOnCurrentLine` + - `1408:00dd` -> `BreakpointInsertSorted` + - `1408:01a5` -> `BreakpointRemove` + - `1408:029e` -> `HasBreakpoint` + - `1408:02f5` -> `CallstackPushFrame` + - `1408:03b0` -> `CallstackPushEntry` + - `1408:03f7` -> `CallstackPopEntry` + - `1408:0419` -> `EnableSingleStep` + - `1408:0432` -> `ClearStepState` + - `1408:0444` -> `CurrentEntryGetUnitName` +- Tightened the live method signatures to explicit object-style forms, including: + - `UsecodeDebuggerBreakState * __cdecl16far Create(UsecodeDebuggerBreakState * this, dword init_spec)` + - `void __cdecl16far MaybeBreakOnCurrentLine(UsecodeDebuggerBreakState * this, word current_line)` + - `byte __cdecl16far BreakpointInsertSorted(UsecodeDebuggerBreakState * this, dword unit_name_farptr, word line_number)` + - `void __cdecl16far BreakpointRemove(UsecodeDebuggerBreakState * this, dword unit_name_farptr, word line_number)` + - `uint __cdecl16far HasBreakpoint(UsecodeDebuggerBreakState * this, dword unit_name_farptr, word line_number)` + - `void __cdecl16far CallstackPushFrame(UsecodeDebuggerBreakState * this, dword unit_name_farptr, dword source_stream_target_farptr, dword current_frame_payload_farptr, dword aux_farptr)` + - `byte __cdecl16far CallstackPushEntry(UsecodeDebuggerBreakState * this, dword unit_name_farptr)` + - `void __cdecl16far CallstackPopEntry(UsecodeDebuggerBreakState * this)` + - `void __cdecl16far EnableSingleStep(UsecodeDebuggerBreakState * this)` + - `void __cdecl16far ClearStepState(UsecodeDebuggerBreakState * this)` + - `dword __cdecl16far CurrentEntryGetUnitName(UsecodeDebuggerBreakState * this)` +- Added decompiler comments on the breakpoint and callstack helpers so the recovered inline-record layout is visible in-session even before every field is formally typed. +- Added decompiler comments on the only verified `Interpreter_NextUsecodeOp` caller of `CallstackPushFrame`, which confirms the current live read of the three trailing callstack dwords: + - `source_stream_target_farptr` is source-stream-derived from the interpreter `+0xd6/+0xd8` lane plus one fetched word + - `current_frame_payload_farptr` is current-frame-derived from the `frame_base + 0x04` lane + - `aux_farptr` is still zero in the only verified caller + +## Current Cautions + +- The retail instantiation path still appears absent; no normal caller currently reaches `Create` in the unpatched retail binary. +- The record boundaries inside both tables are now landed in the live datatype, and two of the three trailing callstack dwords now have caller-backed structural names. The exact gameplay role behind those two far pointers is still only partly recovered. +- `init_spec` on `Create` and `unit_name_farptr` on the breakpoint/callstack helpers are intentionally neutral names; the live signatures are object-correct, but the payload semantics should stay conservative. + +## Best Next Moves + +1. Identify the real gameplay semantics of `source_stream_target_farptr` and `current_frame_payload_farptr` from the interpreter-side caller lanes before promoting subsystem-specific names. +2. Identify the vtable callback slots used by `MaybeBreakOnCurrentLine` and decide whether one or two additional methods belong on the class owner. +3. Cross-check the seg1408 class note against the interpreter callback site at `1418:04b5` so the dormant-orphan lifecycle remains explicit in the live notes. +4. Decide whether `aux_farptr` should remain neutral or can be promoted after one more caller or consumer pass. + +## Bottom Line + +`UsecodeDebuggerBreakState` is now past the “interesting orphan subsystem” stage and into real class territory. + +The live database now has a bounded debugger-state class with a constructor, breakpoint gate, breakpoint table helpers, callstack helpers, explicit recovered `0x0b` / `0x15` entry schemas, and step-state helpers, even though the retail game still appears to leave that object dormant. \ No newline at end of file diff --git a/ghidra_mcp_wishlist.md b/ghidra_mcp_wishlist.md index fe2d9bc..0ad7cfe 100644 --- a/ghidra_mcp_wishlist.md +++ b/ghidra_mcp_wishlist.md @@ -1,342 +1,83 @@ # Ghidra MCP Wishlist -This file records concrete gaps in the current Ghidra MCP workflow. -Update it whenever a task requires PyGhidra or another local-only fallback because MCP lacks the needed operation. +This file records concrete MCP gaps hit during Crusader workflow passes. -For each new entry, keep the format short: -- Missing capability -- Current fallback -- Why it matters in this repo -- Proposed MCP endpoint or behavior +Rules for keeping it useful: +- Put only unresolved work in `Remaining TODOs`. +- Move implemented or source-fixed items to `Done / Implemented`. +- Keep each remaining item short: missing capability, fallback, why it matters, proposed behavior, latest status. -## Current Wishlist +## Remaining TODOs -### POST Body Contract Gap Hit During Runtime Prototype Repair (2026-04-05) +### Class-Lift Typing Live Parity -- Missing capability: POST endpoints only accept form-urlencoded key/value parameters; direct JSON bodies fail as if required parameters were omitted. -- Current fallback: use bridge helpers or manual form-encoded POSTs when testing endpoints such as `set_function_prototype(...)` directly. -- Why it matters: MCP clients, ad hoc terminal tests, and future automation naturally try JSON first for structured payloads, especially on newer class-lift and prototype endpoints. -- Proposed MCP behavior: accept both `application/x-www-form-urlencoded` and `application/json` on POST endpoints, or return a structured unsupported-content-type error that explicitly says the route only accepts form parameters. -- Status update (2026-04-05): local plugin `parsePostParams(...)` still only splits `key=value&...` bodies and ignores JSON payloads entirely, which is why direct JSON POSTs looked like missing-parameter failures during the `EntityVmRuntime::Create` repair. -- Status update (2026-04-05, local fork): plugin `parsePostParams(...)` now accepts both form-urlencoded bodies and JSON object bodies across POST routes. Unsupported POST bodies now fail early with an explicit `unsupported-content-type` parser error instead of silently degrading into missing-parameter behavior. +- Missing capability: end-to-end live-session parity for storage-aware `this` typing on 16-bit NE methods whose current storage does not match the default pointer storage the binder would choose. +- Current fallback: use local PyGhidra with `DYNAMIC_STORAGE_ALL_PARAMS`, or move methods with `set_function_class(...)` and defer final `this` typing/manual prototype cleanup. +- Why it matters: `EntityVmContext` lifecycle methods and `EntityVmRuntime::Create` still need the live MCP path to behave like the verified local PyGhidra repair flow. +- Proposed MCP behavior: `set_function_this_type(...)` and `apply_class_layout(...)` should reliably fall back to dynamic storage in-session for these 16-bit cases, while preserving structured per-method warnings instead of aborting the batch. +- Latest status (2026-04-06): local PyGhidra confirmed that `1420:0eec`, `1420:10b6`, `1420:10da`, `1420:1162`, `1420:118f`, and `1420:1278` accept `EntityVmContext * this` cleanly via `DYNAMIC_STORAGE_ALL_PARAMS`. The live storage-aware path now also accepts explicit `/Remorse/EntityVmRuntime *32`, `/Remorse/EntityVmOwnerResource *32`, `/Remorse/EntityVmContext *32`, and `/Remorse/EntityVmSlotEntry *32` signatures in-session once the exact `*32` datatype has first been resolved into the program data-type manager. The remaining live gap is now mostly about deeper mixed-width parameter packs like `1420:0eec CreateFromSlotIndex`, not the previously blocked 4-byte object-pointer cases themselves. -### Live PyGhidra Write Gap Hit During Runtime Repair Pass (2026-04-05) +### Storage-Aware Prototype Live Verification -- Missing capability: constrained live PyGhidra write execution through MCP when Ghidra was started with Python enabled. -- Current fallback: keep read-only inspection in live MCP via `run_readonly_script(...)`, but close the GUI and drop back to local project-open PyGhidra for write-side repairs such as custom-storage prototype fixes and datatype edits. -- Why it matters: the runtime class-lift batch had to leave the live session and reopen the project locally just to repair one 16-bit function signature and one allocator-helper callee, even though the live Ghidra instance could already host Python scripts. -- Proposed MCP behavior: add a narrowly scoped live write-script or transaction endpoint family that runs against the active writable program with explicit safety limits, dry-run support where possible, and machine-friendly transaction results. -- Status update (2026-04-05): the local fork can already probe and run live read-only Python when Ghidra starts with PyGhidra enabled, so the remaining gap is write-side exposure and safety policy rather than Python availability itself. -- Status update (2026-04-05, local fork): local plugin and bridge now expose `run_write_script(script_path|script_text, dry_run?)` plus the alias route `run_transaction_script`. The implementation reuses explicit write-target selectors, validates inline or file-backed scripts against a write-policy denylist, wraps execution in a single MCP-managed transaction, reports machine-friendly status/output, and surfaces `write_script_*` capability fields from `get_runtime_capabilities()`. The remaining gap is finer-grained safety policy and live workflow verification, not basic write-side exposure. -- Status update (2026-04-06, VM class-lift pass): direct bridge `run_write_script(...)` still returned `404 No context found for request` against the active `CRUSADER.EXE` GUI session even with explicit target selectors, so the `EntityVmContext` datatype plus the slot-entry/runtime prototype batch still had to fall back to closed-project local PyGhidra. The remaining gap is now active-session context binding for the write-script route, not route availability alone. -- Status update (2026-04-06, local fork hardening): plugin explicit-target binding now normalizes Windows `project_dir` casing/separators, infers missing `project_dir` / `project_name` from the active program when possible, and fills the matching `folder_path` from the active domain file before trying to reopen a target. Bridge `run_write_script(...)` now retries the `run_transaction_script` alias on `404` or `No context found for request`, reducing mixed-build false negatives while live-session verification continues. -- Status update (2026-04-06, live context-typing retry): the trivial dry-run probe for `run_write_script(...)` still returned `404 No context found for request` against the active `CRUSADER.EXE` session both with implicit active-program targeting and with explicit `project_dir` / `project_name` / `folder_path` / `program_name` selectors. The route is still not usable as an in-session fallback for the `EntityVmContext` typing pass. +- Missing capability: confirmed live-session parity for the newest storage-aware prototype fixes on 16-bit NE repair cases. +- Current fallback: if the active GUI session is on an older plugin build, reload the plugin; if parity still fails, use local PyGhidra or manually compensate when testing stack offsets / calling conventions. +- Why it matters: `1000:42e2` and `1420:1499` are the known proof cases for explicit return storage, stack-word parameter modeling, and 16-bit far calling conventions. +- Proposed MCP behavior: `set_function_prototype_storage(...)` should accept bare `stack:` offsets in the same hex-style form used in current workflow notes and should preserve exact calling-convention tokens such as `__cdecl16far` before falling back to lossy legacy normalization. +- Latest status (2026-04-06): the reloaded live plugin now reaches the real storage-aware implementation in-session on both proof cases, and explicit `AX:DX` return storage survives correctly on `1000:42e2` and `1420:1499`. The remaining live parity issue is now narrower: `calling_convention='__cdecl16far'` still normalizes those proof-case applies to plain `__cdecl`, but direct live `run_write_script(...)` calls can immediately restore `__cdecl16far`, which proves the live database accepts the exact convention token and leaves the endpoint-side normalization/deployment path as the remaining gap. -### Class-Lift Typing Gap Hit During VM Runtime Pass (2026-04-05) +### Live Metadata / Read-Target Verification -- Missing capability: a storage-aware class-layout or `this`-typing path for 16-bit NE methods whose current function storage does not match the default pointer storage the binder tries to apply. -- Current fallback: create/update the class namespace and datatype, then move methods individually with `set_function_class(...)` and leave `this` typing/manual prototype cleanup for later. -- Why it matters: the current Remorse class-lift workflow can land ownership cleanly for `EntityVmRuntime`, but `apply_class_layout(...)` failed on the runtime lifecycle cluster with `Failed to apply this type: Storage size does not match data type size: 2` even though the same binder succeeded for `EntityVmOwnerResource`. -- Proposed MCP behavior: let `apply_class_layout(...)` either skip/soft-fail `this` typing per method with structured results, or accept an explicit storage/calling-convention override for `this` so 16-bit segmented/custom-storage methods can still be class-bound and partially typed in one pass. -- Status update (2026-04-05, later MCP-upgrade pass): the upgraded tool surface now allows direct `set_function_class(...)` moves for additional `EntityVmRuntime` helpers and `set_function_this_type(...)` succeeded on `1420:1601 Destroy` when forced to `this_storage=farptr`, but `1420:1499 Create`, `1420:1536 InitSlots`, and `1420:1575 ReleaseSlots` still fail with the same storage-size mismatch, so the gap is narrower but not resolved. -- Status update (2026-04-05, local fork): `set_function_this_type(...)` now treats `this_storage` as a real storage strategy hint instead of always reusing the old first-parameter storage. For existing parameters it tries preserved custom storage first only when the caller asked to preserve/current storage, then falls back to `DYNAMIC_STORAGE_ALL_PARAMS` when the preserved storage is incompatible with the requested `this` type. `apply_class_layout(...)` now records per-method typing failures as structured warnings instead of aborting the entire batch, and bridge method payloads can carry per-method `this_storage` and `calling_convention` overrides. -- Status update (2026-04-06, VM class-lift pass): after landing `/Remorse/EntityVmContext` and the first slot-entry prototype batch, local PyGhidra could collapse `1420:1536 InitSlots` and `1420:1575 ReleaseSlots` to direct `EntityVmRuntime * this`, but `1420:1499 Create` still reintroduced hidden `__return_storage_ptr__` corruption whenever the split-word far runtime pointer was collapsed to a typed `this`. The open gap is now mostly `Create` plus any future 16-bit constructors/factories with the same far-pointer/custom-storage shape. -- Status update (2026-04-06, live context-typing retry): the old `apply_class_layout(...)` dry-run null failure for `/Remorse/EntityVmContext` no longer reproduces, but the real live write path still behaves like the older storage-preserving build. Actual `apply_class_layout(...)` and direct `set_function_this_type(...)` calls on `1420:10b6`, `1420:10da`, `1420:1162`, `1420:118f`, and `1420:1278` all still fail with `Storage size does not match data type size: 2`, so the open gap is now specifically live deployment parity for the dynamic-storage fallback rather than dry-run binder coverage. -- Status update (2026-04-06, local PyGhidra confirmation): after closing the GUI and running the local `tools.pyghidra_crusader` script path, the same context lifecycle entries (`1420:0eec`, `1420:10b6`, `1420:10da`, `1420:1162`, `1420:118f`, `1420:1278`) all accepted `EntityVmContext * this` cleanly via `DYNAMIC_STORAGE_ALL_PARAMS`. That confirms the typing model is valid and the remaining gap is live-session deployment parity, not the class layout itself. +- Missing capability: fully verified live-session parity for selector-aware reads and metadata helpers in mixed-build or partially refreshed GUI sessions. +- Current fallback: bridge alias retries, explicit-target normalization, and manual project-note cross-checks when a live session still behaves like an older plugin build. +- Why it matters: Crusader work routinely needs side-by-side reads across `/CRUSADER.EXE`, `/es/CRUSADER.EXE`, `/Writable/...`, and other project entries without changing the active Ghidra tab. +- Proposed MCP behavior: `list_project_programs(...)`, `get_runtime_capabilities(...)`, `get_callers(...)`, and other selector-aware read helpers should bind reliably to the requested or active target and return structured unsupported-state output instead of raw context failures. +- Latest status (2026-04-06): the local fork already includes alias fallbacks and Windows path/folder normalization for explicit-target matching. Remaining work is live-session verification after plugin refresh, not additional local source coverage. -### 16-bit Prototype And Hidden Return-Storage Gap Hit During Runtime Repair (2026-04-05) +## Done / Implemented In Local Fork -- Missing capability: a semantics-preserving prototype/storage endpoint for 16-bit NE functions that can set explicit parameter storage, explicit return storage, and avoid parser-induced hidden `__return_storage_ptr__` rewrites. -- Current fallback: inspect the broken caller plus its direct callees, then use local PyGhidra to normalize callee prototypes and apply custom storage manually. -- Why it matters: `1420:1499 Remorse::EntityVmRuntime::Create` kept throwing `Low-level Error: Symbol $$undef00000006 extends beyond the end of the address space` until the shared allocator helper at `1000:42e2` was repaired from a pointer-return signature that decompiled with a hidden return-storage parameter. -- Proposed MCP behavior: expose a storage-aware prototype/update endpoint that accepts explicit parameter and return storage, plus optionally a decompiler-health check or warning when a candidate prototype would inject hidden return storage into a 16-bit caller chain. -- Status update (2026-04-05): parser-string prototype updates alone were not sufficient here; the stable repair required explicit `AX:DX` return storage on `1000:42e2` and split-stack-word modeling for the runtime far pointer on `1420:1499`. -- Status update (2026-04-05, later MCP-upgrade pass): the new live `run_write_script(...)` path gives MCP a constrained way to perform these repairs inside the active writable session, but there is still no first-class storage-aware prototype endpoint that models explicit return/parameter storage declaratively. This wishlist item remains open. -- Status update (2026-04-06, local fork): local plugin and bridge now expose `set_function_prototype_storage(...)` plus the alias `set_storage_aware_prototype(...)`. The endpoint accepts declarative `return_type`, `return_storage`, and ordered `parameters` lines (`name|type|storage`), supports explicit target selectors, applies custom return/parameter storage in one transaction, and reports a warning when the resulting signature still contains hidden `__return_storage_ptr__` state. -- Status update (2026-04-06, live in-session verification): the checked-in Java source now wires both `/set_function_prototype_storage` and `/set_storage_aware_prototype` to the storage-aware implementation, but the active GUI session still does not match that build. Direct live POSTs to `/set_function_prototype_storage` returned HTTP 200 with the old legacy body `failed: set_function_prototype ... Function prototype is required`, while the alias route `/set_storage_aware_prototype` still returned `404 No context found for request`. So the live session still cannot exercise the new explicit-storage modeling in-session, and this remains a deployment/runtime parity gap rather than a source-level endpoint absence. +### Transport And Runtime -### Live MCP Issues Hit During Spanish Cheat Pass (2026-03-26) +- POST endpoints now accept both `application/json` and `application/x-www-form-urlencoded` request bodies. Unsupported POST payloads fail early with `unsupported-content-type` instead of degrading into missing-parameter errors. +- `get_runtime_capabilities()` reports readonly/write-script capability state and `run_readonly_script(...)` returns structured unsupported-state output when Python support is unavailable. +- `run_write_script(...)` and alias `run_transaction_script(...)` are implemented with dry-run support, explicit target selectors, a write-policy denylist, and machine-friendly transaction results. +- Bridge runtime helpers retry compatible aliases on `404` / `No context found for request` for mixed-build live sessions. -- Missing capability: working `search_bytes(...)` requests against the currently opened program. -- Current fallback: `read_region(...)`, `get_data_uses(...)`, `search_instructions(...)`, and manual/xref-driven narrowing inside `/es/CRUSADER.EXE`. -- Why it matters: the Spanish-cheat question specifically needed a direct full-memory search for the English `jassica16` scan-code table and any plausible replacement sequence. -- Proposed MCP behavior: `search_bytes(...)` should honor the active program context by default and return a machine-friendly empty-hit result when no matches exist, not `HTTP 404 No context found for request`. +### Explicit Targeting And Project Access -- Missing capability: reliable explicit target selection on read/query endpoints in the live server session. -- Current fallback: repo notes plus manual project `.prp` metadata inspection after `read_region(...)` and `get_function_by_address(...)` ignored explicit root-vs-`/es` selectors and still resolved against the active Spanish program. -- Why it matters: this repo routinely needs side-by-side comparisons between `/CRUSADER.EXE`, `/es/CRUSADER.EXE`, `/Writable/...`, and other project entries without changing the active Ghidra tab. -- Proposed MCP behavior: all selector-aware read endpoints should actually bind to the requested `project_dir` / `project_name` / `folder_path` / `program_name`, or return a structured target-resolution failure instead of silently reading the active program. +- Explicit write targeting is implemented for edit flows such as `apply_program_edit_plan(...)` and `patch_bytes_and_reanalyze(...)` with deterministic save behavior. +- Selector-aware read/query endpoints now accept `project_dir`, `project_name`, `folder_path`, and `program_name` and reuse the same target-resolution layer as write flows. +- Target matching now normalizes Windows path casing and slash style and can infer missing project selectors from the active domain file when appropriate. +- `list_project_programs(...)` plus alias `project_programs` is implemented and returns machine-friendly folder/program inventory. -- Missing capability: consistent context handling for project/runtime metadata helpers in the live server session. -- Current fallback: direct `get_project_access_info()` plus workspace-side `.prp` reads after `list_project_programs(...)`, `get_callers(...)`, `compare_functions(...)`, and `get_runtime_capabilities()` returned `404 No context found for request` during an otherwise healthy active-program session. -- Why it matters: these are the exact helper endpoints needed to validate which program is active, enumerate comparison targets, and reason about whether a failure is a real analysis result or an MCP/session problem. -- Proposed MCP behavior: metadata helpers should either work whenever an active program exists or return structured unsupported-state details, not raw 404 context failures. -- Status update (2026-03-26, later Spanish pass): the refreshed live server still returned `404 No context found for request` for `get_runtime_capabilities(...)` and `get_callers(...)` during an active `/es/CRUSADER.EXE` session, so this is still a live deployment or routing problem, not just an earlier-session artifact. -- Status update (2026-04-05, class-lift pass): after reloading the updated plugin, `get_project_access_info(...)` and the new class-lift write routes were reachable in the active `CRUSADER.EXE` session, but `list_project_programs(...)` still returned `404 No context found for request`, so the metadata-helper context issue is not fully resolved. -- Status update (2026-04-05, local bridge hardening): bridge `list_project_programs(...)` now retries the legacy `/project_programs` alias whenever the live server answers with `404` or `No context found for request`, which should smooth mixed-build sessions while the remaining live metadata routing issue is verified after redeploy. -- Status update (2026-04-06, local fork hardening): bridge `get_runtime_capabilities(...)` now retries the `/runtime_capabilities` alias on `404` or `No context found for request`, and plugin explicit-target matching no longer depends on exact Windows path casing or slash style when deciding whether an already-open program satisfies the request. This should reduce false context failures in mixed-build live sessions, though full deployment verification is still pending. +### Analysis, Inspection, And Xrefs -### Open Gaps Found During Hidden Usecode Debugger Patch Batch (2026-03-24) +- Function boundary repair helpers are implemented: `create_function_by_address(...)`, `delete_function_by_address(...)`, and `get_function_containing(...)`. +- Arbitrary memory/code inspection helpers are implemented: `read_region(...)`, `disassemble_region(...)`, `get_instruction_window(...)`, `search_instructions(...)`, and `get_data_uses(...)`. +- `search_bytes(...)` is implemented with `??` wildcards and machine-friendly hit output. +- Caller/xref recovery is improved via `get_callers(...)`, and `get_xrefs_to(...)` / `get_xrefs_from(...)` return typed reference kinds plus containing-function metadata. +- `get_symbol_at(address)` now uses direct routes when present and bridge-side legacy fallbacks when the live process is older. -- Missing capability: write-capable project/program selection for MCP edit operations. -- Current fallback: local PyGhidra `run-script` plus `read-region` against `--project-dir K:\ghidra\Crusader_Decomp --project-name Crusader --folder-path /Writable --program-name CRUSADER-PATCHED.EXE`. -- Why it matters: retail NE patch work in this repo must sometimes modify and save `/Writable/CRUSADER-PATCHED.EXE` with the GUI closed, while current MCP write flows depend on the active Ghidra session/program context. -- Proposed MCP addition: add bridge-exposed target selectors (`project_dir`, `project_name`, `folder_path`, `program_name`) for write endpoints, backed by plugin support to open the requested project file, apply `patch_bytes_and_reanalyze` or edit-plan writes, and save deterministically. -- Status update (2026-03-24): local fork now accepts optional `project_dir`, `project_name`, `folder_path`, and `program_name` selectors on `apply_program_edit_plan` and `patch_bytes_and_reanalyze`; explicit targets are opened through `GhidraProject`, written, saved deterministically, and then released. -- Status update (2026-03-24, follow-up): explicit target resolution now reuses an already-open matching program when possible and otherwise opens a writable domain object directly; MCP no longer opens explicit targets in read-only mode for edit operations. +### Batch Edits And Comparison Tools -### Open Gaps Found During Current 0x4588 Pass (2026-03-21) +- Batch helpers are implemented: `set_comments(...)`, `set_decompiler_comments(...)`, `rename_functions_by_address(...)`, and `apply_program_edit_plan(...)` with dry-run support. +- Reanalysis helpers are implemented: `reanalyze_region(...)`, `patch_bytes_and_reanalyze(...)`, and `analyze_function_boundaries(...)`. +- Cross-program comparison helpers are implemented: `compare_regions(...)`, `compare_strings(...)`, and `compare_functions(...)`. +- `port_symbols(...)` now ports verified names/comments between programs with provenance text and explicit source/target selectors. -- Missing capability: usable read-only scripting in the live MCP/Ghidra session. -- Current fallback: terminal-side Python and manual MCP inspection windows after `run_readonly_script` returned `Ghidra was not started with PyGhidra. Python is not available`. -- Why it matters: one-off structure probes and byte-pattern scans are still common during EUSECODE and overlap work, and they are much cleaner as constrained in-process reads than as external heuristics. -- Proposed MCP addition: expose runtime capability state for `run_readonly_script` and either guarantee a working in-process script engine or return a machine-friendly unsupported-state response early. -- Status update (2026-03-24): local fork now exposes `get_runtime_capabilities()` with readonly-script probe state and `run_readonly_script()` returns structured `status`/`reason`/`detail` output early when Python support is unavailable in the live session. -- Status update (2026-03-24, follow-up): `open_current_program_readonly()` is now intentionally disabled and returns an unsupported-state response so MCP does not create accidental read-only program instances in normal workflow. +### Class / Namespace / OO Recovery -- Status update (2026-03-21): the current live plugin process still returns HTTP 404 for direct symbol routes (`/get_symbol_at`, `/symbol_at`) in this chat session, but bridge `get_symbol_at(address)` now avoids raw 404s by falling back to compatible legacy endpoints and returning deterministic symbol-state output (for example `0x844` -> `symbol=`). -- Remaining gap: reload/redeploy the updated plugin build so direct symbol routes are present in the live process; bridge fallback now covers older live builds in the meantime. -- Implemented now: - - `get_xrefs_to(address)` / `get_xrefs_from(address)` with typed ref kinds (`call`, `read`, `write`, `jump`, `other`) plus containing-function metadata. - - tolerant `set_function_prototype` retries for legacy calling-convention tokens (for example `__cdecl16far`) and returns an accepted template example on parse/apply failure. - - `rename_data(address, new_name)` now renames or creates the primary symbol at any valid address and returns the resolved symbol metadata instead of `Rename data attempted`. - - `get_symbol_at(address)` returns the primary symbol state at an address so label changes can be verified directly without depending on decompiler refresh timing. - - `get_symbol_at(address)` now resolves the active program on the Swing thread, falls back to the visible/open program when the current-program pointer is transiently unavailable, and the bridge retries the compatible `/symbol_at` alias if a stale server route returns `404 No context found for request`. - - bridge `get_symbol_at(address)` now probes additional legacy aliases (`getSymbolAt`, `symbolAt`, `get_symbol`) and, if symbol routes are absent, derives symbol state from legacy endpoints (`get_function_by_address`, paged `data`) so callers receive machine-friendly output instead of a raw 404. -- Local bridge audit (2026-03-21): `get_xrefs_to` / `get_xrefs_from` wrappers are already present in `K:\mcp\GhidraMCP\bridge_mcp_ghidra.py`; if a client still does not surface them, that is a client/tool-refresh issue rather than a missing local-fork endpoint. +- Namespace and class authoring helpers are implemented: `create_namespace(...)`, `create_class(...)`, `list_namespace_members(...)`, `move_symbol_to_namespace(...)`, and `set_function_class(...)`. +- Vtable and struct helpers are implemented: `analyze_vtable(...)`, `create_or_update_struct(...)`, `create_or_update_vtable(...)`, and alias coverage such as `build_vtable` / `set_this_type`. +- `set_function_this_type(...)` supports storage-strategy hints and `apply_class_layout(...)` now soft-fails per-method typing with structured warnings instead of aborting the whole batch. -## Implemented In Local GhidraMCP Fork (2026-03-21) +### Prototype And Storage Modeling -Added endpoints in `K:\mcp\GhidraMCP\src\main\java\com\lauriewired\GhidraMCPPlugin.java` and tools in `K:\mcp\GhidraMCP\bridge_mcp_ghidra.py`: +- The storage-aware prototype endpoint is implemented as `set_function_prototype_storage(...)` with alias `set_storage_aware_prototype(...)`. +- The endpoint accepts declarative `return_type`, `return_storage`, ordered parameter lines (`name|type|storage`), explicit target selectors, varargs, and machine-friendly warnings when hidden `__return_storage_ptr__` state is still present. +- Source-level fixes landed on 2026-04-06 for the two known live correctness bugs: + - `stack:` storage is now parsed before generic Ghidra deserialization so workflow-style bare stack offsets are interpreted consistently. + - exact calling-convention tokens are tried before legacy normalization so 16-bit far conventions such as `__cdecl16far` are not needlessly collapsed to plain `__cdecl` when the exact token is accepted. -- Function boundary repair: - - `create_function_by_address(entry, name, body_start, body_end, comment?)` - - `delete_function_by_address(entry)` - - `get_function_containing(address)` -- Arbitrary code and memory inspection: - - `read_region(start, end)` - - `disassemble_region(start, end)` - - `get_instruction_window(address, before_count, after_count)` - - `search_instructions(query, mode=text|operand|address, limit?)` - - `get_data_uses(address, include_operand_scans=true, limit?)` -- Batch and transactional edits: - - `set_comments(batch)` - - `set_decompiler_comments(batch)` - - `rename_functions_by_address(batch)` - - `apply_program_edit_plan(plan, dry_run=false)` -- Reanalysis and repair helpers: - - `reanalyze_region(start, end)` - - `patch_bytes_and_reanalyze(start, bytes, comment?)` - - `analyze_function_boundaries(start, end)` -- Read-only project access and scripting: - - `get_project_access_info()` - - `get_runtime_capabilities()` - - `open_current_program_readonly(version=-1, make_current=true)` - - `run_readonly_script(script_path|script_text)` with a constrained token denylist policy +### Historical Notes -- Explicit write targeting: - - optional `project_dir`, `project_name`, `folder_path`, `program_name` selectors on `apply_program_edit_plan(...)` - - optional `project_dir`, `project_name`, `folder_path`, `program_name` selectors on `patch_bytes_and_reanalyze(...)` - -Batch encoding used by the current bridge: - -- `set_comments` and `set_decompiler_comments`: list of `(address, comment)` pairs. -- `rename_functions_by_address`: list of `(address, new_name)` pairs. -- `apply_program_edit_plan`: one action per line with `|` separators, for example: - - `create_function_by_address|000c:1234|name|000c:1234|000c:1260|note` - - `delete_function_by_address|000c:1234` - - `rename_function_by_address|000c:1234|new_name` - - `set_disassembly_comment|000c:1234|comment text` - - `set_decompiler_comment|000c:1234|comment text` - -Notes on read-only coverage: - -- `open_current_program_readonly` opens a read-only program object for the currently loaded domain file. -- Project-switch/open-by-path is still not implemented; MCP still operates on the active Ghidra GUI project context. - -### Function boundary repair - -- Missing capability: create a function at an explicit entry with an explicit body start/end. -- Current fallback: local PyGhidra `create-function` and JSON repair plans. -- Why it matters: boundary repair is a recurring part of this project, especially for overlapped or truncated raw functions. -- Proposed MCP addition: `create_function_by_address(entry, name, body_start, body_end, comment?)`. - -- Missing capability: delete an incorrect auto-created function. -- Current fallback: local PyGhidra `delete-function`. -- Why it matters: bad auto-analysis often blocks decompilation of adjacent real functions. -- Proposed MCP addition: `delete_function_by_address(entry)`. - -- Missing capability: get the function containing an arbitrary address. -- Current fallback: local PyGhidra `get-function-containing`. -- Why it matters: no-function windows and overlap investigations depend on quickly mapping instruction hits back to owning functions. -- Proposed MCP addition: `get_function_containing(address)`. - -### Arbitrary code and memory inspection - -- Missing capability: read raw bytes from an arbitrary address range in program memory. -- Current fallback: local PyGhidra `read-region`. -- Why it matters: some important sites are real code bytes that are not yet part of any function object. -- Proposed MCP addition: `read_region(start, end)` returning bytes and a compact hex view. - -- Missing capability: dump nearby instructions around an arbitrary address even when no function exists there. -- Current fallback: custom read-only PyGhidra scripts such as `pyghidra_plans/dump_instruction_windows.py`. -- Why it matters: the `0x4588` investigation depended on inspecting instruction windows in no-function regions. -- Proposed MCP addition: `disassemble_region(start, end)` or `get_instruction_window(address, before_count, after_count)`. - -- Missing capability: scan all instructions for a literal operand or address token. -- Current fallback: custom PyGhidra scripts such as `scan_4588_instruction_uses.py`. -- Why it matters: normal xref APIs can miss useful operand-text hits in partially analyzed regions. -- Proposed MCP addition: `search_instructions(query, mode=text|operand|address, limit?)`. - -- Missing capability: robust data-address xrefs that include operand-based uses even when the reference manager has none. -- Current fallback: instruction-text scans and manual disassembly windows. -- Why it matters: globals like `0x4588` can be heavily used before formal references exist in the database. -- Proposed MCP addition: `get_data_uses(address, include_operand_scans=true)`. - -### Batch and transactional edits - -- Missing capability: apply a small transactional edit plan containing function removals, function creations, renames, and comments. -- Current fallback: local PyGhidra `apply-plan` with JSON. -- Why it matters: boundary repair work is safer when a verified batch can be replayed atomically. -- Proposed MCP addition: `apply_program_edit_plan(plan)` with dry-run support. - -- Missing capability: batch comment creation for a verified address set. -- Current fallback: repeated single-address comment calls or PyGhidra plan files. -- Why it matters: reverse-engineering batches often produce several related evidence comments at once. -- Proposed MCP addition: `set_comments(batch)` and `set_decompiler_comments(batch)`. - -- Missing capability: batch rename-by-address for a small verified set. -- Current fallback: repeated `rename_function_by_address` calls or local plan files. -- Why it matters: verified raw-import ports often land in short, evidence-backed batches. -- Proposed MCP addition: `rename_functions_by_address(batch)`. - -### Reanalysis and repair helpers - -- Missing capability: re-disassemble or reanalyze a small address range after patching bytes or changing function boundaries. -- Current fallback: local scripted repair passes. -- Why it matters: the far-call fixup workflow and boundary recovery both depend on deterministic reanalysis of touched ranges. -- Proposed MCP addition: `reanalyze_region(start, end, options?)`. - -- Missing capability: patch a small byte range and immediately re-disassemble affected instructions. -- Current fallback: local PyGhidra repair scripts. -- Why it matters: the NE far-call fixup pass was a major workflow improvement and is exactly the sort of task MCP should eventually support. -- Proposed MCP addition: `patch_bytes_and_reanalyze(start, bytes, comment?)`. - -- Missing capability: detect likely bad function overlaps or candidate function starts in a small range. -- Current fallback: manual repair plus custom PyGhidra probing. -- Why it matters: overlap repair is one of the main reasons the workflow still has to leave MCP. -- Proposed MCP addition: `analyze_function_boundaries(start, end)` returning overlap warnings and candidate entries. - -### Read-only project access and scripting - -- Missing capability: open a locked project read-only or query a specified project clone directly from MCP. -- Current fallback: local PyGhidra against an unlocked temporary project clone. -- Why it matters: the GUI often owns the main project while read-only inspection still needs to continue. -- Proposed MCP addition: read-only project selection/open options for all analysis endpoints. - -- Missing capability: run a small read-only script for one-off inspections that do not justify a permanent MCP endpoint yet. -- Current fallback: local PyGhidra `run-script --read-only`. -- Why it matters: several repo workflows start as one-off analysis helpers before they prove worth productizing. -- Proposed MCP addition: a constrained `run_readonly_script(script_text|script_path)` endpoint with explicit safety limits. - -### Migrated entries from `ghidra-mcp_wishlist.md` - -Short, concrete gaps hit during live Crusader work. Each entry records what MCP lacked, what fallback was needed, and what a useful MCP feature should look like. - -## Open Gaps (migrated) - -### Byte-pattern search across program memory - -- Status: implemented in local fork (2026-03-26) -- Missing MCP capability: search raw bytes or byte patterns across the current program's mapped segments / address spaces. -- Fallback used: manual `read_region` sweeps plus local Python over the MCP HTTP bridge to scan live Spanish `CRUSADER.EXE` memory for the `jassica16` scan-code table. -- Useful MCP feature: - - `search_bytes(pattern, start?, end?, segment_filter?, max_hits?)` - - accepts hex byte patterns with optional wildcards - - returns exact hit addresses plus nearby hex context -- Why it matters: this would have closed the Spanish cheat-sequence question directly inside MCP instead of forcing ad hoc local scripting. -- Status update (2026-03-26): local fork now exposes `search_bytes(pattern, start?, end?, segment_filter?, max_hits?)` in both the Java plugin and Python bridge; it accepts `??` wildcards, scans mapped memory blocks, and returns machine-friendly hit lines with block names and nearby hex context. - -### Reliable caller/xref recovery for local call sites - -- Status: implemented in local fork (2026-03-26) -- Missing MCP capability: reliable function-call xrefs for near/local calls inside the active program. -- Fallback used: manual `search_instructions` and instruction-window inspection because `get_function_xrefs` did not surface some obvious local call sites in the Spanish keyboard/helper cluster. -- Useful MCP feature: - - improve `get_function_xrefs` so it includes near calls, far calls, tail-call-style jumps, and thunk references consistently - - or add `get_callers(address_or_name, include_near=true, include_far=true, include_jumps=true)` -- Why it matters: tracing helper chains around hidden key-sequence code is slower and less reliable when local callers have to be reconstructed by text search. -- Status update (2026-03-26): local fork now exposes `get_callers(target, include_near=true, include_far=true, include_jumps=true, limit?)`, combining reference-manager hits with instruction-flow scans so local near-call sites show up even when plain xrefs are incomplete; `get_function_xrefs` now reuses the same caller recovery path. - -### Cross-program reads inside the same Ghidra project - -- Status: implemented in local fork (2026-03-26) -- Missing MCP capability: read/query another program or assembly in the same project without switching the active program first. -- Fallback used: indirect comparison against repo notes, workspace-side files, and ad hoc local scripts instead of querying `/CRUSADER.EXE`, `/es/CRUSADER.EXE`, `/Writable/...`, or other domain files side by side through MCP. -- Useful MCP feature: - - allow explicit target selectors on all read/query endpoints, not only write endpoints - - example: `read_region(start, end, project_dir?, project_name?, folder_path?, program_name?)` - - same for strings, functions, xrefs, data uses, decompile, disassemble, symbol lookup, and segment listing -- Why it matters: live localized-build comparisons and writable-copy verification should not require changing the active Ghidra tab just to inspect another program. -- Status update (2026-03-26): read/query endpoints in the local fork now accept optional explicit target selectors (`project_dir`, `project_name`, `folder_path`, `program_name`) and reuse the same target-resolution layer as write flows; this now covers method/class listings, segments, imports/exports, namespaces, data items, function lookup/listing, decompile/disassembly, symbol lookup, regions, instruction scans, strings, xrefs, and data-use queries. - -### Cross-project / cross-program compare tooling - -- Status: implemented in local fork (2026-03-26) -- Missing MCP capability: first-class compare operations between two programs in the same project or across projects. -- Fallback used: manual note-to-note comparison, address math, and repeated per-program queries. -- Useful MCP feature: - - `compare_regions(left_program, left_range, right_program, right_range, mode=bytes|words|disasm|strings)` - - `compare_strings(left_program, right_program, filter?)` - - `compare_functions(left_program, left_addr_or_name, right_program, right_addr_or_name, mode=signature|disasm|decompile|xrefs)` - - machine-readable output with address pairs, similarity score, and differing bytes/instructions/strings -- Why it matters: this would make English vs Spanish / Remorse vs Regret / raw vs live NE comparisons much faster and less error-prone. -- Status update (2026-03-26): local fork now exposes `compare_regions(...)`, `compare_strings(...)`, and `compare_functions(...)` with left/right explicit target selectors; outputs are machine-friendly and include comparison mode, similarity score, and capped difference samples for byte/word, disassembly, string, signature, decompile, and xref views. - -### Port renames/comments/symbol facts between programs - -- Status: implemented in local fork (2026-03-26) -- Missing MCP capability: apply verified names/comments from one program to another program with explicit provenance instead of re-entering them one by one. -- Fallback used: manual rename/comment batches plus external notes to carry mapping provenance. -- Useful MCP feature: - - `port_symbols(source_program, target_program, mappings, apply=names|comments|both, provenance_comment_template?)` - - support direct address maps, segment-relative maps, and user-supplied CSV/JSON mapping tables - - dry-run mode showing collisions and ambiguous targets -- Why it matters: porting verified English or raw-import findings into Spanish or live NE targets is a recurring workflow. -- Status update (2026-03-26): local fork now exposes `port_symbols(mappings, apply=names|comments|both, provenance_comment_template?, dry_run?)` with `source_*` and `target_*` selectors; the bridge accepts a verified list of source/target address pairs and the plugin ports names plus PRE/EOL comments with optional provenance text and explicit-target save support. - -### Project inventory / browse endpoint - -- Status: implemented in local fork (2026-03-26) -- Missing MCP capability: list project folders and available programs through MCP. -- Fallback used: repo-side assumptions and local tooling; the current MCP read tools expose only the active program cleanly. -- Useful MCP feature: - - `list_project_programs(project_dir?, project_name?, folder_path?, recursive=true)` - - returns folder path, program name, read-only/writable/versioned state, and whether it is currently open -- Why it matters: comparing or porting across programs is awkward without a discoverable inventory of assemblies already in the Ghidra project. -- Status update (2026-03-26): local fork now exposes `list_project_programs(project_dir?, project_name?, folder_path?, recursive=true)` plus a `project_programs` alias; it walks project folders and returns machine-friendly program inventory lines with folder path, program name, content type, read-only/versioned flags, and current-open state. - -### Class / namespace authoring for C++ lifting - -- Missing MCP capability: create and manage Ghidra class or namespace symbols, then move existing functions under those owners as methods. -- Current fallback: manual Ghidra GUI edits in the Symbol Tree or one-off local scripts outside the normal MCP workflow. -- Why it matters: the Remorse binary already shows repeated ctor/dtor patterns, stable vtable roots, and class-like object families, but the current MCP workflow can only rename flat functions. That blocks a disciplined shift from procedural naming toward grouped C++-style ownership. -- Proposed MCP addition: - - `create_namespace(name, parent_path?, kind=namespace|class)` - - `move_symbol_to_namespace(symbol_address_or_name, namespace_path, new_name?)` - - `set_function_class(function_address, class_path, method_name?, this_param_name?, calling_convention?)` - - machine-friendly responses that include the final symbol path and any rename collisions. -- Status update (2026-04-05): local fork now exposes `create_namespace(...)`, `list_namespace_members(...)`, `move_symbol_to_namespace(...)`, and `set_function_class(...)` in both the Java plugin and Python bridge. The implementation supports explicit target selectors, dry-run moves, collision policies (`fail|keep_existing|rename_incoming`), and compatibility aliases (`create_class`, `move_function_to_class`). - -### Vtable / OO recovery helpers for class reconstruction - -- Missing MCP capability: first-class helpers for identifying vtables, attaching function slots to candidate classes, and materializing class/instance layouts from evidence-backed data. -- Current fallback: manual note collation from decompiler/disassembly output plus ad hoc datatype work in the GUI. -- Why it matters: the repo already has enough evidence to start lifting major families into C++ classes, but a recompilable source path needs more than renamed functions. It needs reproducible vtable maps, `this`-pointer typing, field layouts, inheritance guesses, and explicit provenance for each class model. -- Proposed MCP addition: - - `analyze_vtable(address, slot_count?, namespace_path?)` - - `create_or_update_struct(name, size?, fields)` - - `set_function_this_type(function_address, struct_name, this_storage=stack|register|farptr)` - - `apply_class_layout(class_path, instance_struct, vtable_struct?, methods)` - - optional dry-run output showing inferred slots, unresolved targets, and conflicting field/size evidence. -- Status update (2026-04-05): local fork now exposes `analyze_vtable(...)`, `create_or_update_struct(...)`, `create_or_update_vtable(...)`, `set_function_this_type(...)`, and `apply_class_layout(...)` in both layers. Struct and vtable authoring accept line-encoded field/slot batches from the bridge, `set_function_this_type(...)` updates the first parameter to a typed `this` pointer while preserving storage when possible, and `apply_class_layout(...)` batches namespace moves plus `this` typing with dry-run support. Compatibility aliases now also cover `set_this_type` and `build_vtable`. \ No newline at end of file +- If a future pass hits a new MCP gap, add it under `Remaining TODOs` and move it to `Done / Implemented In Local Fork` once the local source and bridge support are both in place. \ No newline at end of file diff --git a/plan-mid.md b/plan-mid.md index 6f86fb0..dd43c1f 100644 --- a/plan-mid.md +++ b/plan-mid.md @@ -15,7 +15,7 @@ Detailed completed analysis belongs in the files under `docs/`, not in this plan ## Progress Snapshot -Latest verified batch: [docs/combat-dat.md](docs/combat-dat.md) now closes the shipped combat-tactic data file as a documentation target instead of leaving it as a scratch-note reference. Current best read is that all local Remorse/Regret variants share one identical `14`-record `COMBAT.DAT`, the live NE database now already has the right tactic/process field anchors (`combatDatTacticPtr`, `combatDatTacticCurOffset`, `combatDatBlockNo`, `tacticNo`) plus setup helpers, and the shipped opcode subset is now decoded into a full human-readable tactic catalog using direct binary parsing plus the ScummVM Crusader attack-process interpreter as a reference model. +Latest verified batch: [docs/psx/psx.md](docs/psx/psx.md) now tightens the PSX render-side model enough that a renderer change is easier to justify. The late `LSET*.WDL` `DAT_800758d8` candidate still decodes from the embedded `+0x38` parse start and still recovers the first `111` real map-9 art bindings, while the executable pass still closes the second world-facing render lane: `FUN_80040f78` is the stage-2 queued-object projector/builder for `DAT_80078b70` / `DAT_80067472`, `FUN_80041144` consumes that queue, and `FUN_80044fec` resets it each frame. The new renderer-side bridge is intentionally narrow but now verified in live output too: because the generic raw-file `DAT_800758cc/d0/d4` export is still missing, the cache builder temporarily seeds the executable-backed `0x0050` selector map (`0..3 -> frame 0..3`), and retail map `9` now exports `type=80 state_selector=1 chosen_frame=1` instead of the old forced frame `0`. Alongside the earlier palette-source closure (`obj+0xa0` is the original authored record pointer), the `DAT_80067720` event/control-list evidence, and the `DAT_8006769c` persistent `0x3e00` substrate/state evidence, this means the remaining map-viewer gap is now better bounded: part of the missing world content likely lives in the separate stage-2 queued-object pass, while the rest still depends on restoring the generic `DAT_800758cc/d4` export path and then closing state-to-art selection for unresolved families such as `0x0042` and `0x0049`. - Overall useful decompilation progress: about 58% - Reasonable uncertainty band: about 55% to 63% @@ -56,7 +56,7 @@ Latest verified batch: [docs/combat-dat.md](docs/combat-dat.md) now closes the s - The owner-loaded body/range model is no longer speculative. Class-selection uses `class_id + 2`, header/subentry math matches extracted corpus output, and concrete body windows for `NPCTRIG`, `EVENT`, and related families are now verified. - The map-renderer/documentation lane now has a stronger shape/controller crosswalk. Recent closures include `CRUMORPH`, `NPC_ONLY`, `WATCHNS`, `WATCHEW`, `CRYOBOX`, `CRAZYEW`, `CRAZYNS`, `VIDEOBOX`, `PANELEW`, `GENERATR`, and cross-game `DEATHBOX`, with viewer-side links kept conservative where actor-side state is still runtime-only. - The command-line/startup lane is much tighter across both games: `-warp [x y z]`, `-mapoff`, `-egg`, startup teleporter selection, and the `-u` EUSECODE root override all now have practical behavior models instead of folklore-level descriptions. -- The PSX lane is no longer just side inventory. Retail/pre-alpha bundle loading, mission-briefing/passcode structure, and the reduced-content pre-alpha disc now have dedicated notes and enough stable naming to support future targeted passes. +- The PSX lane is no longer just side inventory. Retail/pre-alpha bundle loading, mission-briefing/passcode structure, the reduced-content pre-alpha disc, and now the retail map object's last projection stage all have dedicated notes and enough stable naming to support future targeted passes. - The Remorse class-lift preparation lane now has a usable document cluster: overall plan, candidate inventory, endpoint spec, ABI constraints, family notes for `EntityDispatchEntry` and `SpriteNode`, a conservative `Entity` family split, a VM runtime/owner-resource layout note, a compatibility-header draft, and one grouped resume index. - The same class-lift prep lane is now more execution-ready: the `0x4588` broker family has its own focused object note, the toolchain story has a dedicated fingerprint-evidence note, and there is now a concrete first-batch class-authoring checklist ready for the first MCP-backed namespace/struct/vtable pass. - The live Remorse VM class-lift lane also recovered from a decompiler breakage in `Remorse::EntityVmRuntime::Create`: the root cause was a hidden-return-storage allocator helper signature at `1000:42e2`, `Create` now decompiles again, and the provisional `/Remorse/EntityVmSlotEntry` datatype now exists with the stable `+0x1e..+0x24` buffer-pair fields named. @@ -68,6 +68,19 @@ Latest verified batch: [docs/combat-dat.md](docs/combat-dat.md) now closes the s - The matching MCP gap is also clearer now: the old `apply_class_layout` dry-run null failure no longer reproduces for `/Remorse/EntityVmContext`, but the real write path still behaves like the older storage-preserving build. Actual `apply_class_layout` and direct `set_function_this_type` calls on the context lifecycle methods still fail with `Storage size does not match data type size: 2`, and live `run_write_script(...)` still returns `404 No context found for request` even with explicit target selectors. - Closing the GUI and dropping to the local PyGhidra fallback then landed the blocked context typing work cleanly: `CreateFromSlotIndex`, `FreeBuffer`, `SyncGlobalValueAndDispatch`, `Destroy`, `Save`, and `Load` now all carry `EntityVmContext * this` as their first parameter in `CRUSADER.EXE`, which confirms the newer dynamic-storage rewrite is sound even though the live MCP session still is not taking it. - The next live verification pass tightened two details. First, the new checked-in storage-aware prototype endpoint still is not the build currently serving the active GUI session: direct live POSTs to `/set_function_prototype_storage` still answered with the legacy `set_function_prototype` failure body, and the alias route still returned `404 No context found for request`. Second, the direct callers of `CreateFromSlotIndex` still mostly consume the result as a base process object, so the current conservative `UsecodeProcess *` return should stay in place until the inheritance-aware datatype story is explicit. +- The refreshed live MCP build moved that forward materially: `set_function_prototype_storage(...)` now reaches the real storage-aware implementation in-session and the active-program `run_write_script(...)` path now executes instead of failing with `404`. The new blocker is narrower and more concrete: bare `stack:` offsets at `10` and above currently need `0x` prefixes to preserve the intended stack slots, `__cdecl16far` still normalizes to plain `__cdecl`, and `Create` still cannot collapse to a single `EntityVmRuntime * this` because the datatype itself still resolves to a 2-byte pointer size. +- The same live batch also tightened the slot-entry class model: `/Remorse/EntityVmSlotEntry` now carries `match_key_farptr`, `owner_chunk_count`, and `owner_data_base` in addition to the earlier owner-buffer and chunk-state tails, which makes `InitSlotOwnerBuffers`, `AcquireSlotForEntity`, and `EnsureSlotChunkLoaded` read more like object code and less like anonymous offset arithmetic. +- The next live batch tightened the adjacent helper map too: the old unnamed `1420:1d72`, `1420:1d8d`, and `1420:1e17` helpers are now `entity_vm_runtime_get_slot_chunk_ptr_at_offset`, `entity_vm_runtime_release_slot_chunk_ref`, and `entity_vm_runtime_try_unload_slot_chunk`, which makes the slot-entry lifecycle around load, refcount release, and conditional unload materially easier to navigate. +- The latest live batch turned that helper lane into a small shared record model: `/Remorse/EntityVmLoadedChunkRecord` now carries the stable `next_*`, `saved_chunk_*`, `slot_index`, and `chunk_index` anchors, `entity_vm_runtime_try_unload_slot_chunk` now takes `EntityVmLoadedChunkRecord *` and returns `byte` in `AL`, and `entity_vm_runtime_apply_to_matching_owner_rows` now iterates over a typed loaded-chunk record instead of anonymous stack-pair scratch state. +- The adjacent interpreter-side lane is slightly tighter too: local helper `1418:003c` is now `interpreter_pop_saved_farptr`, and the only verified `Interpreter_NextUsecodeOp` release path at `1418:3330` is commented as a save/restore boundary around `entity_vm_runtime_release_slot_chunk_ref` instead of being left as anonymous stack traffic. +- The live class-authoring state moved forward too: `Remorse::EntityVmSlotEntry` now exists as a real class owner in `CRUSADER.EXE`, `CreateOrClear` moved under it with an explicit `this` parameter and `AX` pointer return, and the runtime-local chunk helpers plus owner-row iterator/debug path now sit under `Remorse::EntityVmRuntime` instead of Global. +- The next live pass improved the runtime class surface further: `GetSlotChunkPtrAtOffset` now carries the recovered `runtime_farptr/slot_index/chunk_index/intra_chunk_offset` signature and still returns its far pointer in `DX:AX`, while `ApplyToMatchingOwnerRows` now carries the recovered `runtime_farptr/slot_index_filter/chunk_index_filter` signature and still returns its boolean in `AL`. +- The latest live pass removed the old runtime-wide 2-byte-`this` bottleneck for this cluster: `Create`, `InitSlots`, `ReleaseSlots`, `DebugDumpSlotMemory`, `ReleaseSlotChunkRef`, `GetSlotChunkPtrAtOffset`, `TryUnloadSlotChunk`, `ApplyToMatchingOwnerRows`, and `EnsureSlotChunkLoaded` now all accept an explicit 4-byte `EntityVmRuntime * this` through `/Remorse/EntityVmRuntime *32` custom storage in-session. The remaining live type gap is narrower again: exact `/Remorse/EntityVmSlotEntry *32` return/parameter typing still fails on `AcquireSlotForEntity` and `InitSlotOwnerBuffers`, so those positions are currently held as neutral `dword` placeholders instead of prettier but broken slot-entry pointer types. +- That slot-entry gap is now closed too, and the pointer cleanup widened beyond the runtime core: `AcquireSlotForEntity` now returns `EntityVmSlotEntry *32`, `InitSlotOwnerBuffers` now accepts `EntityVmSlotEntry *32`, `EntityVmOwnerResource::{Create,Destroy}` now carry explicit 4-byte `this`, and the simple `EntityVmContext` lifecycle methods now do the same. The main remaining VM signature outlier is `CreateFromSlotIndex`, whose argument pack still needs caller-side recovery rather than just pointer-width cleanup. +- The next family switch also landed: `Remorse::UsecodeDebuggerBreakState` now exists as a real class owner with a `0x2f2` provisional datatype plus a first method batch for construction, breakpoint gating, breakpoint table helpers, callstack helpers, and step-state helpers. +- That debugger batch is already tighter than the initial shell: `1408:01a5` is now verified as `BreakpointRemove`, `1408:02f5` is now verified as `CallstackPushFrame`, breakpoint entries are recovered as `0x0b` inline-name-plus-line records, and callstack entries are recovered as `0x15` inline-name-plus-three-dword records even though the trailing dword semantics remain open. +- The next pass landed the debugger struct rewrite in-session too: `/Remorse/UsecodeDebuggerBreakpointEntry`, `/Remorse/UsecodeDebuggerCallstackEntry`, and the updated `/Remorse/UsecodeDebuggerBreakState` array layout now exist live instead of only in notes, and the only verified `CallstackPushFrame` caller now narrows those three trailing dwords to `source_stream_target_farptr`, `current_frame_payload_farptr`, and still-neutral `aux_farptr`. +- `CreateFromSlotIndex` is no longer a raw anonymous pack either: the live signature now separates `owner_source_farptr`, `pitemno_farptr`, `mode_flags`, `slot_index`, `value_add_offset`, `intra_chunk_offset`, `ucparam_farptr`, and `ucparamsize`, with explicit `AX:DX` return storage restored even though the endpoint still textualizes the function conservatively as plain `dword __cdecl`. ### Areas That Are No Longer Live Priorities @@ -101,7 +114,8 @@ Latest verified batch: [docs/combat-dat.md](docs/combat-dat.md) now closes the s 4. Tighten the higher-slot wrapper ladder around `0005:3115..31da` so future event-label promotion depends on compiled caller behavior instead of external tables. 5. Tighten the seg006 masked-helper caller chains so the local state-selector/value family can be tied to concrete gameplay subsystems. 6. Classify the paired seg070 loops behind `entity_vm_runtime_owner_resource_create`, especially which temporary buffers and record schemas each family populates. -7. Stay on the Remorse VM class-lift batch while the repaired runtime lane is warm: redeploy or otherwise verify the live storage-aware prototype and storage-fallback class-layout builds so future context and slot-entry typing can stay in-session, then push `/Remorse/EntityVmSlotEntry` one step deeper through `EnsureSlotChunkLoaded` and adjacent slot helpers, keep `CreateFromSlotIndex` on the conservative `UsecodeProcess *` return until the base-process inheritance model is explicit, and keep the storage-aware `this` investigation focused on `Create` specifically now that `InitSlots` / `ReleaseSlots` and the broader context lifecycle are already typed. +7. Stay on the Remorse VM class-lift batch while the repaired runtime lane is warm: use the now-recovered `CreateFromSlotIndex` caller pack to decide whether any remaining scalar positions deserve stronger typedefs, but keep the return semantically conservative until the base-process inheritance model is explicit enough to justify a prettier live return type. +8. Continue the `UsecodeDebuggerBreakState` family from the now-landed live array layout: identify the exact gameplay semantics of `source_stream_target_farptr` and `current_frame_payload_farptr`, then decide whether `aux_farptr` should remain neutral and whether any further seg1408 or interpreter-side helpers belong under that class before widening into another family. 8. In the local GhidraMCP upgrade lane, add support for dual POST body decoding (`application/json` plus form-urlencoded) and a constrained live write-side PyGhidra endpoint family so future custom-storage/type repairs can stay inside the active MCP session when Python is enabled. 9. Promote additional ledger rows directly from already-verified docs and live comments, especially where segments already deserve `Foothold`, `Partial`, or `Deep`; the new seg029 step-aware sweep batch, seg031 queue-release batch, and seg090 movement-helper batch should be the immediate template. 10. If the VM lane stalls, revisit `000e:ffb0` from the now-better-constrained video/audio caller windows and try to recover an adjacent non-overlapped helper before attempting broad boundary repair. diff --git a/tools/psx_export_map_type_probe.py b/tools/psx_export_map_type_probe.py new file mode 100644 index 0000000..1297379 --- /dev/null +++ b/tools/psx_export_map_type_probe.py @@ -0,0 +1,586 @@ +from __future__ import annotations + +import argparse +import json +import math +import zlib +from dataclasses import dataclass +from pathlib import Path + + +DEFAULT_INPUT = Path(r"k:\ghidra\Crusader_Decomp\out\psx_wdl\L0\post_audio_region_01_00007448_paired_u16x6.json") +DEFAULT_OUTPUT_ROOT = Path(r"k:\ghidra\crusader_map_viewer\map_renderer\site\data") +DEFAULT_MAP_ID = 0 +DEBUG_SCENE_VERSION = "psx-region01-type-placement-probe-v1" +ALLOWED_U5 = {0x20, 0x22, 0x30} + + +@dataclass(frozen=True) +class PlacementRecord: + side: str + row_index: int + record_index: int + u0: int + u1: int + u2: int + u3: int + u4: int + u5: int + + +@dataclass(frozen=True) +class PlaceholderSprite: + type_id: int + variant: int + lane: int + atlas_id: str + file_name: str + shape_code: int + width: int + height: int + origin_x: int + origin_y: int + display_name: str + description: str + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Export a PSX LSET region-01 type-placement probe into the current map viewer static data." + ) + parser.add_argument("--input", type=Path, default=DEFAULT_INPUT, help="Path to paired_u16x6 JSON export") + parser.add_argument("--output-root", type=Path, default=DEFAULT_OUTPUT_ROOT, help="Map viewer site/data root") + parser.add_argument("--game-id", default="psx-remorse", help="Catalog game id to write") + parser.add_argument("--game-label", default="No Remorse (PSX)", help="Catalog game label") + parser.add_argument("--map-id", type=int, default=DEFAULT_MAP_ID, help="Numeric map id for the static scene") + parser.add_argument( + "--map-label", + default="PSX LSET1/L0 Type Placement Probe", + help="Human-readable map label for the catalog entry", + ) + parser.add_argument( + "--screen-scale", + type=int, + default=1, + help="Multiplier applied to the raw region-01 coordinate delta when placing probe sprites", + ) + return parser.parse_args() + + +def load_records(path: Path) -> list[PlacementRecord]: + payload = json.loads(path.read_text(encoding="utf-8")) + records: list[PlacementRecord] = [] + for row in payload.get("rows", []): + row_index = int(row["index"]) + for side_index, side in enumerate(("left", "right")): + values = row.get(side) + if not isinstance(values, dict): + continue + record = PlacementRecord( + side=side, + row_index=row_index, + record_index=row_index * 2 + side_index, + u0=int(values.get("u0", 0)), + u1=int(values.get("u1", 0)), + u2=int(values.get("u2", 0)), + u3=int(values.get("u3", 0)), + u4=int(values.get("u4", 0)), + u5=int(values.get("u5", 0)), + ) + if is_structured_candidate(record): + records.append(record) + records.sort(key=lambda record: (record.u2, record.u1, record.u5, record.u4, record.u0, record.record_index)) + return records + + +def is_structured_candidate(record: PlacementRecord) -> bool: + if record.u0 >= 0x200: + return False + if record.u1 == 0 and record.u2 == 0: + return False + if record.u1 >= 0x4000 or record.u2 >= 0x4000: + return False + if record.u3 > 0x20 or record.u4 > 0x04: + return False + if record.u5 not in ALLOWED_U5: + return False + return True + + +def chunk(tag: bytes, payload: bytes) -> bytes: + crc = zlib.crc32(tag) + crc = zlib.crc32(payload, crc) & 0xFFFFFFFF + return len(payload).to_bytes(4, "big") + tag + payload + crc.to_bytes(4, "big") + + +def write_png_rgba(path: Path, rgba: bytes, width: int, height: int) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + stride = width * 4 + scanlines = bytearray() + for row in range(height): + start = row * stride + scanlines.append(0) + scanlines.extend(rgba[start : start + stride]) + ihdr = width.to_bytes(4, "big") + height.to_bytes(4, "big") + bytes((8, 6, 0, 0, 0)) + idat = zlib.compress(bytes(scanlines), level=9) + png = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", idat) + chunk(b"IEND", b"") + path.write_bytes(png) + + +def clamp_channel(value: int) -> int: + return max(0, min(255, value)) + + +def color_from_key(type_id: int, variant: int, lane: int) -> tuple[int, int, int]: + seed = (type_id * 1103515245 + variant * 12345 + lane * 2654435761) & 0xFFFFFFFF + red = 64 + (seed & 0x7F) + green = 72 + ((seed >> 7) & 0x7F) + blue = 80 + ((seed >> 14) & 0x7F) + if lane == 0x22: + green += 24 + elif lane == 0x30: + blue += 28 + return clamp_channel(red), clamp_channel(green), clamp_channel(blue) + + +def placeholder_geometry(lane: int) -> tuple[int, int, int, int]: + if lane == 0x30: + return 64, 64, 32, 52 + if lane == 0x22: + return 64, 40, 32, 28 + return 64, 32, 32, 20 + + +def set_pixel(rgba: bytearray, width: int, x: int, y: int, color: tuple[int, int, int, int]) -> None: + if x < 0 or y < 0 or x >= width: + return + index = (y * width + x) * 4 + if index < 0 or index + 3 >= len(rgba): + return + rgba[index : index + 4] = bytes(color) + + +def build_placeholder_rgba(type_id: int, variant: int, lane: int) -> tuple[bytes, int, int, int, int]: + width, height, origin_x, origin_y = placeholder_geometry(lane) + rgba = bytearray(width * height * 4) + fill_rgb = color_from_key(type_id, variant, lane) + border_rgb = tuple(clamp_channel(channel - 36) for channel in fill_rgb) + top = 4 if lane != 0x30 else 16 + mid_y = top + 8 + bottom = height - 4 + center_x = width // 2 + half_span = width // 2 - 4 + for y in range(top, bottom): + if lane == 0x30 and y < mid_y: + continue + rel = (y - mid_y) / max(1, bottom - mid_y) + span = max(4, int(half_span * (1.0 - abs(rel)))) + left = center_x - span + right = center_x + span + for x in range(left, right + 1): + alpha = 220 + if x in (left, right) or y in (top, bottom, mid_y): + color = (*border_rgb, 255) + else: + color = (*fill_rgb, alpha) + set_pixel(rgba, width, x, y, color) + if lane == 0x30: + for y in range(8, mid_y): + left = center_x - 12 + right = center_x + 12 + for x in range(left, right + 1): + color = (*fill_rgb, 208) if x not in (left, right) else (*border_rgb, 255) + set_pixel(rgba, width, x, y, color) + stripe_count = min(variant, 3) + for stripe in range(stripe_count): + stripe_y = bottom - 6 - stripe * 4 + for x in range(center_x - 10, center_x + 11): + set_pixel(rgba, width, x, stripe_y, (255, 255, 255, 220)) + return bytes(rgba), width, height, origin_x, origin_y + + +def build_placeholder_sprites(output_root: Path, records: list[PlacementRecord]) -> dict[tuple[int, int, int], PlaceholderSprite]: + maps_root = output_root / "maps" / "psx-remorse" / "map-0" + sprites: dict[tuple[int, int, int], PlaceholderSprite] = {} + keys = sorted({(record.u0, record.u4, record.u5) for record in records}) + for index, (type_id, variant, lane) in enumerate(keys): + rgba, width, height, origin_x, origin_y = build_placeholder_rgba(type_id, variant, lane) + atlas_id = f"atlas-type-{type_id:04x}-variant-{variant}-lane-{lane:04x}" + file_name = f"type_{type_id:04X}_variant_{variant}_lane_{lane:04X}.png" + write_png_rgba(maps_root / file_name, rgba, width, height) + shape_code = 0xA000 + index + sprites[(type_id, variant, lane)] = PlaceholderSprite( + type_id=type_id, + variant=variant, + lane=lane, + atlas_id=atlas_id, + file_name=file_name, + shape_code=shape_code, + width=width, + height=height, + origin_x=origin_x, + origin_y=origin_y, + display_name=f"PSX type {type_id:04X} variant {variant} lane {lane:04X}", + description=( + "Region-01 placement probe using PSX type/lane placeholders. " + "This scene is intended to validate coordinate coherence before final art binding." + ), + ) + return sprites + + +def build_scene( + records: list[PlacementRecord], + placeholder_sprites: dict[tuple[int, int, int], PlaceholderSprite], + game_id: str, + game_label: str, + map_id: int, + screen_scale: int, +) -> dict[str, object]: + if not records: + raise ValueError("No structured PSX placement records survived the filter.") + + min_x = min(record.u1 for record in records) + max_x = max(record.u1 for record in records) + min_y = min(record.u2 for record in records) + max_y = max(record.u2 for record in records) + + atlases: list[dict[str, object]] = [] + sprites: list[dict[str, object]] = [] + shape_definitions: list[dict[str, object]] = [] + for sprite in placeholder_sprites.values(): + atlases.append({ + "id": sprite.atlas_id, + "fileName": sprite.file_name, + "width": sprite.width, + "height": sprite.height, + }) + sprites.append({ + "id": f"sprite:{sprite.shape_code}:0", + "atlasId": sprite.atlas_id, + "shape": sprite.shape_code, + "frame": 0, + "x": 0, + "y": 0, + "width": sprite.width, + "height": sprite.height, + "xoff": sprite.origin_x, + "yoff": sprite.origin_y, + }) + shape_definitions.append({ + "id": f"shape:{sprite.shape_code}", + "shape": sprite.shape_code, + "shapeHex": f"0x{sprite.shape_code:04x}", + "family": None, + "label": "Terrain", + "kind": "terrain", + "displayName": sprite.display_name, + "description": sprite.description, + "dimensions": {"x": sprite.width, "y": sprite.height, "z": 1}, + "visibilityTags": ["psx", "type-probe"], + "traits": { + "editor": False, + "roof": False, + "oob": False, + "occluding": False, + "translucent": False, + "solid": False, + "fixed": False, + "land": True, + "draw": True, + "invitem": False, + "animType": 0, + }, + "catalogEntry": { + "humanReadableId": sprite.display_name, + "description": sprite.description, + "roof": None, + "semitransparency": None, + "oob": None, + }, + "catalogOverrides": {"roof": None, "semitransparency": None, "oob": None}, + "tableFallback": None, + }) + + items: list[dict[str, object]] = [] + map_source_items: list[dict[str, object]] = [] + min_screen_left = None + min_screen_top = None + final_right = 0 + final_bottom = 0 + + for draw_order, record in enumerate(records): + sprite = placeholder_sprites[(record.u0, record.u4, record.u5)] + anchor_x = (record.u1 - min_x) * screen_scale + anchor_y = (max_y - record.u2) * screen_scale + screen_left = anchor_x - sprite.origin_x + screen_top = anchor_y - sprite.origin_y + min_screen_left = screen_left if min_screen_left is None else min(min_screen_left, screen_left) + min_screen_top = screen_top if min_screen_top is None else min(min_screen_top, screen_top) + final_right = max(final_right, screen_left + sprite.width) + final_bottom = max(final_bottom, screen_top + sprite.height) + item = { + "id": f"item:{draw_order}:psx-region01-type:{record.side}:{record.row_index}", + "mapSourceIndex": draw_order, + "drawOrder": draw_order, + "kind": "terrain", + "label": "Terrain", + "source": "psx-region01-type-probe", + "world": {"x": record.u1, "y": record.u2, "z": record.u3}, + "mapNum": record.u5, + "npcNum": record.u4, + "nextItem": 0, + "quality": record.u0, + "frame": 0, + "screen": { + "left": screen_left, + "top": screen_top, + "right": screen_left + sprite.width, + "bottom": screen_top + sprite.height, + "width": sprite.width, + "height": sprite.height, + "anchorX": anchor_x, + "anchorY": anchor_y, + }, + "flags": { + "raw": record.u3, + "hex": f"0x{record.u3:04X}", + "invisible": False, + "flipped": False, + }, + "presentation": {"opacity": 1, "visibilityDefault": True}, + "notes": [ + f"PSX region-01 type-placement probe record {record.side} row {record.row_index}", + f"raw words: {record.u0:04X} {record.u1:04X} {record.u2:04X} {record.u3:04X} {record.u4:04X} {record.u5:04X}", + f"placeholder mapping: type={record.u0:04X} variant={record.u4} lane={record.u5:04X}", + ], + "frameSize": { + "width": sprite.width, + "height": sprite.height, + "xoff": sprite.origin_x, + "yoff": sprite.origin_y, + }, + "egg": None, + "npcPreview": None, + "itemPreview": None, + "shapeDefId": f"shape:{sprite.shape_code}", + "spriteId": f"sprite:{sprite.shape_code}:0", + } + items.append(item) + map_source_items.append( + { + "x": record.u1, + "y": record.u2, + "z": record.u3, + "shape": sprite.shape_code, + "frame": 0, + "flags": record.u3, + "quality": record.u0, + "npcNum": record.u4, + "mapNum": record.u5, + "nextItem": 0, + "source": "psx-region01-type-probe", + "rawWords": [record.u0, record.u1, record.u2, record.u3, record.u4, record.u5], + "recordSide": record.side, + "rowIndex": record.row_index, + "typeId": record.u0, + "lane": record.u5, + "variant": record.u4, + "screenLeft": screen_left, + "screenTop": screen_top, + } + ) + + x_shift = -min(0, min_screen_left or 0) + y_shift = -min(0, min_screen_top or 0) + final_right = 0 + final_bottom = 0 + for item in items: + screen = item["screen"] + screen["left"] += x_shift + screen["right"] += x_shift + screen["top"] += y_shift + screen["bottom"] += y_shift + screen["anchorX"] += x_shift + screen["anchorY"] += y_shift + final_right = max(final_right, screen["right"]) + final_bottom = max(final_bottom, screen["bottom"]) + + lane_counts: dict[str, int] = {} + for record in records: + lane_key = f"0x{record.u5:04X}" + lane_counts[lane_key] = lane_counts.get(lane_key, 0) + 1 + + return { + "build": { + "version": DEBUG_SCENE_VERSION, + "fingerprint": "psx-lset1-l0-region01-type-placement", + "generatedAt": "2026-04-06T00:00:00.000Z", + "cacheMode": "single-scene", + }, + "metadata": { + "game": game_id, + "gameLabel": game_label, + "map": map_id, + "rawItemCount": len(records), + "itemCount": len(records), + "paintedItemCount": len(records), + "occludedItemCount": 0, + "invalidItemCount": 0, + "invalidItems": [], + "sceneSummary": { + "atlasCount": len(atlases), + "spriteCount": len(sprites), + "helperCount": 0, + "kindCounts": {"terrain": len(records)}, + "sourceCounts": {"psx-region01-type-probe": len(records)}, + "topFamilies": [{"family": None, "count": len(records)}], + }, + "usage": { + "status": "research", + "confidence": "medium", + "knownHints": [ + "Uses region-01 coordinate/type records with deterministic placeholder sprites instead of the invalid raw-bundle art mapping.", + "This scene is for validating PSX map coherence and placement clustering before final art binding.", + "Palette and final sprite lookup are intentionally deferred in this probe.", + ], + "itemMapNums": sorted({record.u5 for record in records}), + "nonzeroItemMapNums": sorted({record.u5 for record in records if record.u5 != 0}), + "npcLinkedItemCount": sum(1 for record in records if record.u4 != 0), + "note": "Type-placement probe using region-01 records. This is the current best map-coherence debug scene, not final art output.", + "hasRenderableContent": True, + "game": game_id, + "map": map_id, + }, + "baseItemSummary": { + "roofItems": 0, + "editorItems": 0, + "eggFamilyItems": 0, + "invisibleFlaggedItems": 0, + "npcLinkedItems": sum(1 for record in records if record.u4 != 0), + }, + "sorter": "psx_region01_type_probe", + "isEmpty": False, + "emptyReason": None, + "bounds": { + "screenLeft": 0, + "screenTop": 0, + "screenRight": final_right, + "screenBottom": final_bottom, + "width": final_right, + "height": final_bottom, + }, + "zoom": {"min": 0.01, "max": 8, "step": 0.1, "initial": 1}, + "buildFingerprint": "psx-lset1-l0-region01-type-placement", + "generatedAt": "2026-04-06T00:00:00.000Z", + "probeStats": { + "screenScale": screen_scale, + "typeCount": len({record.u0 for record in records}), + "spriteKeyCount": len(placeholder_sprites), + "laneCounts": lane_counts, + "u1Range": [min_x, max_x], + "u2Range": [min_y, max_y], + }, + }, + "atlases": sorted(atlases, key=lambda entry: entry["id"]), + "sprites": sorted(sprites, key=lambda entry: entry["id"]), + "shapeDefinitions": sorted(shape_definitions, key=lambda entry: entry["shape"]), + "items": items, + "mapSource": { + "formatVersion": DEBUG_SCENE_VERSION, + "game": game_id, + "mapId": map_id, + "itemRecordSize": 12, + "itemCount": len(map_source_items), + "originalByteLength": len(map_source_items) * 12, + "exportFileName": None, + "defaultTeleportEggShape": None, + "defaultTeleportEggShapeHex": None, + "defaultTeleportEggFrame": None, + "defaultTeleporterEggFrame": None, + "defaultTeleportDestinationEggFrame": None, + "binaryExportSupported": False, + "items": map_source_items, + }, + } + + +def write_catalog_entry( + output_root: Path, + game_id: str, + game_label: str, + map_id: int, + map_label: str, + raw_item_count: int, + shape_definitions: list[dict[str, object]], +) -> None: + catalog_path = output_root / "catalog.json" + catalog = json.loads(catalog_path.read_text(encoding="utf-8")) if catalog_path.exists() else {"games": []} + games = [game for game in catalog.get("games", []) if game.get("id") != game_id] + games.append( + { + "id": game_id, + "label": game_label, + "mapCount": 1, + "maps": [{"id": map_id, "label": map_label, "rawItemCount": raw_item_count}], + } + ) + games.sort(key=lambda game: game["label"]) + catalog["games"] = games + catalog_path.write_text(json.dumps(catalog, indent=2) + "\n", encoding="utf-8") + + catalogs_dir = output_root / "catalogs" + catalogs_dir.mkdir(parents=True, exist_ok=True) + csv_lines = [ + "shape_code,human_readable_id,description,roof,semitransparency,OOB,categorization,qualities" + ] + for definition in shape_definitions: + csv_lines.append( + ",".join( + [ + definition["shapeHex"], + definition["displayName"], + definition["description"], + "", + "", + "", + definition["kind"], + "", + ] + ) + ) + (catalogs_dir / f"{game_id}.csv").write_text("\n".join(csv_lines) + "\n", encoding="utf-8") + + +def main() -> int: + args = parse_args() + records = load_records(args.input) + maps_root = args.output_root / "maps" / args.game_id / f"map-{args.map_id}" + maps_root.mkdir(parents=True, exist_ok=True) + placeholder_sprites = build_placeholder_sprites(args.output_root, records) + scene = build_scene( + records, + placeholder_sprites, + args.game_id, + args.game_label, + args.map_id, + max(1, args.screen_scale), + ) + (maps_root / "scene.json").write_text(json.dumps(scene, indent=2) + "\n", encoding="utf-8") + write_catalog_entry( + args.output_root, + args.game_id, + args.game_label, + args.map_id, + args.map_label, + len(records), + scene["shapeDefinitions"], + ) + print( + f"wrote PSX type-placement probe: game={args.game_id} map={args.map_id} items={len(records)} unique_shapes={len(scene['shapeDefinitions'])} atlases={len(scene['atlases'])}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/tools/psx_extract_wdl.py b/tools/psx_extract_wdl.py index 17dcf7d..5063483 100644 --- a/tools/psx_extract_wdl.py +++ b/tools/psx_extract_wdl.py @@ -771,7 +771,7 @@ def annotate_region_tim_counts( def parse_lset_wdl(data: bytes) -> dict[str, object] | None: - if len(data) < 0x34: + if len(data) < 0x38: return None header_size = u32(data, 0) @@ -781,6 +781,22 @@ def parse_lset_wdl(data: bytes) -> dict[str, object] | None: header_words = [u32(data, offset) for offset in range(0, header_size, 4)] audio_size = header_words[1] post_audio_start = header_size + audio_size + section_sizes = [u32(data, offset) for offset in range(0x08, 0x38, 4)] + sections: list[dict[str, int | str]] = [] + cursor = post_audio_start + for index, size in enumerate(section_sizes): + if size <= 0: + continue + if cursor + size > len(data): + break + sections.append( + { + "name": f"post_audio_section_{index:02d}", + "offset": cursor, + "size": size, + } + ) + cursor += size high_boundaries = sorted( { value @@ -815,6 +831,7 @@ def parse_lset_wdl(data: bytes) -> dict[str, object] | None: tim_hits = scan_tims(data) annotate_region_tim_counts(regions, tim_hits) + annotate_region_tim_counts(sections, tim_hits) return { "kind": "lset", @@ -822,6 +839,8 @@ def parse_lset_wdl(data: bytes) -> dict[str, object] | None: "header_words": header_words, "audio_size": audio_size, "post_audio_start": post_audio_start, + "section_sizes": section_sizes, + "sections": sections, "high_offset_boundaries": high_boundaries, "regions": regions, "tim_hits": tim_hits, @@ -880,6 +899,10 @@ def summarize(path: Path, summary: dict[str, object]) -> str: lines.append(f"header_size: 0x{summary['header_size']:X}") lines.append(f"audio_size: 0x{summary['audio_size']:X}") lines.append(f"post_audio_start: 0x{summary['post_audio_start']:X}") + lines.append( + "section_sizes: " + + ", ".join(f"0x{value:X}" for value in summary["section_sizes"]) + ) lines.append( "high_offset_boundaries: " + ", ".join(f"0x{value:X}" for value in summary["high_offset_boundaries"]) @@ -896,6 +919,15 @@ def summarize(path: Path, summary: dict[str, object]) -> str: + f"{region['name']}: offset=0x{region['offset']:X} size=0x{region['size']:X} tims={tim_count}" ) + if summary["kind"] == "lset": + lines.append("sections:") + for section in summary["sections"]: + tim_count = section.get("tim_count", 0) + lines.append( + " " + + f"{section['name']}: offset=0x{section['offset']:X} size=0x{section['size']:X} tims={tim_count}" + ) + lines.append("tim_hits:") for hit in summary["tim_hits"]: lines.append(