From 840f194195b5188d211d3d0aac24d8ae739ad005 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 14 Feb 2025 16:06:27 -0500 Subject: [PATCH] Lotta new stuff. --- XPlor.pro | 23 +- asset_structs.h | 19 +- data/Data.qrc | 18 + data/icons/Icon_Copy.png | Bin 0 -> 316 bytes data/icons/Icon_Cut.png | Bin 0 -> 1100 bytes data/icons/Icon_Editor.png | Bin 0 -> 210 bytes data/icons/Icon_Find.png | Bin 0 -> 1337 bytes data/icons/Icon_Model.png | Bin 0 -> 1931 bytes data/icons/Icon_NewFile.png | Bin 0 -> 1087 bytes data/icons/Icon_OpenFile.png | Bin 0 -> 317 bytes data/icons/Icon_Paste.png | Bin 0 -> 537 bytes data/icons/Icon_Pause.png | Bin 0 -> 213 bytes data/icons/Icon_Play.png | Bin 0 -> 318 bytes data/icons/Icon_Save.png | Bin 0 -> 371 bytes data/icons/Icon_SkipBack.png | Bin 0 -> 488 bytes data/icons/Icon_SkipForward.png | Bin 0 -> 532 bytes data/icons/Icon_Sound.png | Bin 0 -> 1848 bytes data/icons/Icon_Stop.png | Bin 0 -> 195 bytes data/icons/Icon_StringTable.png | Bin 0 -> 1964 bytes data/icons/Icon_Tree.png | Bin 0 -> 321 bytes data/icons/Icon_Views.png | Bin 0 -> 1650 bytes enums.h | 15 +- fastfile.cpp | 113 +- fastfile.h | 64 +- fastfile_cod2.cpp | 70 + fastfile_cod2.h | 18 + fastfile_cod5.cpp | 72 + fastfile_cod5.h | 18 + mainwindow.cpp | 57 + mainwindow.ui | 71 +- modelviewer.cpp | 79 +- modelviewer.h | 45 +- modelviewer.ui | 624 ++++++++ preferenceeditor.cpp | 35 + preferenceeditor.h | 22 + preferenceeditor.ui | 528 +++++++ soundviewer.cpp | 80 + soundviewer.h | 34 + soundviewer.ui | 2573 +++++++++++++++++++++++++++++++ stringtableviewer.cpp | 36 + stringtableviewer.h | 25 + stringtableviewer.ui | 24 + utils.h | 68 + xtreewidget.cpp | 264 +++- xtreewidget.h | 7 +- xtreewidgetitem.cpp | 37 + xtreewidgetitem.h | 24 + zonefile.cpp | 287 ++-- zonefile.h | 14 +- 49 files changed, 4961 insertions(+), 403 deletions(-) create mode 100644 data/icons/Icon_Copy.png create mode 100644 data/icons/Icon_Cut.png create mode 100644 data/icons/Icon_Editor.png create mode 100644 data/icons/Icon_Find.png create mode 100644 data/icons/Icon_Model.png create mode 100644 data/icons/Icon_NewFile.png create mode 100644 data/icons/Icon_OpenFile.png create mode 100644 data/icons/Icon_Paste.png create mode 100644 data/icons/Icon_Pause.png create mode 100644 data/icons/Icon_Play.png create mode 100644 data/icons/Icon_Save.png create mode 100644 data/icons/Icon_SkipBack.png create mode 100644 data/icons/Icon_SkipForward.png create mode 100644 data/icons/Icon_Sound.png create mode 100644 data/icons/Icon_Stop.png create mode 100644 data/icons/Icon_StringTable.png create mode 100644 data/icons/Icon_Tree.png create mode 100644 data/icons/Icon_Views.png create mode 100644 fastfile_cod2.cpp create mode 100644 fastfile_cod2.h create mode 100644 fastfile_cod5.cpp create mode 100644 fastfile_cod5.h create mode 100644 modelviewer.ui create mode 100644 preferenceeditor.cpp create mode 100644 preferenceeditor.h create mode 100644 preferenceeditor.ui create mode 100644 soundviewer.cpp create mode 100644 soundviewer.h create mode 100644 soundviewer.ui create mode 100644 stringtableviewer.cpp create mode 100644 stringtableviewer.h create mode 100644 stringtableviewer.ui create mode 100644 xtreewidgetitem.cpp create mode 100644 xtreewidgetitem.h diff --git a/XPlor.pro b/XPlor.pro index 0d07980..ff67012 100644 --- a/XPlor.pro +++ b/XPlor.pro @@ -1,4 +1,4 @@ -QT += core gui 3dcore 3drender 3dinput 3dextras +QT += core gui multimedia RC_ICONS = XPlor.ico @@ -11,6 +11,8 @@ SOURCES += \ ddsfile.cpp \ ddsviewer.cpp \ fastfile.cpp \ + fastfile_cod2.cpp \ + fastfile_cod5.cpp \ fastfileviewer.cpp \ imagewidget.cpp \ iwifile.cpp \ @@ -19,10 +21,13 @@ SOURCES += \ lzokay.cpp \ main.cpp \ mainwindow.cpp \ - modelviewer.cpp \ iwifile.cpp \ + preferenceeditor.cpp \ + soundviewer.cpp \ + stringtableviewer.cpp \ techsetviewer.cpp \ xtreewidget.cpp \ + xtreewidgetitem.cpp \ zonefile.cpp \ zonefileviewer.cpp @@ -36,6 +41,8 @@ HEADERS += \ ddsviewer.h \ enums.h \ fastfile.h \ + fastfile_cod2.h \ + fastfile_cod5.h \ fastfileviewer.h \ imagewidget.h \ ipak_structs.h \ @@ -45,11 +52,14 @@ HEADERS += \ lzokay.hpp \ lzx.h \ mainwindow.h \ - modelviewer.h \ + preferenceeditor.h \ + soundviewer.h \ + stringtableviewer.h \ techsetviewer.h \ utils.h \ xtreewidget.h \ iwifile.h \ + xtreewidgetitem.h \ zonefile.h \ zonefileviewer.h @@ -61,11 +71,14 @@ FORMS += \ iwiviewer.ui \ localstringviewer.ui \ mainwindow.ui \ + modelviewer.ui \ + preferenceeditor.ui \ + soundviewer.ui \ + stringtableviewer.ui \ techsetviewer.ui \ zonefileviewer.ui -RESOURCES += \ - data/Data.qrc +RESOURCES += data/data.qrc LIBS += -L$$PWD/DevILSDK/lib/x64/Unicode/Release -lDevIL LIBS += -L$$PWD/DevILSDK/lib/x64/Unicode/Release -lILU diff --git a/asset_structs.h b/asset_structs.h index f3bb206..ae6f5b8 100644 --- a/asset_structs.h +++ b/asset_structs.h @@ -6,6 +6,7 @@ #include #include #include +#include struct LocalString { QString string; @@ -142,6 +143,8 @@ struct StringTable { quint32 columnCount; quint32 rowCount; QString name; + QVector tablePointers; + QMap content; }; struct Image { @@ -319,6 +322,20 @@ struct MenuFile { QVector menuDefs; }; +struct Sound { + QString path; + QString alias; + quint32 dataPtr; + quint32 dataLength; + QByteArray data; +}; + +struct SoundAsset { + QString name; + quint32 count; + QVector sounds; +}; + struct AssetMap { QVector localStrings; QVector rawFiles; @@ -328,7 +345,7 @@ struct AssetMap { //QVector shaders; QVector techSets; QVector images; - //QVector sounds; + QVector sounds; //QVector collMaps; //QVector lightDefs; //QVector uiMaps; diff --git a/data/Data.qrc b/data/Data.qrc index 44abea1..6334224 100644 --- a/data/Data.qrc +++ b/data/Data.qrc @@ -53,5 +53,23 @@ icons/Icon_WAVFile.png icons/Icon_MenuFile.png icons/Icon_Image.png + icons/Icon_Model.png + icons/Icon_StringTable.png + icons/Icon_Sound.png + icons/Icon_Pause.png + icons/Icon_Play.png + icons/Icon_SkipBack.png + icons/Icon_SkipForward.png + icons/Icon_Stop.png + icons/Icon_Editor.png + icons/Icon_Views.png + icons/Icon_Tree.png + icons/Icon_Copy.png + icons/Icon_Cut.png + icons/Icon_Find.png + icons/Icon_NewFile.png + icons/Icon_Paste.png + icons/Icon_Save.png + icons/Icon_OpenFile.png diff --git a/data/icons/Icon_Copy.png b/data/icons/Icon_Copy.png new file mode 100644 index 0000000000000000000000000000000000000000..2246199450b1a04cf3773b8ef5df03db4fffae32 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0L3?#3!&-4XSjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkP>?6UC&U#flK9sE~8?@6fQ-kg{uXb8doQ9vS=}!RH$Ha;AA`T zp^4ie=H#kt4B`)>7?N5K<~p*52rA6k%sGvZ;Zk44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8paPu$pAc7|WO;e{|NsAijEahi+S=N#uC6Uxw%ose-`?K7x3~A_&z~nw zoLIbgadmZdU0vP9ix;`Lxc>b4)85{mn3ycg;o0~T_HeR@JfsKtV zGBUEVvhv-#cMl&vjE#-8v9Zz7(Q$Qkt*@`2IB{Y@LBag_^QTRl#=*g%udnay?CkIF z|KPy`Nl8f&5s_1;PF=ZjMNm*MFE6jDsma5`eQ+E`S~g;DyF8U z-@ku<^5jW;e0*7186zX(>({UO`T6(n-=CJ2wrJ6!d-v|0Idg`Slhf1FQ%y}RG&I!0 z!a`bFIyW~rI5^nO&TgrL{xe`OrIZBu1v3DHjMkt;-M*E9fyvX;#WAE}&fDpCy`>#R zjy_*-sVn05f&f-mmq5qd6=6=RTU-QGl_GSGvi~=aXVDDeo}@H2N@JlykjqPF-Ci+~ zKMamvJSXO)e^a)$zPWR=XV%As%$u!?&(D!BFHhIgk;qSYYI^!}7>CC7cM4kUAxYwI z?H~9tC?&M&@roW|wE9_>I^|# zE%BQtiZs1reZ_dNeSweyV;N3lk5X%Ts8i7z<8Q$DX;)DmgNM6E z@H0*a(L84N4Gs^QBmuj)QJz0`h5 zxTYiH6?NAFsR+reO+SUVzMr2T)Wnn!yMM-=xQDT;>RLra7^?T`te<^q!_Dn%j<&t| zc5xzY&*$HGFfB0Pwd3m(lZ-1S#UvGX3$`rkx?;EKFHbhJbdDT%V9TRPX2HSjhyT7_ zJ~QJ`?VtD`&l$F+OuP2)*!;M1& literal 0 HcmV?d00001 diff --git a/data/icons/Icon_Editor.png b/data/icons/Icon_Editor.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b72af85bf02bf510389a60ff205fea24889793 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0L3?#3!&-4XSjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkP>?6UC&U#khF4Ec;Y>*rMa)9fSquIc{M% uGp$~br&;NU+h3k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pn|plpAc7|WO;dcMMXt*b#;Ax{ltkA_w3p8{rmU-|Nnpa@@4z>?Gq+U zm^^v%=g*&CzkZ#UmzR~5RaI4W`SN9FXXnz=(zdp?ckkXA8X6`gCAGJ=|N8Z7-@biK zO-=s({`&g*0RaIA4jizsu<-WwuC1+IxNsp44-e2rX=!P5b8{abA2TyEH8nLE85uV> zw}%fOy12Nsw6vT)eVUPxF*-VW(xgcrK70@s7Phsu_4W1r`0-;;PtU7YuQ)k5=gysL zXJ?n2o4a@KUT$t~K0dx_)22Op_N=k7F*`eZ?b@~1uU{7w6#V=5Z(Lm5tXZ>|n3(MC z?e+BZy1Tmt1O)2p>U4B;9336y<>gaTQ~mt>L_|a)A|lqTS;NN0cJboHYuBz76cnsp zy;@FAuClT+GBWbUjT>*?ya^5to;r2v-Me>LSXkWM-AznPJUu;&ii&!Bd#$alfByX0 z(b2Jg|NfOLS2i~{uUoh7_wV1|zI`h$F6QOsO-@dpGiQ#nvT|l-W=2MagoFe?KYv+S zS!ifzI(L06FkG5Sg8YIRfI&a7!127E6$1m)K2I0Nkcv6UDGW?*Y@ZI)&xqsVRS35} z!f}dsfr~Xqk5t2D1~<23K(|iLWKcF%b`kNYRnOp9#(1Y^zo3kn+p+#7k_rcY@P#3S(YqjW5*yz*o3MZ6O#_?NXvI6lxny}mqQoyy}{CEkE4re`M`SPrKBsbLCR ze5Rt+g1IE0k<;wxd!GOC&dq-RZ>lS-Jb9v`PlCC}@!JcdxmR&n7U%v>VP{*G;M-jHN4i~j0#AY6p#urOCpadW&l1|+%)`U?eM-52$$?_|3lnblY`-wI;(cO5 zLDIg;*%KIF7n_1*2^&1KFuzJLaiJ8ynU!|0(IU(T3^8IZK75H@LiUyz;{< zmPO@}hh6EY?s|{Nk#<+~kd~ z-Axq&%G5e0J>17KbxJj3;t4~6U^xayyCaL89`>}q;kP=o`3C#s35NyQC6*T1Y4a_A zWWu;NuE4S}Y~AL+hw_=+&K{YeWWd=u{rHY|f^otduJo=u*7MtOsdDqWAE{5(c^3BA z{_r^vWqo}OZ{m;BH9s^BIM?&#HKrKHs6EJ%zapP(YW?;0D#1gClbI&+^4w7qxb1W> zlj)+bOpKbqeYZo2Oq{(sZ`3AS@;aQwFVdQ&MBb@ E0JF+^lK=n! literal 0 HcmV?d00001 diff --git a/data/icons/Icon_Model.png b/data/icons/Icon_Model.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e03554df7a14463770f7642de2d75ee66d8bd2 GIT binary patch literal 1931 zcmds1`8ON*1N_8&lx$f=TUV))YV%dC^`%I&(K_SSh`Ntu9i{A|iqKV8Q>4^=RLQEY zs1mk9De4T3dl5?A5@)1|tG@Q%_|AJX^XAR3^CsB}eMd~>tOx)=%)}UFbK;19Da3oC zg$;4c6N7}>+(AMndNg8Bf^e|0V<-TklK&C{@(N@C;CW?bZfA5-PQr?2(WLdP&oju^ z3my&3=lxTfFBiPtGcyO4e{216`wdsLI@4+T#pUWi} zdUr{uRwg~m22Sjb1%J3M8c0m27p|rD=7&Da11^W#+|}8ycA{tN1s=XhPJ0Th(mnlK zppSPNW^%y2#-dkPAU~p#lL{(cX+2riPm%w{}k(J>c z*eDV+Mg5!twW%TGlmIUaeu)fYpN-{}aD-`gU>L3WVZ$==^518L9 z1SFHO9(F&RpVlLeU&{Brti3unzZ}_iz}@eDS3lD~JcFp%UuBPd>Mp}Q`Gos(&gSjH zr}47bgn8=t9)~m3+CJ9Wb;LRF0Wf-X?>qaHZ@tTtt$m&<@Kx&cyXQH-16?Lzpb`WH z0!`N7GU1n?C%|;Hqsv$0r()pwE4a$u-uZYsq4`yX75MQX3LOm)%!Nf$hXuzZ!TNZ# zxrMt7*jjdUg<+W&jBKZ-X+&>=!97$m62Je&f_OE>~EOq$>t;K z6B54Mu5Sm z?Zw@g{+8BM{21LzHp8@w{vEDEXJxGlpbF2xBO#cy`|E322v@lD7D{7TunAkZ6}O=b z6gr%maQ9P19V`IT_rc{Q{=>8SFgKo7S5?>oKV8~ASHx{h5kL;se0?md?#jbK9HvxU zNU(=qJ!~G#{=KIIgu-dbBBV;oTROg1^sor6xvUu3LxjqbzkD3|6nY_}sC;nXdW<6M zZidt>_cTr?k+_7dV^zmhW>7->>m`yXDBHpEr9fMVj%`vlZ6xDd)Ms;HnPTLSQC`Xw z5mTDv4`YW0xc`c7;LYPF&bMfb&7yRtIbmo|+<#UxAtA77L-v)zTqNdYr(+U@Dp=6@ zqlDxKBE#iz2R$zBCeB?VnZ&5WQBhbKXWbq@FNk_{lnT@jLWsadI9%&V=-?rSs1%(W z;d{_wKKCdIpLeMHHrYTpJw^!yskpj$IA3?QNn&P;Zm!)P(xo`xq;p$Ns8hLapgMy- zz@%8m%OD%ZzrC^ic)KV}=z-*O=ukP+Bktx70q+*0RtLL*fT%g$>ex~JO%b1|&{skN zqb{T}sV=V#TqPpNbi~AluHMbxAKvqG*iXLAg(j3}ovu4I0=*7r7BM8@bg|G0U&FC- zK~_^8?y$e*lpU%~$}$?)yA>~10=1IDstpNNbe3EQkH+OiUlg1q3I(@7k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8paSs#pAc7|WO;e{|NsAi3}LZgq9_uV24}g@u)sl@~8w zoRpNbZQC|=b@kuBe}Dh}otc@Li;GK2N-8HOM^;vrkB=`SBjfVr%R6`OEGsLUJ$p72 z6VtbE-*|X<^!4@Eu3fug#fp-WlKlMqpFe*pDJiwJwRLrMWoBl&ySq=CG$}YZxS^rp z>C>n8?%mVW)HE?M;o#s13=G_}XHQB>inO$JUS6JnfPl8Pc4}&BWMt%N`J;1yAre~> z}(*&ks{s85o!%JY5_^D(1Ys{?Pljg9yWi?_6SAlse`Xm+_>CoLUy7rfsoY zzGQ)hl^gqSY4%+wS7+R?Wxb8?K{-sv%u&iczOdAqm# z{dMn4_qKAZQFO^}iU=6H#Ohb#QkIU~3S#!sx`oThS`eV9WYZc_mv!e)mPjh4t4P!a4XW zS~wUS4po^gVzRrLrf@JXl4k*H{*6LWS%!?GQcG@p|8Aspp|2va{$k_w>wmGf0-Epqz$U21wx)Xe3Lel$8{O!J%NFFnLQT2Y4UAMPWJ>UKVx6j*s^_BMC zwBdYtTgmm!t`*xKgzMkDrFN>hcaqX0891AhPj literal 0 HcmV?d00001 diff --git a/data/icons/Icon_OpenFile.png b/data/icons/Icon_OpenFile.png new file mode 100644 index 0000000000000000000000000000000000000000..a7166b5260dab69d60805e3e5c1913b920ee4e3a GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0L3?#3!&-4XSjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkP>?6UC&U#Xhv z3gdIpg)5jG_#cTgwAytsoM_iz;)&v2ufLdsfmyF2q@j#S@xhkE4IQThG8P@xWH_IC zP*B5jcB9}72}LF!Z3V4$rWdr_1<&1N@n&aW7O0fe2;2S?=nDok44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkP{nPlzj!E-x?d>FN3Z|9@?5?Su&v)YQ}>BO@O^e7Jk}?sxCrEnT`) zKtKSf?8=oZTeoiI;^JavW|otai-?HO*VlJ;cJ}e{2?`3bx3@oh`0%r5&(5Ab>+9=V zUteEYS;^1OfAZwXw6ru+kM3rm5lSUNe!&btoA3j@zmxX^Js{`l;uumf=k4`q?j{2X zwg>q$(==V!Up)E$KekCARl$=rm3{Ndr^nKY%L2E(KezFFq2EolrX>O=Lg%DsGi~}f zWd;A0PWx2{87}s%o7(uTcgK|g6_%27f2>neWxN~Sz08$6;KD9#GQLjy%^&^KA*+wZ1k^j|99(2EXo~%f*zBA;mY9Y>gTe~DWM4fvFYw^ literal 0 HcmV?d00001 diff --git a/data/icons/Icon_Pause.png b/data/icons/Icon_Pause.png new file mode 100644 index 0000000000000000000000000000000000000000..8d46f91b78a9a64666c056fb17dc681882a4d5ea GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0L3?#3!&-4XSjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkP>?6UC&U#eK@{oCO|{#S9GG z!XV7ZFl&wkP>?6UC&U#x#m6N0T+${r3{w^yLpOQ9LrcM!W)%XZ<#i@^E_DB zy6tsBpUj!va<2sWOU)hj{pEgH@@>Oye!fp1AK7hV`{(=n`U3^|b?;7ZJ>bO6ld!;{ zk%^T_#=zr%0%Ic^hnj%TUy{(Ao*`_H;)Ni&;XZ=g3AJYD@<);T3K F0RaE&aiRbK literal 0 HcmV?d00001 diff --git a/data/icons/Icon_Save.png b/data/icons/Icon_Save.png new file mode 100644 index 0000000000000000000000000000000000000000..587ea2ea269fc352012c2cb0afaf1da7b39de1a6 GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0L3?#3!&-4XSjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkP>?6UC&U#m>g4=216FL1A7^C8VY88;4xu*CMwW)q2a1R za+tMD46A5_*sHk=Ce{hfWz1a-=fxCSwHUUrMINZuz7QhP7<4_=fN{cPr@g_>51A6u z9|#MSGE|=CR%9r%T^*$z(~v0Y;Vq%<#K81IXM>c`s{Pv!v6NXmJj$^3d0-yWeK6Fd zv0<$Nm4~*+#5t%lvo@btv)a{{=dk=B(Z>rXUz>(fs>OQOQf<6r7~0rsT_F9 z`?S5zyJ1ObV1_`Is)MloWv-5{Y~C%W0$YmB4qjqpP-B}XDcr{;3iLaJr>mdKI;Vst E04V}@o&W#< literal 0 HcmV?d00001 diff --git a/data/icons/Icon_SkipBack.png b/data/icons/Icon_SkipBack.png new file mode 100644 index 0000000000000000000000000000000000000000..fd217d8883eb4fa2382623fa6ed86166cb1760e9 GIT binary patch literal 488 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 mAN2ln^jZB6vG?EOHu?v9ZpI3}KcJQX0000P literal 0 HcmV?d00001 diff --git a/data/icons/Icon_Stop.png b/data/icons/Icon_Stop.png new file mode 100644 index 0000000000000000000000000000000000000000..fb18f609eb89042034c29c78425fa28fbdf92791 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0L3?#3!&-4XSjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkP>?6UC&U#gTe~DWM4f_Y*eC literal 0 HcmV?d00001 diff --git a/data/icons/Icon_StringTable.png b/data/icons/Icon_StringTable.png new file mode 100644 index 0000000000000000000000000000000000000000..9ca4722ab4628ecd84e23dfeb240a01ab0ea60b9 GIT binary patch literal 1964 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 VaWT(Y1z9x!pe*eXEpYFYe*r*6T>=0A literal 0 HcmV?d00001 diff --git a/enums.h b/enums.h index 8818afa..b5f8204 100644 --- a/enums.h +++ b/enums.h @@ -12,12 +12,15 @@ enum FF_PLATFORM { enum FF_GAME { FF_GAME_NONE = 0x00, // No game - FF_GAME_COD4 = 0x01, // Modern Warware 1 - FF_GAME_COD5 = 0x02, // World at War - FF_GAME_COD6 = 0x03, // Modern Warfare 2 - FF_GAME_COD7 = 0x04, // Black Ops 1 - FF_GAME_COD8 = 0x05, // Modern Warfare 3 - FF_GAME_COD9 = 0x06, // Black Ops 2 + FF_GAME_COD1 = 0x01, // Call of Duty + FF_GAME_COD2 = 0x02, // Call of Duty 2 + FF_GAME_COD3 = 0x03, // Call of Duty 3 + FF_GAME_COD4 = 0x04, // Modern Warware 1 + FF_GAME_COD5 = 0x05, // World at War + FF_GAME_COD6 = 0x06, // Modern Warfare 2 + FF_GAME_COD7 = 0x07, // Black Ops 1 + FF_GAME_COD8 = 0x08, // Modern Warfare 3 + FF_GAME_COD9 = 0x09, // Black Ops 2 }; enum IWI_VERSION { diff --git a/fastfile.cpp b/fastfile.cpp index 4e57009..328a382 100644 --- a/fastfile.cpp +++ b/fastfile.cpp @@ -4,42 +4,21 @@ #include #include -FastFile::FastFile() : - fileStem(), - company(), - fileType(), - signage(), - magic(), - version() { -} - FastFile::~FastFile() { } -FastFile::FastFile(const FastFile &aFastFile) { - fileStem = aFastFile.GetFileStem(); - company = aFastFile.GetCompany(); - fileType = aFastFile.GetFileType(); - signage = aFastFile.GetSignage(); - magic = aFastFile.GetMagic(); - version = aFastFile.GetVersion(); - zoneFile = aFastFile.zoneFile; - game = aFastFile.GetGame(); - platform = aFastFile.GetPlatform(); -} - FastFile &FastFile::operator=(const FastFile &other) { if (this != &other) { - fileStem = other.GetFileStem(); - company = other.GetCompany(); - fileType = other.GetFileType(); - signage = other.GetSignage(); - magic = other.GetMagic(); - version = other.GetVersion(); - zoneFile = other.zoneFile; - game = other.GetGame(); - platform = other.GetPlatform(); + mStem = other.GetStem(); + mType = other.GetType(); + mCompany = other.GetCompany(); + mSignage = other.GetSignage(); + mMagic = other.GetMagic(); + mVersion = other.GetVersion(); + mZoneFile = other.GetZoneFile(); + mGame = other.GetGame(); + mPlatform = other.GetPlatform(); } return *this; } @@ -52,13 +31,24 @@ bool FastFile::Load(const QByteArray aData) { fastFileStream.setByteOrder(QDataStream::LittleEndian); // Parse header values. - company = pParseFFCompany(&fastFileStream); - fileType = pParseFFFileType(&fastFileStream); - signage = pParseFFSignage(&fastFileStream); - magic = pParseFFMagic(&fastFileStream); - version = pParseFFVersion(&fastFileStream); - platform = pCalculateFFPlatform(); - game = pCalculateFFGame(); + if (fastFileStream.device()->peek(2).toHex() == "0000") { + company = COMPANY_INFINITY_WARD; + fileType = FILETYPE_FAST_FILE; + signage = SIGNAGE_UNSIGNED; + magic = 0; + version = 0; + platform = "360"; + game = "COD2"; + + } else { + company = pParseFFCompany(&fastFileStream); + fileType = pParseFFFileType(&fastFileStream); + signage = pParseFFSignage(&fastFileStream); + magic = pParseFFMagic(&fastFileStream); + version = pParseFFVersion(&fastFileStream); + platform = pCalculateFFPlatform(); + game = pCalculateFFGame(); + } if (game == "COD5") { // For COD5, simply decompress from offset 12. @@ -189,6 +179,19 @@ bool FastFile::Load(const QByteArray aData) { // Load the zone file with the decompressed data (using an Xbox platform flag). zoneFile.Load(decompressedData, fileStem.section('.', 0, 0) + ".zone", FF_PLATFORM_XBOX); + } else if (game == "COD2") { + Utils::ReadUntilHex(&fastFileStream, "78"); + QByteArray compressedData = aData.mid(fastFileStream.device()->pos()); + QByteArray decompressedData = Compressor::DecompressZLIB(compressedData); + + QFile testFile("exports/" + fileStem.split('.')[0] + ".zone"); + if(testFile.open(QIODevice::WriteOnly)) { + testFile.write(decompressedData); + testFile.close(); + } + + // Load the zone file with the decompressed data (using an Xbox platform flag). + zoneFile.Load(decompressedData, fileStem.section('.', 0, 0) + ".zone", FF_PLATFORM_XBOX, FF_GAME_COD2); } return true; @@ -222,42 +225,6 @@ bool FastFile::Load(const QString aFilePath) { return true; } -QString FastFile::GetFileStem() const { - return fileStem; -} - -FF_COMPANY FastFile::GetCompany() const { - return company; -} - -FF_FILETYPE FastFile::GetFileType() const { - return fileType; -} - -FF_SIGNAGE FastFile::GetSignage() const { - return signage; -} - -QString FastFile::GetMagic() const { - return magic; -} - -quint32 FastFile::GetVersion() const { - return version; -} - -ZoneFile FastFile::GetZoneFile() const { - return zoneFile; -} - -QString FastFile::GetGame() const { - return game; -} - -QString FastFile::GetPlatform() const { - return platform; -} - FF_COMPANY FastFile::pParseFFCompany(QDataStream *afastFileStream) { // Check for null datastream ptr if (!afastFileStream) { return COMPANY_NONE; } diff --git a/fastfile.h b/fastfile.h index 0277d1b..de02d34 100644 --- a/fastfile.h +++ b/fastfile.h @@ -10,42 +10,42 @@ class FastFile { public: - FastFile(); - ~FastFile(); - FastFile(const FastFile &aFastFile); - FastFile &operator=(const FastFile &other); + virtual ~FastFile(); + virtual FastFile &operator=(const FastFile &other); - bool Load(const QString aFilePath); - bool Load(const QByteArray aData); + virtual bool Load(const QString aFilePath) = 0; + virtual bool Load(const QByteArray aData) = 0; - QString GetFileStem() const; - FF_COMPANY GetCompany() const; - FF_FILETYPE GetFileType() const; - FF_SIGNAGE GetSignage() const; - QString GetMagic() const; - quint32 GetVersion() const; - ZoneFile GetZoneFile() const; - QString GetGame() const; - QString GetPlatform() const; + virtual QString GetStem() const { return mStem; } + virtual FF_FILETYPE GetType() const { return mType; } + virtual FF_COMPANY GetCompany() const { return mCompany; } + virtual FF_SIGNAGE GetSignage() const { return mSignage; } + virtual QString GetMagic() const { return mMagic; } + virtual quint32 GetVersion() const { return mVersion; } + virtual ZoneFile GetZoneFile() const { return mZoneFile; } + virtual QString GetGame() const { return mGame; } + virtual QString GetPlatform() const { return mPlatform; } + + virtual void SetStem(const QString aStem) { mStem = aStem; } + virtual void SetType(const FF_FILETYPE aType) { mType = aType; } + virtual void SetCompany(const FF_COMPANY aCompany) { mCompany = aCompany; } + virtual void SetSignage(const FF_SIGNAGE aSignage) { mSignage = aSignage; } + virtual void SetMagic(const QString aMagic) { mMagic = aMagic; } + virtual void SetVersion(const quint32 aVersion) { mVersion = aVersion; } + virtual void SetZoneFile(const ZoneFile aZoneFile) { mZoneFile = aZoneFile; } + virtual void SetGame(const QString aGame) { mGame = aGame; } + virtual void SetPlatform(const QString aPlatform) { mPlatform = aPlatform; } private: - QString fileStem; - FF_COMPANY company; - FF_FILETYPE fileType; - FF_SIGNAGE signage; - QString magic; - quint32 version; - ZoneFile zoneFile; - QString game; - QString platform; - - QString pCalculateFFGame(); - QString pCalculateFFPlatform(); - QString pParseFFMagic(QDataStream *afastFileStream); - FF_SIGNAGE pParseFFSignage(QDataStream *afastFileStream); - FF_FILETYPE pParseFFFileType(QDataStream *afastFileStream); - FF_COMPANY pParseFFCompany(QDataStream *afastFileStream); - quint32 pParseFFVersion(QDataStream *afastFileStream); + QString mStem; + FF_FILETYPE mType; + FF_COMPANY mCompany; + FF_SIGNAGE mSignage; + QString mMagic; + quint32 mVersion; + ZoneFile mZoneFile; + QString mGame; + QString mPlatform; }; #endif // FASTFILE_H diff --git a/fastfile_cod2.cpp b/fastfile_cod2.cpp new file mode 100644 index 0000000..4eebd3e --- /dev/null +++ b/fastfile_cod2.cpp @@ -0,0 +1,70 @@ +#include "fastfile_cod2.h" + +#include +#include + +FastFile_COD2::FastFile_COD2() { + +} + +FastFile_COD2::~FastFile_COD2() { + +} + +FastFile &FastFile_COD2::operator=(const FastFile &other) { + +} + +bool FastFile_COD2::Load(const QString aFilePath) { + if (aFilePath.isEmpty()) { + return false; + } + + // Check fastfile can be read + QFile *file = new QFile(aFilePath); + if (!file->open(QIODevice::ReadOnly)) { + qDebug() << QString("Error: Failed to open FastFile: %1!").arg(aFilePath); + return false; + } + + // Decompress fastfile and close + const QString fastFileStem = aFilePath.section("/", -1, -1); + SetStem(fastFileStem); + if (!Load(file->readAll())) { + qDebug() << "Error: Failed to load fastfile: " << fastFileStem; + return false; + } + + file->close(); + + // Open zone file after decompressing ff and writing + return true; +} + +bool FastFile_COD2::Load(const QByteArray aData) { + // Create a QDataStream on the input data. + QDataStream fastFileStream(aData); + fastFileStream.setByteOrder(QDataStream::LittleEndian); + + // Parse header values. + SetCompany(COMPANY_INFINITY_WARD); + SetType(FILETYPE_FAST_FILE); + SetSignage(SIGNAGE_UNSIGNED); + SetMagic(0); + SetVersion(0); + SetPlatform("360"); + SetGame("COD2"); + + Utils::ReadUntilHex(&fastFileStream, "78"); + QByteArray compressedData = aData.mid(fastFileStream.device()->pos()); + QByteArray decompressedData = Compressor::DecompressZLIB(compressedData); + + QFile testFile("exports/" + GetStem().split('.')[0] + ".zone"); + if(testFile.open(QIODevice::WriteOnly)) { + testFile.write(decompressedData); + testFile.close(); + } + + // Load the zone file with the decompressed data (using an Xbox platform flag). + zoneFile.Load(decompressedData, GetStem().section('.', 0, 0) + ".zone", FF_PLATFORM_XBOX, FF_GAME_COD2); +} diff --git a/fastfile_cod2.h b/fastfile_cod2.h new file mode 100644 index 0000000..f6fee49 --- /dev/null +++ b/fastfile_cod2.h @@ -0,0 +1,18 @@ +#ifndef FASTFILE_COD2_H +#define FASTFILE_COD2_H + +#include "fastfile.h" + +class FastFile_COD2 : public FastFile +{ +public: + FastFile_COD2(); + ~FastFile_COD2(); + + FastFile &operator=(const FastFile &other) override; + + bool Load(const QString aFilePath) override; + bool Load(const QByteArray aData) override; +}; + +#endif // FASTFILE_COD2_H diff --git a/fastfile_cod5.cpp b/fastfile_cod5.cpp new file mode 100644 index 0000000..501ab90 --- /dev/null +++ b/fastfile_cod5.cpp @@ -0,0 +1,72 @@ +#include "fastfile_cod5.h" + +#include +#include + +FastFile_COD5::FastFile_COD5() { + +} + +FastFile_COD5::~FastFile_COD5() { + +} + +FastFile &FastFile_COD5::operator=(const FastFile &other) { + +} + +bool FastFile_COD5::Load(const QString aFilePath) { + if (aFilePath.isEmpty()) { + return false; + } + + // Check fastfile can be read + QFile *file = new QFile(aFilePath); + if (!file->open(QIODevice::ReadOnly)) { + qDebug() << QString("Error: Failed to open FastFile: %1!").arg(aFilePath); + return false; + } + + // Decompress fastfile and close + const QString fastFileStem = aFilePath.section("/", -1, -1); + SetStem(fastFileStem); + if (!Load(file->readAll())) { + qDebug() << "Error: Failed to load fastfile: " << fastFileStem; + return false; + } + + file->close(); + + // Open zone file after decompressing ff and writing + return true; +} + +bool FastFile_COD5::Load(const QByteArray aData) { + QByteArray decompressedData; + + // Create a QDataStream on the input data. + QDataStream fastFileStream(aData); + fastFileStream.setByteOrder(QDataStream::LittleEndian); + + // Parse header values. + SetCompany(pParseFFCompany(&fastFileStream)); + fileType = pParseFFFileType(&fastFileStream); + signage = pParseFFSignage(&fastFileStream); + magic = pParseFFMagic(&fastFileStream); + version = pParseFFVersion(&fastFileStream); + platform = pCalculateFFPlatform(); + game = pCalculateFFGame(); + + // For COD5, simply decompress from offset 12. + decompressedData = Compressor::DecompressZLIB(aData.mid(12)); + + QFile testFile("exports/" + fileStem.section('.', 0, 0) + ".zone"); + if(testFile.open(QIODevice::WriteOnly)) { + testFile.write(decompressedData); + testFile.close(); + } + + zoneFile.Load(decompressedData, fileStem.section('.', 0, 0) + ".zone"); + + return true; +} diff --git a/fastfile_cod5.h b/fastfile_cod5.h new file mode 100644 index 0000000..f82d3d0 --- /dev/null +++ b/fastfile_cod5.h @@ -0,0 +1,18 @@ +#ifndef FASTFILE_COD5_H +#define FASTFILE_COD5_H + +#include "fastfile.h" + +class FastFile_COD5 : public FastFile +{ +public: + FastFile_COD5(); + ~FastFile_COD5(); + + FastFile &operator=(const FastFile &other) override; + + bool Load(const QString aFilePath) override; + bool Load(const QByteArray aData) override; +}; + +#endif // FASTFILE_COD5_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 8715004..37e4880 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,7 +1,10 @@ #include "mainwindow.h" #include "aboutdialog.h" #include "fastfile.h" +#include "preferenceeditor.h" #include "qheaderview.h" +#include "soundviewer.h" +#include "stringtableviewer.h" #include "techsetviewer.h" #include "ui_mainwindow.h" #include "compressor.h" @@ -34,6 +37,11 @@ MainWindow::MainWindow(QWidget *parent) //ModelViewer *mModelViewer = new ModelViewer(container); //mModelViewer->setAcceptDrops(false); + connect(ui->actionPreferences, &QAction::triggered, this, [this](bool checked = false) { + PreferenceEditor *prefEditor = new PreferenceEditor(this); + prefEditor->exec(); + }); + ui->tabWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->tabWidget, &QTabWidget::customContextMenuRequested, this, [this](const QPoint &pos) { if (pos.isNull()) @@ -104,6 +112,10 @@ MainWindow::MainWindow(QWidget *parent) ui->tabWidget->removeTab(index); }); + connect(mTreeWidget, &XTreeWidget::Cleared, this, [this]() { + ui->tabWidget->clear(); + }); + connect(mTreeWidget, &XTreeWidget::RawFileSelected, this, [this](std::shared_ptr rawFile) { QPlainTextEdit *scriptEditor = new QPlainTextEdit(this); scriptEditor->setAcceptDrops(false); @@ -250,6 +262,51 @@ MainWindow::MainWindow(QWidget *parent) ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); }); + connect(mTreeWidget, &XTreeWidget::StrTableSelected, this, [this](std::shared_ptr aStrTable) { + + StringTableViewer *strTableViewer = new StringTableViewer(this); + strTableViewer->setAcceptDrops(false); + strTableViewer->SetStringTable(aStrTable); + + QString fileStem = aStrTable->name; + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + return; + } + } + + ui->tabWidget->addTab(strTableViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, QIcon(":/icons/icons/Icon_StringTable.png")); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::SoundSelected, this, [this](std::shared_ptr aSound) { + + SoundViewer *soundViewer = new SoundViewer(this); + soundViewer->setAcceptDrops(false); + soundViewer->SetSound(aSound); + + QString fileStem = aSound->path.split('/').last(); + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + return; + } + } + + ui->tabWidget->addTab(soundViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, QIcon(":/icons/icons/Icon_Sound.png")); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::TabSelected, this, [this](QString tabName) { + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == tabName) { + ui->tabWidget->setCurrentIndex(i); + break; + } + } + }); + // Connect Help > About dialog connect(ui->actionAbout, &QAction::triggered, this, [this](bool checked) { Q_UNUSED(checked); diff --git a/mainwindow.ui b/mainwindow.ui index 9ed5eb0..1b257d7 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -58,17 +58,11 @@ Recent... - - - Import... - - - @@ -114,6 +108,8 @@ + + @@ -140,7 +136,8 @@ - + + :/icons/icons/Icon_NewFile.png:/icons/icons/Icon_NewFile.png New @@ -148,7 +145,8 @@ - + + :/icons/icons/Icon_NewFile.png:/icons/icons/Icon_NewFile.png New Fast File @@ -156,7 +154,8 @@ - + + :/icons/icons/Icon_NewFile.png:/icons/icons/Icon_NewFile.png New Zone File @@ -164,7 +163,8 @@ - + + :/icons/icons/Icon_OpenFile.png:/icons/icons/Icon_OpenFile.png Open Fast File @@ -172,7 +172,8 @@ - + + :/icons/icons/Icon_OpenFile.png:/icons/icons/Icon_OpenFile.png Open Zone File @@ -180,7 +181,8 @@ - + + :/icons/icons/Icon_OpenFile.png:/icons/icons/Icon_OpenFile.png Open Folder @@ -188,16 +190,14 @@ - + + :/icons/icons/Icon_Save.png:/icons/icons/Icon_Save.png Save - - - Save As @@ -248,24 +248,19 @@ - - - Undo - - - Redo - + + :/icons/icons/Icon_Cut.png:/icons/icons/Icon_Cut.png Cut @@ -273,7 +268,8 @@ - + + :/icons/icons/Icon_Copy.png:/icons/icons/Icon_Copy.png Copy @@ -281,16 +277,14 @@ - + + :/icons/icons/Icon_Paste.png:/icons/icons/Icon_Paste.png Paste - - - Rename @@ -306,9 +300,6 @@ - - - Delete @@ -334,9 +325,6 @@ - - - About @@ -347,22 +335,27 @@ - - - Check for Updates - + + :/icons/icons/Icon_Find.png:/icons/icons/Icon_Find.png Find + + + Preferences... + + - + + + diff --git a/modelviewer.cpp b/modelviewer.cpp index 27058b0..a461223 100644 --- a/modelviewer.cpp +++ b/modelviewer.cpp @@ -1,73 +1,14 @@ #include "modelviewer.h" +#include "ui_modelviewer.h" ModelViewer::ModelViewer(QWidget *parent) - : QWidget{parent} { - Qt3DExtras::Qt3DWindow *view = new Qt3DExtras::Qt3DWindow(); - view->defaultFrameGraph()->setClearColor(QColor(QRgb(0x4d4d4f))); - - QWidget *container = QWidget::createWindowContainer(view); - QSize screenSize = view->screen()->size(); - container->setMinimumSize(QSize(200, 100)); - container->setMaximumSize(screenSize); - - QHBoxLayout *hLayout = new QHBoxLayout(this); - QVBoxLayout *vLayout = new QVBoxLayout(); - vLayout->setAlignment(Qt::AlignTop); - hLayout->addWidget(container, 1); - hLayout->addLayout(vLayout); - - // Root entity - Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); - - // Camera - Qt3DRender::QCamera *cameraEntity = view->camera(); - - cameraEntity->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f); - cameraEntity->setPosition(QVector3D(0, 0, 50.0f)); // Move farther along Z-axis - cameraEntity->setUpVector(QVector3D(0, 1, 0)); - cameraEntity->setViewCenter(QVector3D(0, 0, 0)); - - Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity(rootEntity); - Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight(lightEntity); - light->setColor("white"); - light->setIntensity(1); - lightEntity->addComponent(light); - - Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform(lightEntity); - lightTransform->setTranslation(cameraEntity->position()); - lightEntity->addComponent(lightTransform); - - // For camera controls - Qt3DExtras::QFirstPersonCameraController *camController = new Qt3DExtras::QFirstPersonCameraController(rootEntity); - camController->setCamera(cameraEntity); - - // Set root object of the scene - view->setRootEntity(rootEntity); - - // Load custom 3D model - Qt3DRender::QMesh *customMesh = new Qt3DRender::QMesh(); - customMesh->setSource(QUrl::fromLocalFile(":/obj/data/obj/defaultactor_LOD0.obj")); - - // Adjust the model transformation - Qt3DCore::QTransform *customTransform = new Qt3DCore::QTransform(); - customTransform->setRotationX(-90); - customTransform->setRotationY(-90); - - // Keep translation if necessary - customTransform->setTranslation(QVector3D(0.0f, -100.0f, -200.0f)); - - Qt3DExtras::QNormalDiffuseMapMaterial *customMaterial = new Qt3DExtras::QNormalDiffuseMapMaterial(); - - Qt3DRender::QTextureLoader *normalMap = new Qt3DRender::QTextureLoader(); - normalMap->setSource(QUrl::fromLocalFile(":/obj/data/obj/normalmap.png")); - customMaterial->setNormal(normalMap); - - Qt3DRender::QTextureLoader *diffuseMap = new Qt3DRender::QTextureLoader(); - diffuseMap->setSource(QUrl::fromLocalFile(":/obj/data/obj/diffusemap.png")); - customMaterial->setDiffuse(diffuseMap); - - Qt3DCore::QEntity *m_torusEntity = new Qt3DCore::QEntity(rootEntity); - m_torusEntity->addComponent(customMesh); - m_torusEntity->addComponent(customMaterial); - m_torusEntity->addComponent(customTransform); + : QWidget(parent) + , ui(new Ui::ModelViewer) +{ + ui->setupUi(this); +} + +ModelViewer::~ModelViewer() +{ + delete ui; } diff --git a/modelviewer.h b/modelviewer.h index c56d376..1fa493f 100644 --- a/modelviewer.h +++ b/modelviewer.h @@ -1,53 +1,22 @@ #ifndef MODELVIEWER_H #define MODELVIEWER_H -#include #include -#include -#include -#include -#include -#include -#include -// Qt3DCore includes -#include -#include -#include -#include -#include -#include -// Qt3DInput includes -#include -// Qt3DRender includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// Qt3DExtras includes -#include -#include -#include -#include -#include -#include -#include +namespace Ui { +class ModelViewer; +} class ModelViewer : public QWidget { Q_OBJECT + public: explicit ModelViewer(QWidget *parent = nullptr); + ~ModelViewer(); -signals: +private: + Ui::ModelViewer *ui; }; #endif // MODELVIEWER_H diff --git a/modelviewer.ui b/modelviewer.ui new file mode 100644 index 0000000..d321671 --- /dev/null +++ b/modelviewer.ui @@ -0,0 +1,624 @@ + + + ModelViewer + + + + 0 + 0 + 1001 + 897 + + + + Form + + + + + + Properties + + + + + + + + Name Pointer: + + + + + + + 1000000000 + + + + + + + Model Name: + + + + + + + + + + Tag Count: + + + + + + + tags + + + 1000000000 + + + + + + + Root Tag Count: + + + + + + + root tags + + + 1000000000 + + + + + + + Surface Count: + + + + + + + surfaces + + + 1000000000 + + + + + + + Unknown A: + + + + + + + 1000000000 + + + + + + + Bone Name Pointer: + + + + + + + 1000000000 + + + + + + + Parent List Pointer: + + + + + + + 1000000000 + + + + + + + Quats Pointer: + + + + + + + 1000000000 + + + + + + + Transformation Pointer: + + + + + + + 1000000000 + + + + + + + Classification Pointer: + + + + + + + 1000000000 + + + + + + + Base Material Pointer: + + + + + + + 1000000000 + + + + + + + Surfaces Pointer; + + + + + + + 1000000000 + + + + + + + Material Handlers Pointer: + + + + + + + 1000000000 + + + + + + + Coll Surf Pointer: + + + + + + + 1000000000 + + + + + + + Coll Surface Count: + + + + + + + 1000000000 + + + + + + + Contents: + + + + + + + 1000000000 + + + + + + + Bone Info Pointer: + + + + + + + 1000000000 + + + + + + + Radius: + + + + + + + + + + Min X: + + + + + + + + + + Min Y: + + + + + + + + + + Min Z: + + + + + + + + + + Max X: + + + + + + + + + + Max Y: + + + + + + + + + + Max Z: + + + + + + + + + + Lod Count: + + + + + + + 1000000000 + + + + + + + Coll Lod: + + + + + + + 1000000000 + + + + + + + Stream Info Pointer: + + + + + + + 1000000000 + + + + + + + Memory Usage: + + + + + + + 1000000000 + + + + + + + Flags: + + + + + + + 1000000000 + + + + + + + Phys Preset Pointer: + + + + + + + 1000000000 + + + + + + + Phys Geometry Pointer: + + + + + + + 1000000000 + + + + + + + + + + + Lod Info + + + + + + Lod Info Index: + + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + Distance: + + + + + + + + + + Surface Count: + + + + + + + 1000000000 + + + + + + + Surface Index: + + + + + + + 1000000000 + + + + + + + Part Bit 1: + + + + + + + 1000000000 + + + + + + + Part Bit 2: + + + + + + + 1000000000 + + + + + + + Part Bit 3: + + + + + + + 1000000000 + + + + + + + Part Bit 4: + + + + + + + 1000000000 + + + + + + + Part Bit 5: + + + + + + + 1000000000 + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + + + + 3D Window + + + + + + + + diff --git a/preferenceeditor.cpp b/preferenceeditor.cpp new file mode 100644 index 0000000..44b0ae1 --- /dev/null +++ b/preferenceeditor.cpp @@ -0,0 +1,35 @@ +#include "preferenceeditor.h" +#include "ui_preferenceeditor.h" + +PreferenceEditor::PreferenceEditor(QWidget *parent) + : QDialog(parent) + , ui(new Ui::PreferenceEditor) +{ + ui->setupUi(this); + + ui->frame_View->show(); + ui->frame_TreeWidget->hide(); + ui->frame_FileEditors->hide(); + + connect(ui->listWidget_Categories, &QListWidget::itemSelectionChanged, this, [this]() { + const QString itemText = ui->listWidget_Categories->selectedItems().first()->text(); + if (itemText == "View") { + ui->frame_View->show(); + ui->frame_TreeWidget->hide(); + ui->frame_FileEditors->hide(); + } else if (itemText == "Tree Widget") { + ui->frame_View->hide(); + ui->frame_TreeWidget->show(); + ui->frame_FileEditors->hide(); + } else if (itemText == "File Editors") { + ui->frame_View->hide(); + ui->frame_TreeWidget->hide(); + ui->frame_FileEditors->show(); + } + }); +} + +PreferenceEditor::~PreferenceEditor() +{ + delete ui; +} diff --git a/preferenceeditor.h b/preferenceeditor.h new file mode 100644 index 0000000..60ed8c0 --- /dev/null +++ b/preferenceeditor.h @@ -0,0 +1,22 @@ +#ifndef PREFERENCEEDITOR_H +#define PREFERENCEEDITOR_H + +#include + +namespace Ui { +class PreferenceEditor; +} + +class PreferenceEditor : public QDialog +{ + Q_OBJECT + +public: + explicit PreferenceEditor(QWidget *parent = nullptr); + ~PreferenceEditor(); + +private: + Ui::PreferenceEditor *ui; +}; + +#endif // PREFERENCEEDITOR_H diff --git a/preferenceeditor.ui b/preferenceeditor.ui new file mode 100644 index 0000000..ab80157 --- /dev/null +++ b/preferenceeditor.ui @@ -0,0 +1,528 @@ + + + PreferenceEditor + + + + 0 + 0 + 1118 + 861 + + + + + 703 + 512 + + + + Dialog + + + + + + + + + + false + + + + 0 + 0 + + + + + 150 + 16777215 + + + + + Roboto + 10 + + + + Filter + + + + + + + + 0 + 0 + + + + + 150 + 16777215 + + + + + Roboto + 10 + + + + 0 + + + + View + + + + :/icons/icons/Icon_Views.png:/icons/icons/Icon_Views.png + + + + + Tree Widget + + + + :/icons/icons/Icon_Tree.png:/icons/icons/Icon_Tree.png + + + + + File Editors + + + + :/icons/icons/Icon_Editor.png:/icons/icons/Icon_Editor.png + + + + + + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + + + + 30 + 30 + + + + + 30 + 30 + + + + + + + :/icons/icons/Icon_Views.png + + + true + + + + + + + + Roboto + 12 + true + + + + View + + + + + + + + + + Roboto + 10 + + + + 0 + + + + Font && Colors + + + + + + Font + + + + + + Family: + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Size: + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Zoom: + + + + + + + % + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 588 + + + + + + + + + + + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + + + + 30 + 30 + + + + + 30 + 30 + + + + + + + :/icons/icons/Icon_Tree.png + + + true + + + + + + + + Roboto + 12 + true + + + + Tree Widget + + + + + + + + + + Roboto + 10 + + + + 0 + + + + Tab 1 + + + + + Tab 2 + + + + + + + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + + + + 30 + 30 + + + + + 30 + 30 + + + + + + + :/icons/icons/Icon_Editor.png + + + true + + + + + + + + Roboto + 12 + true + + + + File Editors + + + + + + + + + + Roboto + 10 + + + + 7 + + + + Fast File + + + + + Zone File + + + + + Images + + + + + Local Strings + + + + + String Table + + + + + Sounds + + + + + Tech Set + + + + + Model + + + + + Page + + + + + + + + + + + + + + + + + + + + + + + + + + + + Roboto + 10 + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + + + buttonBox + accepted() + PreferenceEditor + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PreferenceEditor + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/soundviewer.cpp b/soundviewer.cpp new file mode 100644 index 0000000..9007e9f --- /dev/null +++ b/soundviewer.cpp @@ -0,0 +1,80 @@ +#include "soundviewer.h" +#include "ui_soundviewer.h" + +SoundViewer::SoundViewer(QWidget *parent) + : QWidget(parent) + , ui(new Ui::SoundViewer) + , player(new QMediaPlayer()) + , buffer(new QBuffer()) +{ + ui->setupUi(this); + + connect(ui->pushButton_Play, &QPushButton::clicked, player, &QMediaPlayer::play); + connect(ui->pushButton_Pause, &QPushButton::clicked, player, &QMediaPlayer::pause); + connect(ui->pushButton_Stop, &QPushButton::clicked, this, [this]() { + if (player->isPlaying()) { + player->stop(); + } + }); + connect(ui->pushButton_SkipForward, &QPushButton::clicked, this, [this]() { + player->setPosition(player->position() + 30); + }); + connect(ui->pushButton_SkipBack, &QPushButton::clicked, this, [this]() { + player->setPosition(player->position() - 30); + }); + connect(player, &QMediaPlayer::positionChanged, player, [this](qint64 position) { + ui->horizontalSlider->setSliderPosition(position); + ui->label_Time->setText(QString("%1:%2:%3") + .arg(position / 60000) + .arg((position % 60000) / 1000) + .arg(position % 1000)); + }); + connect(player, &QMediaPlayer::durationChanged, player, [this](qint64 duration) { + ui->horizontalSlider->setMaximum(duration); + ui->label_TimeMax->setText(QString("%1:%2:%3") + .arg(duration / 60000) + .arg((duration % 60000) / 1000) + .arg(duration % 1000)); + }); + connect(ui->horizontalSlider, &QSlider::sliderMoved, this, [this](int position) { + player->setPosition(position); + }); + + for (auto outputDevice : QMediaDevices::audioOutputs()) { + ui->comboBox_Output->addItem(outputDevice.description()); + } + connect(ui->comboBox_Output, &QComboBox::currentIndexChanged, this, [this](int index) { + auto outputDevice = QMediaDevices::audioOutputs()[index]; + QAudioOutput *audioOutput = new QAudioOutput(outputDevice); + player->setAudioOutput(audioOutput); + }); + + auto outputDevice = QMediaDevices::defaultAudioOutput(); + QAudioOutput *audioOutput = new QAudioOutput(outputDevice); + player->setAudioOutput(audioOutput); +} + +SoundViewer::~SoundViewer() +{ + delete buffer; + delete player; + delete ui; +} + +void SoundViewer::SetSound(std::shared_ptr aSound) +{ + buffer->setData(aSound->data); + if (!buffer->open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open QBuffer."; + return; + } + + ui->groupBox->setTitle(aSound->path); + player->setSourceDevice(buffer); +} + +void SoundViewer::SetOutput(QAudioOutput *aOutput) { + if (!aOutput) { return; } + + player->setAudioOutput(aOutput); +} diff --git a/soundviewer.h b/soundviewer.h new file mode 100644 index 0000000..825c28c --- /dev/null +++ b/soundviewer.h @@ -0,0 +1,34 @@ +#ifndef SOUNDVIEWER_H +#define SOUNDVIEWER_H + +#include "asset_structs.h" + +#include +#include +#include +#include +#include +#include + +namespace Ui { +class SoundViewer; +} + +class SoundViewer : public QWidget +{ + Q_OBJECT + +public: + explicit SoundViewer(QWidget *parent = nullptr); + ~SoundViewer(); + + void SetSound(std::shared_ptr aSound); + + void SetOutput(QAudioOutput *aOutput); +private: + Ui::SoundViewer *ui; + QMediaPlayer *player; + QBuffer *buffer; +}; + +#endif // SOUNDVIEWER_H diff --git a/soundviewer.ui b/soundviewer.ui new file mode 100644 index 0000000..e6b290a --- /dev/null +++ b/soundviewer.ui @@ -0,0 +1,2573 @@ + + + SoundViewer + + + + 0 + 0 + 294 + 198 + + + + Form + + + + + + + 0 + 160 + + + + + 16777215 + 131 + + + + Player + + + + + + + + Output Device: + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 40 + 40 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + + + + + + :/icons/icons/Icon_SkipBack.png:/icons/icons/Icon_SkipBack.png + + + + 40 + 40 + + + + + + + + + 40 + 40 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + + + + + + :/icons/icons/Icon_Stop.png:/icons/icons/Icon_Stop.png + + + + 40 + 40 + + + + + + + + + 50 + 50 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + + + + + + :/icons/icons/Icon_Play.png:/icons/icons/Icon_Play.png + + + + 50 + 50 + + + + + + + + + 40 + 40 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + + + + + + :/icons/icons/Icon_Pause.png:/icons/icons/Icon_Pause.png + + + + 40 + 40 + + + + + + + + + 40 + 40 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 247 + 247 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 120 + 120 + 120 + + + + + + + 160 + 160 + 160 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 240 + 240 + 240 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 120 + 120 + 120 + + + + + + + 255 + 255 + 255 + + + + + + + + + + + + :/icons/icons/Icon_SkipForward.png:/icons/icons/Icon_SkipForward.png + + + + 40 + 40 + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 00:00:00 + + + + + + + Qt::Orientation::Horizontal + + + + + + + 00:00:00 + + + + + + + + + + + + + + + diff --git a/stringtableviewer.cpp b/stringtableviewer.cpp new file mode 100644 index 0000000..b71b8e5 --- /dev/null +++ b/stringtableviewer.cpp @@ -0,0 +1,36 @@ +#include "stringtableviewer.h" +#include "ui_stringtableviewer.h" + +StringTableViewer::StringTableViewer(QWidget *parent) + : QWidget(parent) + , ui(new Ui::StringTableViewer) +{ + ui->setupUi(this); +} + +StringTableViewer::~StringTableViewer() +{ + delete ui; +} + +void StringTableViewer::SetStringTable(std::shared_ptr aStringTable) { + ui->tableWidget_Strings->clear(); + + ui->tableWidget_Strings->setRowCount(aStringTable->rowCount); + ui->tableWidget_Strings->setColumnCount(aStringTable->columnCount); + + int currentIndex = 0; + for (const QString &key : aStringTable->content.keys()) { + const QString value = aStringTable->content[key]; + + QTableWidgetItem *tableKeyItem = new QTableWidgetItem(); + tableKeyItem->setText(key); + ui->tableWidget_Strings->setItem(currentIndex, 0, tableKeyItem); + + QTableWidgetItem *tableValItem = new QTableWidgetItem(); + tableValItem->setText(value); + ui->tableWidget_Strings->setItem(currentIndex, 1, tableValItem); + + currentIndex++; + } +} diff --git a/stringtableviewer.h b/stringtableviewer.h new file mode 100644 index 0000000..29dc08a --- /dev/null +++ b/stringtableviewer.h @@ -0,0 +1,25 @@ +#ifndef STRINGTABLEVIEWER_H +#define STRINGTABLEVIEWER_H + +#include "asset_structs.h" +#include + +namespace Ui { +class StringTableViewer; +} + +class StringTableViewer : public QWidget +{ + Q_OBJECT + +public: + explicit StringTableViewer(QWidget *parent = nullptr); + ~StringTableViewer(); + + void SetStringTable(std::shared_ptr aStringTable); + +private: + Ui::StringTableViewer *ui; +}; + +#endif // STRINGTABLEVIEWER_H diff --git a/stringtableviewer.ui b/stringtableviewer.ui new file mode 100644 index 0000000..55ce6e0 --- /dev/null +++ b/stringtableviewer.ui @@ -0,0 +1,24 @@ + + + StringTableViewer + + + + 0 + 0 + 525 + 752 + + + + Form + + + + + + + + + + diff --git a/utils.h b/utils.h index 49c21dd..f7e9117 100644 --- a/utils.h +++ b/utils.h @@ -10,6 +10,74 @@ class Utils { public: + static bool ReadUntilString(QDataStream* stream, const QString& targetString) { + if (!stream || targetString.isEmpty()) { + return false; // Invalid input + } + + QByteArray buffer; + QByteArray targetBytes = targetString.toUtf8(); // Handle multibyte characters + const int targetLength = targetBytes.size(); + qDebug() << targetBytes << targetLength; + + // Read as unsigned bytes to handle all possible values (0-255) + unsigned char byte; + while (!stream->atEnd()) { + // Read one byte at a time + *stream >> byte; + buffer.append(static_cast(byte)); // Append as char for QByteArray + + // Keep buffer size limited to the target length + if (buffer.size() > targetLength) { + buffer.remove(0, 1); + } + + // Check if the buffer matches the target string in raw bytes + if (buffer == targetBytes) { + // Backup to the start of the matched string + stream->device()->seek(stream->device()->pos() - targetLength); + return true; + } + } + + // Target string not found + return false; + } + + static bool ReadUntilHex(QDataStream* stream, const QString& hexString) { + if (!stream || hexString.isEmpty() || hexString.size() % 2 != 0) { + return false; // Invalid input + } + + // Convert hex string to byte array + QByteArray targetBytes = QByteArray::fromHex(hexString.toUtf8()); + const int targetLength = targetBytes.size(); + + QByteArray buffer; + unsigned char byte; + + while (!stream->atEnd()) { + // Read one byte at a time + *stream >> byte; + buffer.append(static_cast(byte)); // Append as char for QByteArray + + // Keep buffer size limited to the target length + if (buffer.size() > targetLength) { + buffer.remove(0, 1); + } + + // Check if the buffer matches the target byte sequence + if (buffer == targetBytes) { + // Backup to the start of the matched sequence + stream->device()->seek(stream->device()->pos() - targetLength); + return true; + } + } + + // Target sequence not found + return false; + } + /* AssetTypeToString() diff --git a/xtreewidget.cpp b/xtreewidget.cpp index 691aba7..a3fd0c2 100644 --- a/xtreewidget.cpp +++ b/xtreewidget.cpp @@ -13,11 +13,20 @@ XTreeWidget::XTreeWidget(QWidget *parent) setContextMenuPolicy(Qt::CustomContextMenu); setSelectionMode(QTreeWidget::SingleSelection); setColumnCount(3); - setColumnWidth(0, 275); - setColumnWidth(1, 50); - setColumnWidth(2, 50); header()->hide(); setMinimumWidth(350); + setSortingEnabled(true); + + header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + + // Set the last two columns to a fixed width + //header()->setSectionResizeMode(1, QHeaderView::Fixed); + //header()->setSectionResizeMode(2, QHeaderView::Fixed); + + // Adjust the fixed widths to suit your icon size (e.g., 32 pixels) + //header()->resizeSection(0, 275); + //header()->resizeSection(1, 32); + //header()->resizeSection(2, 32); connect(this, &QTreeWidget::itemSelectionChanged, this, &XTreeWidget::ItemSelectionChanged); connect(this, &XTreeWidget::customContextMenuRequested, this, &XTreeWidget::PrepareContextMenu); @@ -28,7 +37,7 @@ XTreeWidget::~XTreeWidget() { } void XTreeWidget::AddFastFile(std::shared_ptr aFastFile) { - QTreeWidgetItem *fastFileItem = new QTreeWidgetItem(this); + XTreeWidgetItem *fastFileItem = new XTreeWidgetItem(this); fastFileItem->setText(0, aFastFile->GetFileStem()); fastFileItem->setIcon(0, QIcon(":/icons/icons/Icon_FastFile.png")); if (aFastFile->GetPlatform() == "PC") { @@ -55,14 +64,15 @@ void XTreeWidget::AddFastFile(std::shared_ptr aFastFile) { mFastFiles[aFastFile->GetFileStem().section(".", 0, 0)] = aFastFile; resizeColumnToContents(1); + setSortingEnabled(true); } -void XTreeWidget::AddZoneFile(std::shared_ptr aZoneFile, QTreeWidgetItem *aParentItem) { - QTreeWidgetItem *zoneItem; +void XTreeWidget::AddZoneFile(std::shared_ptr aZoneFile, XTreeWidgetItem *aParentItem) { + XTreeWidgetItem *zoneItem; if (aParentItem != nullptr) { - zoneItem = new QTreeWidgetItem(aParentItem); + zoneItem = new XTreeWidgetItem(aParentItem); } else { - zoneItem = new QTreeWidgetItem(this); + zoneItem = new XTreeWidgetItem(this); } zoneItem->setIcon(0, QIcon(":/icons/icons/Icon_ZoneFile.png")); zoneItem->setText(0, aZoneFile->GetFileStem()); @@ -70,40 +80,39 @@ void XTreeWidget::AddZoneFile(std::shared_ptr aZoneFile, QTreeWidgetIt auto assetMap = aZoneFile->GetAssetMap(); if (!assetMap.localStrings.isEmpty()) { - QTreeWidgetItem *localStrRoot = new QTreeWidgetItem(zoneItem); + XTreeWidgetItem *localStrRoot = new XTreeWidgetItem(zoneItem); localStrRoot->setText(0, "String Files"); localStrRoot->setIcon(0, QIcon(":/icons/icons/Icon_StringFile.png")); - QTreeWidgetItem *localStrItem = new QTreeWidgetItem(localStrRoot); + XTreeWidgetItem *localStrItem = new XTreeWidgetItem(localStrRoot); localStrItem->setText(0, aZoneFile->GetFileStem().section('.', 0, 0) + ".str"); localStrItem->setIcon(0, QIcon(":/icons/icons/Icon_StringFile.png")); } if (!assetMap.techSets.isEmpty()) { - QTreeWidgetItem *techSetRoot = new QTreeWidgetItem(zoneItem); + XTreeWidgetItem *techSetRoot = new XTreeWidgetItem(zoneItem); techSetRoot->setText(0, "Tech Sets"); techSetRoot->setIcon(0, QIcon(":/icons/icons/Icon_TechSetFile.png")); for (TechSet techSet : assetMap.techSets) { - QTreeWidgetItem *techSetItem = new QTreeWidgetItem(techSetRoot); + XTreeWidgetItem *techSetItem = new XTreeWidgetItem(techSetRoot); techSetItem->setText(0, techSet.name); techSetItem->setIcon(0, QIcon(":/icons/icons/Icon_TechSetFile.png")); } } if (!assetMap.rawFiles.isEmpty()) { - QTreeWidgetItem *rawFileRoot = new QTreeWidgetItem(zoneItem); + XTreeWidgetItem *rawFileRoot = new XTreeWidgetItem(zoneItem); rawFileRoot->setText(0, "Raw Files"); rawFileRoot->setIcon(0, QIcon(":/icons/icons/Icon_GSCFile.png")); for (RawFile rawFile : assetMap.rawFiles) { if (!rawFile.length) { continue; } - QTreeWidgetItem *tempItem = rawFileRoot; + XTreeWidgetItem *tempItem = rawFileRoot; for (const QString &pathPart : rawFile.path.split('/')) { bool childFound = false; for (int i = 0; i < tempItem->childCount(); i++) { - QTreeWidgetItem *childItem = tempItem->child(i); - //qDebug() << "Child text: " << childItem->text(0); + XTreeWidgetItem *childItem = dynamic_cast(tempItem->child(i)); if (childItem->text(0) == pathPart) { tempItem = childItem; @@ -113,12 +122,12 @@ void XTreeWidget::AddZoneFile(std::shared_ptr aZoneFile, QTreeWidgetIt } if (pathPart.contains(".gsc")) { - QTreeWidgetItem *rawFileItem = new QTreeWidgetItem(tempItem); + XTreeWidgetItem *rawFileItem = new XTreeWidgetItem(tempItem); rawFileItem->setText(0, pathPart); tempItem = rawFileItem; } else if (!childFound) { - tempItem = new QTreeWidgetItem(tempItem); + tempItem = new XTreeWidgetItem(tempItem); tempItem->setText(0, pathPart); } @@ -128,16 +137,16 @@ void XTreeWidget::AddZoneFile(std::shared_ptr aZoneFile, QTreeWidgetIt } if (!assetMap.menuFiles.isEmpty()) { - QTreeWidgetItem *menuRoot = new QTreeWidgetItem(zoneItem); + XTreeWidgetItem *menuRoot = new XTreeWidgetItem(zoneItem); menuRoot->setText(0, "Menu Files"); menuRoot->setIcon(0, QIcon(":/icons/icons/Icon_MenuFile.png")); int menuIndex = 1; for (MenuFile menuFile : assetMap.menuFiles) { - QTreeWidgetItem *menuFileRoot = new QTreeWidgetItem(menuRoot); + XTreeWidgetItem *menuFileRoot = new XTreeWidgetItem(menuRoot); menuFileRoot->setText(0, QString("Menu %1").arg(menuIndex)); for (Menu menu : menuFile.menuDefs) { - QTreeWidgetItem *menuItem = new QTreeWidgetItem(menuFileRoot); + XTreeWidgetItem *menuItem = new XTreeWidgetItem(menuFileRoot); menuItem->setText(0, menu.name); menuItem->setIcon(0, QIcon(":/icons/icons/Icon_MenuFile.png")); } @@ -146,17 +155,81 @@ void XTreeWidget::AddZoneFile(std::shared_ptr aZoneFile, QTreeWidgetIt } if (!assetMap.images.isEmpty()) { - QTreeWidgetItem *imageRoot = new QTreeWidgetItem(zoneItem); + XTreeWidgetItem *imageRoot = new XTreeWidgetItem(zoneItem); imageRoot->setText(0, "Images"); imageRoot->setIcon(0, QIcon(":/icons/icons/Icon_Image.png")); for (Image image : assetMap.images) { - QTreeWidgetItem *imageItem = new QTreeWidgetItem(imageRoot); + XTreeWidgetItem *imageItem = new XTreeWidgetItem(imageRoot); imageItem->setText(0, image.materialName); imageItem->setIcon(0, QIcon(":/icons/icons/Icon_Image.png")); } } + if (!assetMap.models.isEmpty()) { + XTreeWidgetItem *modelsRoot = new XTreeWidgetItem(zoneItem); + modelsRoot->setText(0, "Models"); + modelsRoot->setIcon(0, QIcon(":/icons/icons/Icon_Model.png")); + + for (Model model: assetMap.models) { + XTreeWidgetItem *modelItem = new XTreeWidgetItem(modelsRoot); + modelItem->setText(0, model.modelName); + modelItem->setIcon(0, QIcon(":/icons/icons/Icon_Model.png")); + } + } + + if (!assetMap.stringTables.isEmpty()) { + XTreeWidgetItem *strTableRoot = new XTreeWidgetItem(zoneItem); + strTableRoot->setText(0, "String Tables"); + strTableRoot->setIcon(0, QIcon(":/icons/icons/Icon_StringTable.png")); + + for (StringTable strTable: assetMap.stringTables) { + XTreeWidgetItem *modelItem = new XTreeWidgetItem(strTableRoot); + modelItem->setText(0, strTable.name); + modelItem->setIcon(0, QIcon(":/icons/icons/Icon_StringTable.png")); + } + } + + if (!assetMap.sounds.isEmpty()) { + XTreeWidgetItem *soundsRoot = new XTreeWidgetItem(zoneItem); + soundsRoot->setText(0, "Sounds"); + soundsRoot->setIcon(0, QIcon(":/icons/icons/Icon_Sound.png")); + for (SoundAsset soundAsset : assetMap.sounds) { + for (Sound sound : soundAsset.sounds) { + XTreeWidgetItem *tempItem = soundsRoot; + + if (!sound.dataLength) { continue; } + + for (const QString &pathPart : sound.path.split('/')) { + if (pathPart.isEmpty()) { continue; } + + bool childFound = false; + for (int i = 0; i < tempItem->childCount(); i++) { + XTreeWidgetItem *childItem = dynamic_cast(tempItem->child(i)); + if (childItem->text(0) == pathPart) { + tempItem = childItem; + + childFound = true; + break; + } + } + + if (pathPart.contains(".wav")) { + XTreeWidgetItem *soundItem = new XTreeWidgetItem(tempItem); + soundItem->setText(0, pathPart); + + tempItem = soundItem; + } else if (!childFound) { + tempItem = new XTreeWidgetItem(tempItem); + tempItem->setText(0, pathPart); + } + + } + tempItem->setIcon(0, QIcon(":/icons/icons/Icon_Sound.png")); + } + } + } + mZoneFiles[aZoneFile->GetFileStem().section(".", 0, 0)] = aZoneFile; } @@ -265,6 +338,8 @@ void XTreeWidget::PrepareContextMenu(const QPoint &pos) { Q_UNUSED(checked); clear(); + + emit Cleared(); }); QAction *closeAllButAction = new QAction("Close All BUT This"); @@ -358,6 +433,98 @@ void XTreeWidget::PrepareContextMenu(const QPoint &pos) { //zoneFile->SaveFastFile(); }); + } else if (activeItem && activeText.contains(".wav")) { + XTreeWidgetItem *parentItem = dynamic_cast(activeItem->parent()); + while (parentItem && !parentItem->text(0).contains(".zone")) { + parentItem = dynamic_cast(parentItem->parent()); + + if (parentItem == invisibleRootItem()) { + break; + } + } + if (parentItem && parentItem != invisibleRootItem() && parentItem->text(0).contains(".zone")) { + const QString fileStem = parentItem->text(0).section('.', 0, 0); + QVector soundAssets = mZoneFiles[fileStem]->GetAssetMap().sounds; + for (SoundAsset soundAsset : soundAssets) { + for (Sound sound : soundAsset.sounds) { + if (sound.path.contains(activeText)) { + QMenu *exportSubmenu = new QMenu("Export...", this); + contextMenu->addMenu(exportSubmenu); + + QAction *exportWAVAction = new QAction("Export as WAV File"); + exportSubmenu->addAction(exportWAVAction); + connect(exportWAVAction, &QAction::triggered, this, [sound](bool checked) { + Q_UNUSED(checked); + + QDir dir = QDir::currentPath(); + if (!dir.exists("exports/")) { + dir.mkdir("exports/"); + } + + if (!dir.exists("exports/sounds/")) { + dir.mkdir("exports/sounds/"); + } + + const QString fileName = "exports/sounds/" + sound.path.split('/').last(); + QFile wavFile(fileName); + if (!wavFile.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to write wav file!"; + return; + } + wavFile.write(sound.data); + wavFile.close(); + }); + break; + } + } + } + } + } else if (activeItem && activeText == "Sounds") { + XTreeWidgetItem *parentItem = dynamic_cast(activeItem->parent()); + while (parentItem && !parentItem->text(0).contains(".zone")) { + parentItem = dynamic_cast(parentItem->parent()); + + if (parentItem == invisibleRootItem()) { + break; + } + } + if (parentItem && parentItem != invisibleRootItem() && parentItem->text(0).contains(".zone")) { + const QString fileStem = parentItem->text(0).section('.', 0, 0); + auto zoneFile = mZoneFiles[fileStem]; + + QMenu *exportSubmenu = new QMenu("Export...", this); + contextMenu->addMenu(exportSubmenu); + + QAction *exportAllWAVAction = new QAction("Export ALL as WAV Files"); + exportSubmenu->addAction(exportAllWAVAction); + connect(exportAllWAVAction, &QAction::triggered, this, [zoneFile](bool checked) { + Q_UNUSED(checked); + + for (SoundAsset soundAsset : zoneFile->GetAssetMap().sounds) { + for (Sound sound : soundAsset.sounds) { + if (!sound.dataLength) { continue; } + + QDir dir = QDir::currentPath(); + if (!dir.exists("exports/")) { + dir.mkdir("exports/"); + } + + if (!dir.exists("exports/sounds/")) { + dir.mkdir("exports/sounds/"); + } + + const QString fileName = "exports/sounds/" + sound.path.split('/').last(); + QFile wavFile(fileName); + if (!wavFile.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to write wav file!"; + return; + } + wavFile.write(sound.data); + wavFile.close(); + } + } + }); + } } QPoint pt(pos); @@ -369,13 +536,15 @@ void XTreeWidget::PrepareContextMenu(const QPoint &pos) { void XTreeWidget::ItemSelectionChanged() { if (selectedItems().isEmpty()) { return; } - QTreeWidgetItem *selectedItem = selectedItems().first(); + XTreeWidgetItem *selectedItem = dynamic_cast(selectedItems().first()); if (!selectedItem) { return; } if (selectedItem->text(0).isEmpty()) { return; } QString selectedText = selectedItem->text(0); + emit TabSelected(selectedText); + const QString fileStem = selectedText.section(".", 0, 0); - QTreeWidgetItem *parentItem = selectedItem->parent(); + XTreeWidgetItem *parentItem = dynamic_cast(selectedItem->parent()); if (selectedText.contains(".dds")) { if (!mDDSFiles.contains(fileStem)) { @@ -409,11 +578,11 @@ void XTreeWidget::ItemSelectionChanged() { } emit LocalStringSelected(mZoneFiles[fileStem]); } else if (selectedText.contains(".gsc")) { - QTreeWidgetItem *zoneRoot = selectedItem; + XTreeWidgetItem *zoneRoot = selectedItem; if (!zoneRoot) { return; } while (!zoneRoot->text(0).contains(".zone")) { - zoneRoot = zoneRoot->parent(); + zoneRoot = dynamic_cast(zoneRoot->parent()); if (!zoneRoot) { return; } } @@ -431,7 +600,7 @@ void XTreeWidget::ItemSelectionChanged() { } } } else if (parentItem && (parentItem->text(0) == "Images")) { - QTreeWidgetItem *grandpaItem = parentItem->parent(); + XTreeWidgetItem *grandpaItem = dynamic_cast(parentItem->parent()); if (grandpaItem && grandpaItem->text(0).contains(".zone")) { const QString fileStem = grandpaItem->text(0).section('.', 0, 0); QVector images = mZoneFiles[fileStem]->GetAssetMap().images; @@ -443,7 +612,7 @@ void XTreeWidget::ItemSelectionChanged() { } } } else if (parentItem && (parentItem->text(0) == "Tech Sets")) { - QTreeWidgetItem *grandpaItem = parentItem->parent(); + XTreeWidgetItem *grandpaItem = dynamic_cast(parentItem->parent()); if (grandpaItem && grandpaItem->text(0).contains(".zone")) { const QString fileStem = grandpaItem->text(0).section('.', 0, 0); auto techsets = mZoneFiles[fileStem]->GetAssetMap().techSets; @@ -454,6 +623,39 @@ void XTreeWidget::ItemSelectionChanged() { } } } + } else if (parentItem && (parentItem->text(0) == "String Tables")) { + XTreeWidgetItem *grandpaItem = dynamic_cast(parentItem->parent()); + if (grandpaItem && grandpaItem->text(0).contains(".zone")) { + const QString fileStem = grandpaItem->text(0).section('.', 0, 0); + QVector strTables = mZoneFiles[fileStem]->GetAssetMap().stringTables; + for (StringTable strTable : strTables) { + if (strTable.name == selectedText) { + emit StrTableSelected(std::make_shared(strTable)); + break; + } + } + } + } else if (parentItem && selectedText.contains(".wav")) { + XTreeWidgetItem *grandpaItem = dynamic_cast(parentItem->parent()); + while (grandpaItem && !grandpaItem->text(0).contains(".zone")) { + grandpaItem = dynamic_cast(grandpaItem->parent()); + + if (grandpaItem == invisibleRootItem()) { + break; + } + } + if (grandpaItem && grandpaItem != invisibleRootItem() && grandpaItem->text(0).contains(".zone")) { + const QString fileStem = grandpaItem->text(0).section('.', 0, 0); + QVector soundAssets = mZoneFiles[fileStem]->GetAssetMap().sounds; + for (SoundAsset soundAsset : soundAssets) { + for (Sound sound : soundAsset.sounds) { + if (sound.path.contains(selectedText)) { + emit SoundSelected(std::make_shared(sound)); + break; + } + } + } + } } } @@ -485,7 +687,7 @@ void XTreeWidget::AddIWIFile(std::shared_ptr aIWIFile) { } } - QTreeWidgetItem *iwiItem = new QTreeWidgetItem(this); + XTreeWidgetItem *iwiItem = new XTreeWidgetItem(this); iwiItem->setIcon(0, QIcon(":/icons/icons/Icon_IWIFile.png")); iwiItem->setText(0, iwiFileName); mIWIFiles[aIWIFile->fileStem.section(".", 0, 0)] = aIWIFile; @@ -501,7 +703,7 @@ void XTreeWidget::AddDDSFile(std::shared_ptr aDDSFile) { } } - QTreeWidgetItem *ddsItem = new QTreeWidgetItem(this); + XTreeWidgetItem *ddsItem = new XTreeWidgetItem(this); ddsItem->setIcon(0, QIcon(":/icons/icons/Icon_DDSFile.png")); ddsItem->setText(0, ddsFileName); mDDSFiles[aDDSFile->fileStem.section(".", 0, 0)] = aDDSFile; diff --git a/xtreewidget.h b/xtreewidget.h index 0d49910..c134576 100644 --- a/xtreewidget.h +++ b/xtreewidget.h @@ -6,6 +6,7 @@ #include "ddsfile.h" #include "iwifile.h" #include "fastfile.h" +#include "xtreewidgetitem.h" #include "zonefile.h" #include @@ -18,7 +19,7 @@ public: ~XTreeWidget(); void AddFastFile(std::shared_ptr aFastFile); - void AddZoneFile(std::shared_ptr aZoneFile, QTreeWidgetItem *aParentItem = nullptr); + void AddZoneFile(std::shared_ptr aZoneFile, XTreeWidgetItem *aParentItem = nullptr); void AddIWIFile(std::shared_ptr aIWIFile); void AddDDSFile(std::shared_ptr aDDSFile); @@ -31,7 +32,11 @@ signals: void RawFileSelected(std::shared_ptr aRawFile); void ImageSelected(std::shared_ptr aImage); void TechSetSelected(std::shared_ptr aZoneFile); + void StrTableSelected(std::shared_ptr aStrTable); void MenuSelected(std::shared_ptr aMenu); + void SoundSelected(std::shared_ptr aSound); + void TabSelected(const QString aTabName); + void Cleared(); protected: void ItemSelectionChanged(); diff --git a/xtreewidgetitem.cpp b/xtreewidgetitem.cpp new file mode 100644 index 0000000..af9d707 --- /dev/null +++ b/xtreewidgetitem.cpp @@ -0,0 +1,37 @@ +#include "xtreewidgetitem.h" + +XTreeWidgetItem::XTreeWidgetItem(QTreeWidget *parent, bool group) + : QTreeWidgetItem(parent), isGroup(group) { + +} + +XTreeWidgetItem::XTreeWidgetItem(QTreeWidgetItem *parent, bool group) + : QTreeWidgetItem(parent), isGroup(group) { + +} + +bool XTreeWidgetItem::operator<(const QTreeWidgetItem &other) const { + // Attempt to cast the other item to our custom type. + const XTreeWidgetItem* otherItem = dynamic_cast(&other); + if (otherItem) { + if ((this->childCount() > 0) != (otherItem->childCount() > 0)) + return this->childCount() <= 0; // true if this item is a group and other is not + } + // Fallback to the default string comparison on the current sort column. + return !QTreeWidgetItem::operator<(other); +} + +XTreeWidgetItem& XTreeWidgetItem::operator=(const XTreeWidgetItem &other) +{ + if (this != &other) { + // Copy text and icon for each column. + const int colCount = other.columnCount(); + for (int i = 0; i < colCount; ++i) { + setText(i, other.text(i)); + setIcon(i, other.icon(i)); + } + // Copy custom members. + this->isGroup = other.isGroup; + } + return *this; +} diff --git a/xtreewidgetitem.h b/xtreewidgetitem.h new file mode 100644 index 0000000..e189d04 --- /dev/null +++ b/xtreewidgetitem.h @@ -0,0 +1,24 @@ +#ifndef XTREEWIDGETITEM_H +#define XTREEWIDGETITEM_H + +#include +#include + +// Custom item class +class XTreeWidgetItem : public QTreeWidgetItem +{ +public: + // Flag to indicate if the item is a collapsible group/header. + bool isGroup; + + // Constructors: default to non-group unless specified. + XTreeWidgetItem(QTreeWidget *parent, bool group = false); + XTreeWidgetItem(QTreeWidgetItem *parent, bool group = false); + + // Override the less-than operator to customize sorting. + bool operator<(const QTreeWidgetItem &other) const override; + XTreeWidgetItem &operator =(const XTreeWidgetItem &other); +}; + + +#endif // XTREEWIDGETITEM_H diff --git a/zonefile.cpp b/zonefile.cpp index e423d78..1044041 100644 --- a/zonefile.cpp +++ b/zonefile.cpp @@ -69,7 +69,9 @@ bool ZoneFile::Load(const QString aFilePath, FF_PLATFORM platform) { return true; } -bool ZoneFile::Load(const QByteArray aFileData, const QString aFileStem, FF_PLATFORM platform) { +bool ZoneFile::Load(const QByteArray aFileData, const QString aFileStem, FF_PLATFORM platform, FF_GAME game) { + fileStem = aFileStem; + // Open zone file as little endian stream QDataStream zoneFileStream(aFileData); if (platform == FF_PLATFORM_PC) { @@ -79,12 +81,11 @@ bool ZoneFile::Load(const QByteArray aFileData, const QString aFileStem, FF_PLAT } // Parse data from zone file header - pParseZoneHeader(&zoneFileStream); - fileStem = aFileStem; + pParseZoneHeader(&zoneFileStream, game); records = - pParseZoneIndex(&zoneFileStream, recordCount); + pParseZoneIndex(&zoneFileStream, recordCount, game); assetMap = - pParseAssets(&zoneFileStream, records); + pParseAssets(&zoneFileStream, records, game); return true; } @@ -117,23 +118,66 @@ AssetMap ZoneFile::GetAssetMap() { return assetMap; } -void ZoneFile::pParseZoneHeader(QDataStream *aZoneFileStream) { - size = pParseZoneSize(aZoneFileStream); - pParseZoneUnknownsA(aZoneFileStream); +void ZoneFile::pParseZoneHeader(QDataStream *aZoneFileStream, FF_GAME game) { + if (game != FF_GAME_COD2) { + size = pParseZoneSize(aZoneFileStream); + pParseZoneUnknownsA(aZoneFileStream); + } tagCount = pParseZoneTagCount(aZoneFileStream); - pParseZoneUnknownsB(aZoneFileStream); + quint32 extraCount; + if (game == FF_GAME_COD2) { + aZoneFileStream->skipRawData(4); + *aZoneFileStream >> extraCount; + qDebug() << "Extra Count: " << extraCount; + aZoneFileStream->skipRawData(4); + } else { + pParseZoneUnknownsB(aZoneFileStream); + } recordCount = pParseZoneRecordCount(aZoneFileStream); if (tagCount) { - pParseZoneUnknownsC(aZoneFileStream); - if (tagCount > 1) { - tags = pParseZoneTags(aZoneFileStream, tagCount); + if (game == FF_GAME_COD2) { + + } else { + pParseZoneUnknownsC(aZoneFileStream); } + tags = pParseZoneTags(aZoneFileStream, tagCount, game); } else { aZoneFileStream->skipRawData(4); } + + int thingCount = 0; + if (game == FF_GAME_COD2) { + if (extraCount != 4294967295) { + qDebug() << "Pre Pos: " << aZoneFileStream->device()->pos(); + for (int i = 0; i < extraCount; i++) { + quint32 thing; + *aZoneFileStream >> thing; + + if (thing == 4294967295) { + thingCount++; + } + } + qDebug() << "Post Pos: " << aZoneFileStream->device()->pos(); + qDebug() << "Thing Count: " << thingCount; + + QStringList tags2; + QString tag; + char zoneTagChar; + for (quint32 i = 0; i < thingCount; i++) { + *aZoneFileStream >> zoneTagChar; + while (zoneTagChar != 0) { + tag += zoneTagChar; + *aZoneFileStream >> zoneTagChar; + } + tags2 << tag; + tag.clear(); + } + qDebug() << tags2; + } + } } quint32 ZoneFile::pParseZoneSize(QDataStream *aZoneFileStream) { @@ -235,11 +279,15 @@ void ZoneFile::pParseZoneUnknownsC(QDataStream *aZoneFileStream) { Parses the string tags ate the start of zone file */ -QStringList ZoneFile::pParseZoneTags(QDataStream *aZoneFileStream, quint32 tagCount) { +QStringList ZoneFile::pParseZoneTags(QDataStream *aZoneFileStream, quint32 tagCount, FF_GAME game) { QStringList tags; // Byte 48-51: Repeated separators? ÿÿÿÿ x i - aZoneFileStream->skipRawData(4 * (tagCount - 1)); + if (game == FF_GAME_COD2) { + aZoneFileStream->skipRawData(4 * (tagCount + 1)); + } else { + aZoneFileStream->skipRawData(4 * (tagCount - 1)); + } // Parse tags/strings before index QString zoneTag; @@ -252,9 +300,6 @@ QStringList ZoneFile::pParseZoneTags(QDataStream *aZoneFileStream, quint32 tagCo } tags << zoneTag; zoneTag.clear(); - - qDebug() << "Peek: " << aZoneFileStream->device()->peek(8) << aZoneFileStream->device()->peek(8).contains(QByteArray::fromHex("FFFFFFFF")); - if (aZoneFileStream->device()->peek(8).contains(QByteArray::fromHex("FFFFFFFF"))) { break; } } return tags; } @@ -264,12 +309,16 @@ QStringList ZoneFile::pParseZoneTags(QDataStream *aZoneFileStream, quint32 tagCo Parse the binary zone index data and populate table */ -QStringList ZoneFile::pParseZoneIndex(QDataStream *aZoneFileStream, quint32 recordCount) { +QStringList ZoneFile::pParseZoneIndex(QDataStream *aZoneFileStream, quint32 recordCount, FF_GAME game) { QStringList result; // Don't parse if no records if (!recordCount) { return result; } + if (aZoneFileStream->device()->peek(4).toHex().contains("ffff")) { + aZoneFileStream->device()->seek(aZoneFileStream->device()->pos() - 2); + } + // Parse index & map found asset types for (quint32 i = 0; i <= recordCount; i++) { // Skip record start @@ -283,7 +332,7 @@ QStringList ZoneFile::pParseZoneIndex(QDataStream *aZoneFileStream, quint32 reco return result; } -AssetMap ZoneFile::pParseAssets(QDataStream *aZoneFileStream, QStringList assetOrder) { +AssetMap ZoneFile::pParseAssets(QDataStream *aZoneFileStream, QStringList assetOrder, FF_GAME game) { AssetMap result; aZoneFileStream->device()->seek(aZoneFileStream->device()->pos() - 8); @@ -312,7 +361,7 @@ AssetMap ZoneFile::pParseAssets(QDataStream *aZoneFileStream, QStringList assetO } else if (typeStr == "IMAGE") { // image result.images << pParseAsset_Image(aZoneFileStream); } else if (typeStr == "SOUND") { // loaded_sound - pParseAsset_LoadedSound(aZoneFileStream); + result.sounds << pParseAsset_Sound(aZoneFileStream); } else if (typeStr == "COLLISION MAP") { // col_map_mp pParseAsset_ColMapMP(aZoneFileStream); } else if (typeStr == "MP MAP") { // game_map_sp @@ -527,74 +576,6 @@ Shader ZoneFile::pParseAsset_Shader(QDataStream *aZoneFileStream) { return result; } -bool ZoneFile::pReadUntilString(QDataStream* stream, const QString& targetString) { - if (!stream || targetString.isEmpty()) { - return false; // Invalid input - } - - QByteArray buffer; - QByteArray targetBytes = targetString.toUtf8(); // Handle multibyte characters - const int targetLength = targetBytes.size(); - qDebug() << targetBytes << targetLength; - - // Read as unsigned bytes to handle all possible values (0-255) - unsigned char byte; - while (!stream->atEnd()) { - // Read one byte at a time - *stream >> byte; - buffer.append(static_cast(byte)); // Append as char for QByteArray - - // Keep buffer size limited to the target length - if (buffer.size() > targetLength) { - buffer.remove(0, 1); - } - - // Check if the buffer matches the target string in raw bytes - if (buffer == targetBytes) { - // Backup to the start of the matched string - stream->device()->seek(stream->device()->pos() - targetLength); - return true; - } - } - - // Target string not found - return false; -} - -bool ZoneFile::pReadUntilHex(QDataStream* stream, const QString& hexString) { - if (!stream || hexString.isEmpty() || hexString.size() % 2 != 0) { - return false; // Invalid input - } - - // Convert hex string to byte array - QByteArray targetBytes = QByteArray::fromHex(hexString.toUtf8()); - const int targetLength = targetBytes.size(); - - QByteArray buffer; - unsigned char byte; - - while (!stream->atEnd()) { - // Read one byte at a time - *stream >> byte; - buffer.append(static_cast(byte)); // Append as char for QByteArray - - // Keep buffer size limited to the target length - if (buffer.size() > targetLength) { - buffer.remove(0, 1); - } - - // Check if the buffer matches the target byte sequence - if (buffer == targetBytes) { - // Backup to the start of the matched sequence - stream->device()->seek(stream->device()->pos() - targetLength); - return true; - } - } - - // Target sequence not found - return false; -} - TechSet ZoneFile::pParseAsset_TechSet(QDataStream *aZoneFileStream) { TechSet result; @@ -687,8 +668,121 @@ Image ZoneFile::pParseAsset_Image(QDataStream *aZoneFileStream) { return result; } -void ZoneFile::pParseAsset_LoadedSound(QDataStream *aZoneFileStream) { - Q_UNUSED(aZoneFileStream); +SoundAsset ZoneFile::pParseAsset_Sound(QDataStream *aZoneFileStream) { + SoundAsset result; + + qDebug() << aZoneFileStream->device()->pos(); + + QByteArray rootNamePtr(4, Qt::Uninitialized); + aZoneFileStream->readRawData(rootNamePtr.data(), 4); + qDebug() << "Root name ptr: " << (QString)rootNamePtr.toHex(); + + aZoneFileStream->skipRawData(4); + + *aZoneFileStream >> result.count; + + if (rootNamePtr.toHex() == "ffffffff") { + // Read in sound file name + char soundNameChar; + *aZoneFileStream >> soundNameChar; + while (soundNameChar != 0) { + result.name += soundNameChar; + *aZoneFileStream >> soundNameChar; + } + } + + int tagCount = 0; + int resultCount = 0; + for (int i = 0; i < result.count; i++) { + aZoneFileStream->skipRawData(12); + + QByteArray tagPtr(4, Qt::Uninitialized); + aZoneFileStream->readRawData(tagPtr.data(), 4); + + if (tagPtr.toHex() == "ffffffff") { + qDebug() << "Tag Ptr: " << tagPtr.toHex(); + tagCount++; + } + aZoneFileStream->skipRawData(4); + + QByteArray pathPtr(4, Qt::Uninitialized); + aZoneFileStream->readRawData(pathPtr.data(), 4); + + if (pathPtr.toHex() == "ffffffff") { + qDebug() << "Path Ptr: " << pathPtr.toHex(); + resultCount++; + } + + aZoneFileStream->skipRawData(160); + } + + for (int i = 0; i < tagCount; i++) { + // Read in tag? + QString tag; + char tagChar; + *aZoneFileStream >> tagChar; + while (tagChar != 0) { + tag += tagChar; + *aZoneFileStream >> tagChar; + } + qDebug() << "Tag: " << tag; + } + + for (int i = 0; i < resultCount; i++) { + Sound sound; + + if (aZoneFileStream->device()->peek(12).toHex().contains("ffffffff00000000")) { + aZoneFileStream->skipRawData(12); + } + + aZoneFileStream->skipRawData(8); + + qDebug() << "- " << aZoneFileStream->device()->pos(); + QByteArray aliasPtr(4, Qt::Uninitialized); + aZoneFileStream->readRawData(aliasPtr.data(), 4); + + QByteArray namePtr(4, Qt::Uninitialized); + aZoneFileStream->readRawData(namePtr.data(), 4); + + *aZoneFileStream >> sound.dataLength; + qDebug() << "- Data length: " << sound.dataLength; + + if (aliasPtr.toHex() == "ffffffff") { + // Read in sound alias name + char soundAliasChar; + *aZoneFileStream >> soundAliasChar; + while (soundAliasChar != 0) { + sound.alias += soundAliasChar; + *aZoneFileStream >> soundAliasChar; + } + qDebug() << "- Alias: " << sound.alias; + } + + if (aZoneFileStream->device()->peek(4) == "RIFF") { + sound.path = sound.alias; + sound.alias = ""; + } else if (namePtr.toHex() == "ffffffff") { + // Read in sound file path + char soundPathChar; + *aZoneFileStream >> soundPathChar; + while (soundPathChar != 0) { + sound.path += soundPathChar; + *aZoneFileStream >> soundPathChar; + } + sound.path.replace(",", ""); + qDebug() << "- Path: " << sound.path; + } + + if (sound.dataLength) { + QByteArray data(sound.dataLength, Qt::Uninitialized); + aZoneFileStream->readRawData(data.data(), sound.dataLength); + sound.data = data; + } + result.sounds.append(sound); + } + qDebug() << "- " << aZoneFileStream->device()->pos(); + + return result; } void ZoneFile::pParseAsset_ColMapMP(QDataStream *aZoneFileStream) { @@ -1048,16 +1142,15 @@ StringTable ZoneFile::pParseAsset_StringTable(QDataStream *aZoneFileStream) { *aZoneFileStream >> stringTableNameChar; } - QVector tablePointers = QVector(); for (quint32 i = 0; i < result.rowCount; i++) { QByteArray pointerData(4, Qt::Uninitialized); aZoneFileStream->readRawData(pointerData.data(), 4); - tablePointers.push_back(pointerData.toHex()); + result.tablePointers.push_back(pointerData.toHex()); aZoneFileStream->skipRawData(4); } - for (const QString &pointerAddr : tablePointers) { + for (const QString &pointerAddr : result.tablePointers) { QString leadingContent = ""; if (pointerAddr == "FFFFFFFF") { char leadingContentChar; @@ -1077,13 +1170,7 @@ StringTable ZoneFile::pParseAsset_StringTable(QDataStream *aZoneFileStream) { content += contentChar; *aZoneFileStream >> contentChar; } - QPair tableEntry = QPair(); - tableEntry.first = leadingContent; - tableEntry.second = content; - //if (!mStrTableMap.contains(stringTableName)) { - // mStrTableMap[stringTableName] = QVector>(); - //} - //mStrTableMap[stringTableName].push_back(tableEntry); + result.content[leadingContent] = content; } return result; } diff --git a/zonefile.h b/zonefile.h index 93d8cb7..ede2c28 100644 --- a/zonefile.h +++ b/zonefile.h @@ -14,7 +14,7 @@ public: ZoneFile &operator=(const ZoneFile &other); bool Load(const QString aFilePath, FF_PLATFORM platform = FF_PLATFORM_PC); - bool Load(const QByteArray aFileData, const QString aFileStem, FF_PLATFORM platform = FF_PLATFORM_PC); + bool Load(const QByteArray aFileData, const QString aFileStem, FF_PLATFORM platform = FF_PLATFORM_NONE, FF_GAME game = FF_GAME_NONE); QString GetFileStem(); quint32 GetSize(); @@ -25,27 +25,25 @@ public: AssetMap GetAssetMap(); private slots: - void pParseZoneHeader(QDataStream *aZoneFileStream); + void pParseZoneHeader(QDataStream *aZoneFileStream, FF_GAME game); quint32 pParseZoneSize(QDataStream *aZoneFileStream); void pParseZoneUnknownsA(QDataStream *aZoneFileStream); quint32 pParseZoneTagCount(QDataStream *aZoneFileStream); quint32 pParseZoneRecordCount(QDataStream *aZoneFileStream); void pParseZoneUnknownsB(QDataStream *aZoneFileStream); void pParseZoneUnknownsC(QDataStream *aZoneFileStream); - QStringList pParseZoneTags(QDataStream *aZoneFileStream, quint32 tagCount); - QStringList pParseZoneIndex(QDataStream *aZoneFileStream, quint32 recordCount); - AssetMap pParseAssets(QDataStream *aZoneFileStream, QStringList assetOrder); + QStringList pParseZoneTags(QDataStream *aZoneFileStream, quint32 tagCount, FF_GAME game); + QStringList pParseZoneIndex(QDataStream *aZoneFileStream, quint32 recordCount, FF_GAME game); + AssetMap pParseAssets(QDataStream *aZoneFileStream, QStringList assetOrder, FF_GAME game); LocalString pParseAsset_LocalString(QDataStream *aZoneFileStream); RawFile pParseAsset_RawFile(QDataStream *aZoneFileStream); void pParseAsset_PhysPreset(QDataStream *aZoneFileStream); Model pParseAsset_Model(QDataStream *aZoneFileStream); void pParseAsset_Material(QDataStream *aZoneFileStream); Shader pParseAsset_Shader(QDataStream *aZoneFileStream); - bool pReadUntilString(QDataStream* stream, const QString& targetString); - bool pReadUntilHex(QDataStream* stream, const QString& hexString); TechSet pParseAsset_TechSet(QDataStream *aZoneFileStream); Image pParseAsset_Image(QDataStream *aZoneFileStream); - void pParseAsset_LoadedSound(QDataStream *aZoneFileStream); + SoundAsset pParseAsset_Sound(QDataStream *aZoneFileStream); void pParseAsset_ColMapMP(QDataStream *aZoneFileStream); void pParseAsset_GameMapSP(QDataStream *aZoneFileStream); void pParseAsset_GameMapMP(QDataStream *aZoneFileStream);