From 07716364d85502f562c503c3e022adcdc63b5825 Mon Sep 17 00:00:00 2001 From: Dimy <43338144+Joschuka@users.noreply.github.com> Date: Sat, 26 Mar 2022 19:38:10 +0100 Subject: [PATCH] NUNO5, texture updates, small bug fixes --- ProjectG1M.sln | 10 +- ProjectG1M.vcxproj | 84 ++++++ ProjectG1M.vcxproj.user | 8 + ProjectG1M.zip | Bin 0 -> 49682 bytes Source/Private/Source.cpp | 197 +++++++++---- Source/Public/G1A.h | 61 +++- Source/Public/G1M/G1MG.h | 6 +- Source/Public/G1M/G1MG/G1MGVertexAttributes.h | 4 +- Source/Public/G1M/G1MM.h | 4 +- Source/Public/G1M/G1MS.h | 5 +- Source/Public/G1M/NUNO.h | 276 +++++++++++++----- Source/Public/G1M/NUNS.h | 16 +- Source/Public/G1M/NUNV.h | 16 +- Source/Public/G1M/SOFT.h | 12 +- Source/Public/G1T.h | 44 ++- Source/Public/OBJD.h | 2 +- Source/Public/Oid.h | 4 +- 17 files changed, 569 insertions(+), 180 deletions(-) create mode 100644 ProjectG1M.zip diff --git a/ProjectG1M.sln b/ProjectG1M.sln index d4c9042..2df88f1 100644 --- a/ProjectG1M.sln +++ b/ProjectG1M.sln @@ -1,20 +1,26 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31605.320 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectG1M", "ProjectG1M.vcxproj", "{A6A5FA32-48E6-4629-A71B-44B7DDD081FE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Debug|Win32.ActiveCfg = Debug|Win32 {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Debug|Win32.Build.0 = Debug|Win32 + {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Debug|x64.ActiveCfg = Debug|x64 + {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Debug|x64.Build.0 = Debug|x64 {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Release|Win32.ActiveCfg = Release|Win32 {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Release|Win32.Build.0 = Release|Win32 + {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Release|x64.ActiveCfg = Release|x64 + {A6A5FA32-48E6-4629-A71B-44B7DDD081FE}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ProjectG1M.vcxproj b/ProjectG1M.vcxproj index 36a2ca3..2245872 100644 --- a/ProjectG1M.vcxproj +++ b/ProjectG1M.vcxproj @@ -5,23 +5,37 @@ Debug Win32 + + Debug + x64 + Release Win32 + + Release + x64 + ../Public/stdafx.h + ../Public/stdafx.h ../Public/stdafx.h + ../Public/stdafx.h Create + Create Create + Create ../Public/stdafx.h + ../Public/stdafx.h ../Public/stdafx.h + ../Public/stdafx.h @@ -65,20 +79,37 @@ Unicode true + + DynamicLibrary + v142 + Unicode + true + DynamicLibrary v142 Unicode + + DynamicLibrary + v142 + Unicode + + + + + + + <_ProjectFileVersion>15.0.27130.2010 @@ -89,12 +120,20 @@ true ..;$(IncludePath) + + true + ..;$(IncludePath) + $(SolutionDir)$(Configuration)\ $(Configuration)\ false ..;$(IncludePath) + + false + ..;$(IncludePath) + Disabled @@ -119,6 +158,29 @@ copy "$(OutDir)$(ProjectName).dll" "..\..\plugins\projectG1M.dll" + + + Disabled + _NOE64;WIN32;_DEBUG;_WINDOWS;_USRDLL;_USE_RTM_VERSION;%(PreprocessorDefinitions);WINDOWS_IGNORE_PACKING_MISMATCH + EnableFastChecks + MultiThreadedDebugDLL + 1Byte + NotUsing + Level3 + ProgramDatabase + stdcpp17 + + + projectG1M.def + true + Windows + $(OutDir)$(TargetName).pdb + + + Copying to Noesis plugins dir + copy "$(OutDir)$(ProjectName).dll" "..\..\plugins\x64\projectG1M.dll" + + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;_USRDLL;_USE_RTM_VERSION;%(PreprocessorDefinitions);WINDOWS_IGNORE_PACKING_MISMATCH @@ -142,6 +204,28 @@ copy "$(OutDir)$(ProjectName).dll" "..\..\plugins\projectG1M.dll" + + + _CRT_SECURE_NO_WARNINGS;_NOE64;WIN32;NDEBUG;_WINDOWS;_USRDLL;_USE_RTM_VERSION;%(PreprocessorDefinitions);WINDOWS_IGNORE_PACKING_MISMATCH + MultiThreadedDLL + 1Byte + NotUsing + Level3 + ProgramDatabase + stdcpp17 + + + projectG1M.def + true + Windows + true + true + + + Copying to Noesis plugins dir + copy "$(OutDir)$(ProjectName).dll" "..\..\plugins\x64\projectG1M.dll" + + diff --git a/ProjectG1M.vcxproj.user b/ProjectG1M.vcxproj.user index 0703d83..2701da3 100644 --- a/ProjectG1M.vcxproj.user +++ b/ProjectG1M.vcxproj.user @@ -8,7 +8,15 @@ ..\..\Noesis.exe false + + WindowsLocalDebugger + ..\..\Noesis64.exe + false + WindowsLocalDebugger + + WindowsLocalDebugger + \ No newline at end of file diff --git a/ProjectG1M.zip b/ProjectG1M.zip new file mode 100644 index 0000000000000000000000000000000000000000..331b290a68b22a7f98b9d2f9aa14070aa8ad5260 GIT binary patch literal 49682 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≫WB}Y;bmu}RJJJ0 z%IC972PB1M)`_U6&XAfMALWP#d4Cs^LX95p)K}wZF`+RjG4IPmq6wfTfVfhXNnd~G zIc_SDb@|n9cv>{!06#X1f+Sfp>6nBwOwWG78y(+aw4->7DNEM;1o&1KN za6YzNjb&AH-2T^xEh6}r1~co|BmVt59Zjoa)EJhb4~q<@HWTki;2l?_6xnTN-bz7V zS;e4~#E}P7I&pSt-zKnCyDH(D5}2%+(ojSzwZcfFM=8`Kk;$4290-}*+#ls_E$HLH zkd}>z$>~}Hk9c{IUSP=nKLjAtr!4CFfE-qXiUG=C7s;UIjh^q4nxuNGtzKKt{j7{L z<|-K_22erAv;JL$vw6Zcca=01fNht&dnp=JAaayK8z*=W17Q$gN~D&$!UN3fWzGq@ zlTd;NmGEg{Hlk`!{Sf*gRK!19m zt(|hIRnyz;PnFxQf|W4nXu7i_U8ItKIR5HG5B-#WT(1;3&g-eL9>q)uObbKPQ+=LrJIDV|?Ef=cbG@A{ zL;30IHyHjq?8~Xj$G{7vOrE~v^~~PGUE^4;D9eV+ zFI(E-B6IfKUo*as$q^vGV|FKI`o|6FWpWb_) z3Z0LPgtxEN&+Y2w`#xUO43b{JNu6u5L48n>V%&KTkrE1>ptKMa-tl}ZDT6I8q;S*g z`fKcl^E)(G*Xt>GwcpqXoPq3S3MdWvFf<*Fm#oI~V80C^dilH4W1T3`gv-=0^CY-1 zE#$WJl*Oq2_Aw?n9{WXWIRoJ){$M0)7`4z1v`B5lfCCzxFtXMbYcSNx^Sq4^gkYMK zJb!UPHHz1%+I|EMFN8D2K%ZaZ?KqEoWi~}doU>%My5q!ts)V0nmw6>{S`ZT7+LO-=m7Z{J#uU5e~_eNJNJ;p{k3ghy3RY2bD@0=P>zd6}UX%c-NMz5ICHnS4fD zmUWV2*vo_!YeXHUG&U6^E4IfR>16gbp`+u%eNtx<#;EyJiIXqS5 z*UO3UPGDN=BAAbd1$G>Pj-=@X{4iqSEC6W8n93it?p4<0Bm$i0vEDvwb!OVw%NG~w zj7WT=>Blm1Uy#L{YZXnx2o9iGVmGb__U#@1E^K=GEMRXrw^0;^OiEl?@deWuj@npU-sv}yxp z=Yh<|I>5W>LgvJ;JUgV4T^eA5cB#;j<-ir}_8GhH?5FIOs{hX%*`=C=#hnS==c^;$`gD6Wb#6aJB zYYQ>RX8|Gc>=I+6G7y2rW-KljJ%(`T;zDIogm2)LQcQ2JCC=co4d9mVgB|X3n~iZ5 zvtz^^(yzN#Iu6BKoje?+hN3(MbJ+d*p<0tSPanU?pzvWsDb3|E>5A-q&BD%FoA2v- zJfv~hs7#wc;pY9XLIX6u^OEM-B&yWMEd8M64x#v00+ri`^lhd_sg_?XetyCFc6OE% z-ps4)5l|hJ9yBt2EF0(^j5j4zw^nc}u{U5xuEH-k=U#L~5ty zek%P_9MBL83q-;NT+OsR;3ICdT{t7D`I4)lsqx-?J7bNWcaNZcxKw9l!CuO0Qp$2f zN^(Gyq2?wY3zlN-rBgjCh$~ur(S$9YsLvWHUI7RMQ7q{4p1@hUJ==-+U`HWu} z^fN)e^|eu*b9(~P>?zA9)b=yrS8n{wXB5x5x6nR%mMJJn!_%?_;K4!LerAeszuAz{ zze|#wsHzm|bS7!$(@H*cnijI|40QS>cI#EJ&!7Dy+l8xro~`IeOO9&BM0zP`2aYzH zz#?5A;AxmVV-iO7lPl=5l`t90fBURf%goM1t+qnTPzEBsl;>w=Z<~@S;ysun89kF$+VPP9L$&8mryFc!Vs&1iyJo zxfHY{k7VfKLn|7F@7gcsbVi3!5;Y!0gTUOvE8imbcb@y2r>%>CfhYE|!0m&K2p-bGCm&|gw?}Hs zCwKn1eshdoZBtn|$O+$8|6u@08hMV*vTNj&WF%sxJEG4Vx8gWEncfB{)&bS{kr1{! zwMgdmx}=B2rQzhX0B3K^Ce;UF2E=e-*_NB>p~-a!6?OF`P~_I}%}~R|Kn3bk2>D8e zNY}CRZlE@mLSybAK@t$#sqZ;FGb|tr#JY~KP|p*(4>CJ=Db#iPlKq! z0&(O=equGxkd}DA6V-u2Gj&C2z`Ha7KBbPwt`A?__i~7w#ovvF?6772#6VN8PEn%g z+sQ;5+Z3}3KBUdx(5fC~dW!N2c>-S)DhrHPvhh35TOs13Gw9=YL=PTN5nPxlPc;NE zPn||Yp_{+D`I;Jso=>eDFV0ZuYl#Z95SB`xHeH;Wgs-?3ztE@H*jW9Ei8ksOnV7P% zxj2-YCiml9=h=qtQw>4RGRCsL^M=8sV`rs|{}l?2{sIE$n6-{B8*hrb?U8~+jsZtF zf%_dE=^r#D69nIAS}Rw9zRPp! zf5BzW4Mgf6nU^?-e#_y=U7W!v?*#ds+`nHXZeteZQ@*hLdQ<$&h3b(1w+81Qy5sla z0lVi<7yTpF{%`1xpGx|ti_ZSf(O6KWl(G=zbup?!QL-wn(sF*wd6WDINT|qf4k;W9 z_Sxv^rW(+#F9PR5!0>EmTe??4+)lGh2U zjQMMQ{2}6n7Yi0F-51t&@%95|`WZ}5I2^wV3^#$k6~A%PK_`q1miO=lBqePLTslm) zK>~eAC-sC{(pRtp+uTx9lf$*N_}(Zb?8&RD!A|DZI%XL}#~H)~fP`jkMlU|*yvzU)y^eJ{-$eqk0T%hZrmF0imd}-?l&o`N>IYPvj^VHvXxPaqwTK4oI0Y9oh z)jYi_&5-37hN;v-JshdH%IgU9kg_Tp9h^5(c^_dZ3su2kcmqA9J2S1dJVEEC`lF4V zzM>gN5rQ?}89ka*&JCu{ZzQnc`Rg4i^tnTk; zbn>$QXv*+@(a@RQT}V7vF{S%OP5AhJWUU|op4A8Q$;8xHi%qbCA?vo{w27;*^4v{$ zPeV)d0_>)}wpPM{TXGgHx!`@`0`^e$WuT<+5UP6>R2hl7Df|g8-ne_*8+tQEG_|K&Jm&+G&v6_U}UQ*eofS-fAwg3qrSLlIdZg zq7kK7+p8INU{iOzzSNeI?8))KqK-=p`D){T9}Y zSf~PuT4^p4x#}*uWk3CnawGt{pm5CFT=?$V=RhY10YMc?bLP*T9we^`HK&f^AZ}8s zNs`COlr4|O^X2^;vrC_aE4JUQ8OZwO=*o>nbU(>5`J?So5}HZw7zlhK&!V3_SWDlm zD-T-DQ+f(UN62@fKJ-g=qI5{JXdMD-Un!J{>Uwq|QuzC9eoUXNqA%02$x0gN^+_H& zp-*`VgRo1`pBjtf^6_^?_=if+14Bu`CLrevaWX_0ddyAD#L56^k?rJ?!g&Ys+AFnv zSFu=Nee@1(q_P-andkUL5l-jIds>KM4qBBVE|}$6BR-gBp#Na}xYnZpm^VM7)SSv# zd(3eFh%BBFBusA3WB@(Heu3!_7{zD#KxDq4&r#$07qgP|QbIZ=I;zQlWD4qJ?(^jE z@XO4b_YW@rcb6xNk&Y^W_62Q^+X!l2oiLM(>@A;4mE%k52-meKhgqf6F|H^u>I2qHL+>Lcd2@S0Smv#KD{3$>xYb0{5n>uKQEqq-OTylZTR1L+y%k) z6~Vf6uwCf{YVt(yjpj0JrsHy|VA6!+Ct5!=LIC(ZLu;v;%YSUfuVT8$_yyud)}+wJ z0lf~xXyXMo)MkIFE$&7`f<`=SL<}kdtTScen)Q zc<(*1BVP!n>evE!%dn)BdZS5GZg4PFrVQzJ1W6|*{xKumO&5L`1p+k5vC~~#C@*HP z7Nr>?s>fy1f=e{>wYs zb3bqgb_1Ww7WF=&D_0@V!RNW^UpRhnp!${PIhye>MKn6g5RLxh`mS4vI^7AFUY{1h zs#qsORB@9nb;Af!K#WKb07YK~EglS%hkd6Kc!7r9@dJ-b__xgoyVg4E;a9-Is2tdL zpr(moau{>TWEXmH{Eg94emhU*V#SVLh~q3V5)eQT;p~$5=dwXn4O^Q z_03;nfh(c@Cod&05z{cz$=z$vL8qaOhyAv%3fdGiTw!JP+wxc~3AL%E|Og&8mfwNY@V9;P6We zO)aGN7y1IZuniuaTDCm>T%GT~j68i^IkP`}o=W>ap4!wDgw_bU@!257U;`iW1oQIk z(OA=|$7T;M>m+XqC~peE0R2{RbaUo`zk0WWD9v?|zftMIN`iyJlrIuJZcROIT>lo| zb*|xQ#+twTRGh51=IRUzCIQXSY6514o;@Nr8Fv^G|qABrt&?gDaiufkex zAhF;fyU=?!mZtiCc|qeD91M?Mk{leRjQ`c0LB?2K;P=4l(9S?{DuO8 zq+M#wzX4TzqSRQ;z>p3tQ(3D@O=bz89^_J!Mx(|}`O zwnAJ75V>#eL4|udCw?`N17r7!Ada4%sTYp7!*piY99dlYiM_tJUzpp ziB)Rp&eoRz7+R*F>Aq{MPf@6|-F%AiChgbuLL6I=nZ9(qug(!7K{v!6BR4iD6+#a3{HWHSKP?lNB()MP@ zFoV(PI}hHWMVe{=ggA8`MDg9=GIi-B&1R<0L~n3m5I7IITB&MbOM#(UHR4q(wspp6 zWuV?TSQbkj{@LK#J1)QP-*t*GRyoibwc)=$&DkB9ewE#xq6emS`c3g{h124-#4tC; zT@Z1?=3kBJG)?3+6sw&fcIKgN4(uO3Qm5EjCA-)sJR6KE@8eD_ByF>8c?*2&M*N^nQ=^n2b2RJ|0SG(#| z!LGg5n)5vYks55HD2MU^RQPQQ7ESl(=Cijy`>pC4R?8enp3>e=;B&ob)rZ7w{iD6m z%S}KZ#Su7*R%xIF>rF(r)o);5&#sRx@J9w&mgt=TU}t=VxNTJe)>nnJjo zeA(vM=GK-K=DQT^<#8jWb0F91!-VY&a9hGYrm`DjAVi!1yxe)B9!i`h6<%lY7sfofQSPdhJ8~ zs_lK?2hyYkaZvSu0QD@~9qyIIzt8Eq*U}q@QEy>F<|!BUiDbZ; z(mR@m@;m^RxsC)LQ!wg}e>h`wH?Tuy3%H+JVvww7JdArwtuI}Z`|}dXS{ETn7IsYY zC)Dmx?Brmktl(n?8j=s!f8i)E&&Yxe+7-?Fu{daQ*5qbn;vOadxqk=HF4761zpQBH zX5!|y@e)mS;On?x{5gy%f!I<;mQX8*goIp_fds9h(-qIo$jqc#{INB|I^3wue!IPY z7qmy=L`>)`&J4#jOxIU!w~Y}rz!}L$rwWm#P59?RXObdbYZhogTZ8q!wxSlZ#UU&+C8O76nO?j>E9^C7T zFb7vD-zYzhe9C19zEf7Rl{!?0zThSHNm$<3=^C=aa7;b%I&`}irw6y+#FnB1s zM_qf>dR&b{BQ_HU_v~~%<$yD#qZupaTGq}Td#<}3(nQ@1isB$23Fr!JbEKD9VMJ&A zNELK=d9)^fa=0Jo^!%U+OhAWe3sbPA6VWa;$^t!PqJnMkCqvd|wN*dBigfR+U2n8; zKW`DX@fB#I@#hh{a0z|IZ9Jr18j>8z{2Qzksf%RVBT|E&Ep^iJLKKA03{4fs)v9?W zkx=|@b9fD-tUDSbHzh{1| zYc(}xvO6f!s+;wYe3dYtd{ie<2>oG(Ll<|rk>)H!`nbh>BjZou7NnxQD1WDl^dtSN zoJMa=N@q<*YmP$0a^puc&`~l%0Q@@hlX69K?5(~sMCKpfUS6m~vqw!qbXm&_;?vM$ zn{xEnyh`Qi2n(K)Hxi-I*o~>heZIuG4_8JFcHy&6SGFB#tx9$^f(A@$;?3PP>?@^% z$+0j6Hlt4X>*{Wh^Ld&Tt)7QhB$`nw)~han({uR2*5$ki8|v4nL!UG3_@>kGPqf0!6mSE%}OB zk|Zu~@*0SQF14q4N@U}q+yZZ8+8*c1i?Eb7F0a!BQYh*LEi)Q`d`t_=WqLbfb_u77 zQ6+TBmoI?$oK`K|Qo^sMWx;=Xi>4VW4vS9O0^NU}rz^9;(Rhs(>42jYA$&E`|z)C^-4jNS!y^8DhaQA|{@)tSH+>G)&q>fbe`*&M7Y8BI$q}uv^`4 zPReD>G;7ed;rjhkZR*{bSK=@y(U(Ivu&qDB>}Bu1e! z?%^$OYc!7ZG@fVA_p|;iNCnlv3Pcv@Xg#YCTiisUa~&y|NEpZy5aUr0xTl)M5wv7G z%bT|8S+%J}Oky?-S2+lKwUOsY(A)vHKMezjxR{xR`p6w{PFKtH4*`-93m4mqkbR!P;l)+VwvRD}+H0s1Z=q=HwL=PHM8gS_=q)lOxqi&|YTls$aMV%>a@+%{juhL^7lUnqx zh=ikM%L0rvt=kZ>AbWMSKZ=r3zhmSWnR_o9(ok4AN`pgrEsgR}gq17Y9ag`#gFU_2 zORldL*mjeP_$o1DQzzB#cjtZi3~8mZx&4_~A4>2(b}IHB;t-etCbaFf+PL*|Y2$H? z&KXA(d^j{vfETL*>n-UV(ej6 zz2Jh_NsQg6%psA=2otL?@;J(Klc1WcaEptY;nSH(Pi%Q#Y)9ecR-bh1VjudM^9Smj zlnEP|suWZmy6pp-@SUF_R>P_Ms$Ehk9pUL)WPttPklpj4jj-GY98b!uTAEwp=8Z5_ z51Eouf{gb)UE+|l)p7JONXpS|EB=?QQIRL#Mmo}iwvqOy={BpsY?03gi92+r6-tLI zpwxF5_lF-Xd4aE6UG;y}NT@xBmhmmP7m+fOvvcV7;kHW(HI9Y4IUm=ibE?h4$=LdM zmzaYznZ{QKnHFcp@jdVdlggxJ0c_MsmVxl>VKVW7Po^nUbynj{Gki~HIsC3|4=WTM z>4eWG{*1}iPX<;o%uM~*X=5o4@ZBX7 z^2Srs*B&rF88Vci>$%T{y9iO}HFj34!9au`$!d%GY&EwYxt29U?!?r-@E$L#ddUp& zDb%(`{5aQHc>S%87N^Zx(f ztHXTS>=Zv5Gx=FtKOYvBOOFx~sx%y%7c_5v{5I`Y$QSy!Z*coV3kB9Rgo`edje z4%r?U=Ookz-jxNPQ=(-*x!SSV-lsmt#K99dQH{+h`5B37Y(&ulUb`(6VA-Mpn>5mP56W7O;WC1?C z&%PGu{Zb%w!AB@ALfsq;{k$vZE-6A(4D!4pxg}V0z;aKHX$Mw!e?IEi$fubSN~0wH10Z zE(??z%w+~m*}ZNCR-cMN^EoD9m~Gvi-hXUtVk0O|@b30%58*kyqXUCmG!vItHOKZz zm9pQMa_XbEbayV?^w-Pl9FH7%r~|iwp?QHB%~Guh5i2RkY5 z#>AFxtXzne9zycZ3@fR#`>wW2fJIB>}&Sp?^*iHdE_d@TbmiMdyDp<{WsfY#3J6}6*TBESj6`gbIant`ru z=_{!tSn520YM)YpR_*N&T^2l^$g5%O@_f_WAIj{9k!ZNgW#q*Vc)Z|LE1$SiUHcKp zKGlY5;-venXX{>N#%KEHOnq)b89O}ASte+%@>65J667?=4fDiVzE$DL zucYq^$-45vls#prH_R1R{uE-#rzGdz9O9k?5WEE5qaR*O;O25|LR1*kcKA@-?&G9O zzu%69xM<*ZJlsQNkZy)%i@@S5wWm(09gJh1Uhgk}YE*JU=>i&ECz=$Sg!B2KX^teO zW(#he}={voc@ zzVKW*I1VDtxklWJ3$$$V+WE!UnxQ3gBr06MOf~KhQ_$rZn?5~!aH}d%M0nP^5Zz-S z3lwihz>oDpZm={Ld%d$e#o5OrEj#M--p<*RD;I|Lwa&<6Qc~XH-sn1rJH>fUU9)KA zs=5*XLEJ9(9dLtytbxHRuqsx!@e_}hHzhLJ&3xry_})6v18!3fq>5wmBEPkaW;o{} zbh_{IDiM0FFWe6388?;pDd7tAJq(!3aU7%!MN>YxLuPSr3aGZNd|<&i>z5Alr5b~& zQe43wO~dBFLA;ezZs5wGka{TPxF*0xpu`3VsPz3vQ+wsjn3#CYlz{5)+`Q8AL{Y(U z{Vb2DO37vwiV=&OAf{i`HH1mxX-fbM7Hewh1~QVcOHd7BSx3Gz51R-0n{St$mJ``* znks8u70K%{DnXXcMO<2cBghjY8 z<>1J+v7`9NP_#rOltq|ontd#x1WHOj^iX<@PwXHlSB73X(IqZwV&!X-LXDH;yR2kWxa5MjCjPHhiOINKAsfa@3fxR!JeD>4Csm1BURTj!kY4l)gw88U>HWO-zKyf z_vf#8ydf$!#p=XxgYu;K`!Sgj z#ow(frIBYam#82v=lFU@1YPA*MAgZwmKPxej9Ej66EH~_XDvU+AiET~`jmWejN|)A zp7(*cDn19xAxsmJeJ^64#Q)xEgCi6u8P<283P#5ULp_5GV2P5;Z|9?YTJeK2 zabvnQJo>(rC;h}%oGXDeV`&SDZ+ilTSF}MpLV8_R;UA$nEw89;n(}r=;+X4uLZSS> z41Lnxa1QnH4;ve4Y9yDg&y|h{#ov!NXa$0@^UFs;f3mOj9Ui7EGZpdIE*3C15+DfN zwiK3^QQE2Z6Bwf7Wn`7f^9m&3Y0g+CBwu);&3^yYlKl-;xQZjouL_91lthnJa0G--cWy zdmQNRXY2S`eja7bh~0l>7zo|9p#0G@0?#_AeI`<&M39(v4I|rn4+?|H!xcr=M=C>4 z{;d-(-O3#B&JlU{KfSXPcn3WcIwZ4=62N^5WWtNC5tQI_J}9+_f=pg4?Tr7 z0OM!Gw`9D+A0@~n#opS4B9vZbmiSO!F_B(^u#JDJmnzK!+y#K3B!qwbKyAt3k8_1e zOJ!l#TC%+P+UC@1mIJmzFwrvp_~LR}p;4t<1NuRZJwUxqd3Dos&qs!5%^h=X9c`Y= z-@UXA3C*U1IJ!n01=MhAqsDU22Cd$7Rw&e-GT{c69$u*Yp-8!0YF8>6H0^wUYjO|8 zCqC$+k|A*$Qm8Ah(!XG1e2QnIoe&(3hwyBK`y(Bp-Fi)vVl=*x6w*_a5Pl|ssy_5d zD}P}JFy5Fg$~lphdYL(rq+U#%(We<@2-DIj$`vwuaUWk6?fu)pZcnF`eXS^P_+w5} zi8|?d38ffpA+;uK!{#&CE)r;L6k7?_V3qqx%5Qf;?eo*GS-{+YjoBIa-4&lmi%xXLm!mpg)K?Gx{f0)oLt*G&DNST()`43r0hrUFHH-xJ6lXB2HW? ztHDWcKSGMxO5fCMhk<35=bIkMiUTb91cMHo$LrylT_2;(s+kE$1!r8EOMJ+xV3}$T zxssNS#>hvZa&8Ol@hv_&$o*(0oq8=LUR0BGt=Yt_D(ujZRJl1}5DQDs%0aofly7br z*F7TgY){4kKX6ErnpbQYSrEKxv1$y9;RS7QMCE);5M0O{m1lPTfuCH`NXWU5j%Vyg zL=+Y$CsmOkOK~wR09rn!lPLf+eyxpr_zg2>!AzlxpvADYjH8ql%`g$|qE{0mKGT4( zQB)8se$FlR5Edo}$6e6(bCI^AyO=hnYC%2n1kfB3Qa9(@P7PQtszJH%m{UJ+d+LSfk$IRB!|}%m z9^kifk4qjzel&4w*!NXW9hi+v26xt2mhE;GgPK28{&0C<)$R!1+FAMN;qJBi1)tSk z5!s|6VI+_FtOCTF5S0#GBG7BGLeY+tj>vz#&0HaD`~>A^WTj zoUfQRE%75cEx)^9uAMt%BkjGecI>-I8U#R)EvMrB^>^otw;VdkTMixNADxjh0un-R zr7Wt4mdmUtFSx*-;Ao)gi|5ojzO1FV@Z#wOi}JN*)o6kfn6*M`ro@v3#kJZoPHTh| z67e*gKM;uP;l?A@nkdJUVCay-O%7s=^A?@)K#OI7u3nBB_K#?|x&`B7H=7m_h}HU3 zqtDZ)Fh<97dxZ}~*)y22H7iGuLvLw1dGtp6qKvmXpcw=qwS|7HP9`BDRFk7Vt1$Vn zp9=|1+ocxdo@6`4K-Dj~uB>@*EqHLP*naP6!!HPn`F_Ojf?o8rBqG_Vz8B%3P(&k6 zKtSh?06IVs6Jz*^x3R6cwY4odLrZJSk|)d4+8jVVPm3B2r3LfxkrD332} zQj=HqBQp*k+$6!eP&V?6LUgr}*Vk*a?{*qN zWWl1Z2w3dbU8#!>yvRMLc?JCN*BoD8p+;4Ft+ys}di=_xp0MIaY-rR$_MB}kPE_%fe6QRLG$#Pr5+`;}Oxm3vm|g;l@FpFouXk?m6V&X1y&i{8*#5nj6U7=tt@w1xt`Ub#4@KF@XI-5 zKjwzhvyaqw{L~eX#zT+&tC=Xmt9eMjbZ@*mvIIEFV;`P=5UA{+yu6(018pQ5s#PHW zkZA#nfNwly4>P6?$mOmKlD=0(km>$R!95?fDsYgqPXtA=4ySOU>)dsWxzb0goKDM7 zl^|ng4i#F*PPO6xKZC}wKxpD+0ITa~Um$t9?KKPu54(!~k2ZF+Kd&p`}k z7Swi^eN^gn)J#W-tC`6Y{fY778%FmHd@_{5%fG~*IZ)!&j&>QWm@JwMa_>O+mmqNf zDF>vgldo3zpiXIwiTSq5-Z&!0<^xM&-)yu}Xzy2M_e2?Wui+FLJUT)JC^HBe%~|#E z>na<|V+O{`q%Ltn9eT%ifkm!5FF-|#4md>ROdu_wDUrE5aX)Ldub@Kf^(rmsy+joa zAq;;mu9iD+)WV>I=&yqDXU=}GtEnDvpIz_@5bkpZkV$CWK@wpZO%^p}wTpIXarn4K zsA|4<=(p^^J^OWibRsXDUyrgMc<%{UNLP#BZlLI~a1m~MN&Kl5O}dnUr{)L8r=p;YS*#ij%_WmFrDC8T73y)s{e8X) z-Fd2bFo=oVgIKU;BJ-6!W%d7-7XMY9NZxKkV*WNE z!hMsL_@_d>%ooF7?YN(0w&)SKi{xLsW%DU17Co!L6`{Zrg$W3&kTjLODI09C;$}kh z(H+sR+fNrFFDHR~C6SkztbUAe$uwu}7&Z|_Uw~RPER}npU3dpt+d1&G^pLbMNgr3Q zx=O17G$?NNt*(VPe{MhZywPog6HI;VXr%t}jp9}>;;{Q%$`PdFwO&KI$`=^} zZ!0!n{YKNsojHzsoCU&KmcNq?!^PV7#*3B~p{JNX90lYPNC1>5XgP31U!O)(8|!NS zkjJ#8g`#6@MYX>e)U-<~DLt>ja2A)Ye^$MQz|X?~#RPis1$I`p0E6$eA&ng9)<{T( z1@!?iq;a2{qrdRXo(A{cU7)b5O`4JBGYYInl{QJdsn`^x>sy}1Zx)}iEQJ~>4EHtA z6=|uG?xzzw?SpO+zco{_jDwYcRz=8%n^FYkWKZ1;BhpFH5?Pm4aDch-SHgBf!hp3S zb+IRN3A>!kRD+ix%*N4ek@|2y_tZ&4;tqZ;AC%B1wgryVB+lb~qMFNE@F`*hI6aKqnVjgTQnCgLTd;KJrV_qax) zolL06eGIAXbL>WadJl?wQmXJ*QxZHx#luBQVqDOsS7r?s}7E65JvV$HJA*; zAis_^=FM@Z_~6f2i2Yo861)IQld88CYN$()UC}7ly>qy%21vB_2a(MUFEtC zk#>BZlFpf?eB$hTcp)-2>OJNQ_tH(+=v*h&ucykaVs07erjA|A;7MghP%`QdcgbCZ zJaBN1hQ>3Bw7d3$&I$r)<%x%uQA!OlAN?0vbJq|N5n->wk$Jw$A}|cs+GS{g`e$KP zOK%&h$RFe&(NfBdrrmyPd);WIb5he~wGT1X)%t`@y5nzY-=kZccRZ=vf~;6WW7M3k z(cPhbkTNMzvI1kN{G*!EXrd) z%SkySvi8=|;$>f|9WNYFD&SRKLD`B?wQ!w7c0v*g_qW33$sl=gnk{WJ6u*Y z2_kLowI#F8(gJRia@AoQ9O2x>9_vAo*gBPOS1oJX;!bQWIfP5-1%u|sKY3s8@4d+m zC+yvkK@CGRMwKsn>Oul6?5Eqkl1F|vAoyq`>5@*F-nfRj;c!HsBQ>Jg(tomaNRq8( z4B64kn{_v1!Cvzkt(ep|SZ04(_2LN%Hv6!ef}G~wgd-CehkscF5fqgypTw?UuKvJC zfS5ry8tnPlqM5Rh?nryxQa^nQ4$ONp^_IsTq&>i%_=@OcnjbnL{Nv=1skxEwxp9~C z$%gxzvh)3CG>{+Mq-pplkFF(HP-IFl1HSpr>0jA3(0dh}CcxsOq)d%#PC!idXI=C> z+1-XYvTyC=Pz-v;Ckh%%kOO@2Q>qd5n<2$Fr#V}eoL766p|HntvugIr)b`tIKEu`A zlqDNJ_BKfpE|g9+@=+kUOc4%5tE*rl^uc42u|22Ypl+=1)j~Wgc0feU8jF1GH^{r; z6PA|CB<)jt3NB_rq`aaR z(xnO$W`@Wy)3_5tL^ELaEoG!j>z?(8jt?86m<9QfhBvi~+{M zoq!U<>kejBg)svv#HR1L^qF4TZ9FyVdB?T%^m962iY3Rawwt-y2_`HXdk}&f;usab zidunV+Hn0RvB7EVl*fHZ>m>GG@1hqQ8BTEkBf3a=T=5oUDe48mZ$ZpADp#-Xw|J|f znt}@$t;SoM8UXd%BNf^|9;g1Vp^iyiyoJ>?{o5V>O}@Rm*o`wbJm`xQL~$;^VlJRX z@I}Q%tet-RQm5m?*>RKIl*1&LK2RCC=~2t$>&xeP8<;V4fsD73TQ5Jz z`k`M+?8VL{@Q9pkIy2*Cuqwx!0gCJnti@N;2gJISjAlDn)Jr7EW-F$mB>1;}$w9&f zvFd=bfR?GB6DkW%OCn#hmKc`?Xb_vtC9sV^5?t6cP7DaN=0fG)#hj6)-R@Jj zkC+7gU@o*O|1pw;PvXsoqB(LXld?DjIq~wy%+*7{&%f}<`w>{gS7<}S&2T71x7c2< z9%W96daE2C)yx(lxWpY2|2FAI^$e zPI#*9p2qyd8x+81Z_&t>FkJj^o&n{QkJU7g~RQD`gxP002P!$2Fs5|Hb_6pTe=Go7Gz< zz-yYsJ)E7Ag&~A^WzH%qu^Nd*s?dR%!jeeLnJI#${eDWuj;%jw6<%6>Zf%2oT* zPu~>RM_k5F(^G8vZ|{59zV(xyYlc~JEj!<=;57qt5bJR6p0;0&c>}bYC-wP9d~V3( zCJ>GEIQGPX1I98{ScXj#p<5xw`!EBE5UjE4d~+J{8TH9B7MMZgMP!9lK1e8xqC>qn zI7WkjY25>W<%#o=XjO%^BFlxUe#KY;I%ZsA2rp1n zh}IBLPbwlqwx5<{YQSe5MZoJxu`KsAy#(_zINv1We3KA3q0@M1n z%boe;^`*qetHco6lNt6V&j-kZLN^|zGM}#|zk?5|Rst$~e<5|G_7j9Na;}+@5>dWx zO%|zuJZm}gIb1L>KL}(0_83i?xP6FGT}cR+vH^O8%1WoxU670F2PY&N9B~a*&L*rY zBG_9y|8SxlHJGW7o1;YMS@N2EZ5bneJiPE`MS2O;Wl8=!cDma?y$f``vi3ywzp72d zh{>f>j|7|97*baT^O7Z+53@2CAg{@XIsNpK9*w>|qMx+;bi0%j?Yka?Z`u(d-UP%n zIZt!06jkgh#iCzgqZH9%=}l>&vUOmVUv(*fX+-k_nlZ1D&jyP*6!!?c`DMxEJFE7V zet@(Jy>|w_mU35Ui#os|2(nQqaT;FDzTHB2^5}q`ACAgb#=3Y0a1cUxw#>d4GmVftx5GE zbQkB?yXMc)G!rOO+`mE28MJ$ve(kKn!c< z>xc-+1gjbeh5HX=hl?>0%BMH05_hhxx6@}y9Oym>X?KH-?L_J#XNXVjVhb1&N8FRv zBEN|wpa2w>PTVt}C3~n~S~2MQb6u5$I2GA+a(vkYtH>h45;I)3sqhRtl~!vyA32sP z?EE^^6F_La-eHVm8tGvlZy3-33@mgc&g_ zP+?8jSnWd)xUV1*E88UKc86`iwUR<(L)^&qQk>mN};rZa;J|eQq;3Fx`L;-03&wx$Oh7 zF#Dl7=eVkuzHeK66&Hp@m^tx_)A-j0cK_D2$`hz@>i%KBrF{147Kq$pIaCoV9PKR{yz0${SEKHCb z-|zvxv@EG4A1qpfhhgpT`e51Fc-^1;(n-l-oIEDywy}vB0xu0&@s8J02mLdS$qQ*)&e1#v!%j01iR3_^i6l!5+r4nsR^r(Cuy7h z;NwpULH{Jv1Km!Jrn^>! z?Q9VGcaL87d053>3N#7kn7Qd4d`!-GykmG2)HH1W{Ny&eltR9w-Ek+rdv5 zz>^H3#EHERdz)K9;vIbMn5Sw%Em74n)@ez*iL?nhLNb=X4ry@ta&h-mKD8}1+-%6} z$H`eMBBWrA>Na>0-8J(A?=>==dyd`p_5trwJ~>2?A`7LdT(lix?N|{B%CgZHTb3Y? zkWHSQ10{b7Xvf(vrCitz0nu3hqc=WH8IjxyC=Dl__sN&X?fdv!-WoETosI%=63 z93p);1W!+rT|ak5cRL-v^8m)r150uVrj6mf-BE~oF$d*D$plyJuyGqBLX&(#6E6NU zNMFf9Q*H>>X#{hg|M#wb3c`}xTvmwj>*(hj z4T@{(5OFJd@4B{0qvjVcuNjLYViVM%F5*@tbSVY&LY_5NzDY{!mvM_yogmoWBKAE+ z2=RaHtshc^WWp@I{D}Dgv=k5C~doE8hdG*7u4(cVAuxx!%#1Gwj)7udyZ zoeNNZrSULQHO$)N!lM33o0+ov5!2rge-A}Pax*8AqeJ!lTTm>zb4BrEpB+~_rdIwT z+37@8DFcb+q|&ZtoPvFAQad@BV&RG5O_*i>j(4ieBwfpWAU=XMIvd{+56t|E^hA~0 z5Cb=-@c_dI&N;(FA-4c%{gpgl^4N+5UB)4L7CXxrhI}>espz(Ai{w2k^NNo|Ko(Md zh;HM0-fV+2=#->7M_J7GB_IbS8>L7JPC(Ixz!OL_A%~M;dE>iB4~16J=76GwHe}6G zO$KmaJg{dDIAFKxs4flgyspfZem_)lE(UD4BqepWy4-A6KA`28>NK@!>BfZQ_k-nz z`Ea6V$;j}uav$Ja|7d-+pBdbW)UKIWq!o+@jihpr)6Mh?g1^B%fE&9PuAZGHn*V-) zq()I+mQNN$!6u{Ap6=1SH*oTCs(|DX!6W=?0Lv*W8Rp#cE2oZTGKS@vlRgt*_ zVI#T2GH=fDO}votZWu9aYDwODp?##Qa^ah!+-KcyMkp*`b?^fX4{$j^>1vgpag_$c zrBbQt@Cy{lDw`g`y-djlnkgI(wTd)ffsQyel=+3Op>RjhQ|@k5>n0C1)A2pHJiKD5 z^XMBwZ3Xn`2bcYdPl(Z<&s0||l|LzBdd)}vaER5kwHe)*VM-SPB-3$0Hk+HMBI903 zUP#<$uh}a=dCcVCzewDZw>i1)+v}G*=&ubBfZ$(00RXIRtxSy!?B85C zXbp{we~-0(ORd3kYk!!36SAy)dlvl9SlhRF+4opGbIadjPu^lXs$E@n!2kf7836!( zc^CcK{@Sm@+tcN5v5p4Lzee$U{H&{sB1AvsD9_uzh2KQ^e=Rlr+Wv|Ue_I*;XFRQg zossQ31OPxL*yTT*OW^-vynRP_d%eEZfd1RZ#P*Bho2>tvf64oq`xV@?S?6!Tf4%+XH2WWzKUmbhWBdgFfq6HqeaHOS<@FcluR{qEddK|R z^Y#5m{^a`lEAp=+srg6jzdT^ycMbvYC!@4?KYCi(grRzdKv~ivH^eV*VKa%U0$;NAV{kn_rZ_MzQyHsr>sB{p%dQ5$JwV{$nVA zGPe1J_-iP*O8*t|i}W8hH}5A10^m;uB=6K=mH$co4?B|Isef{|c&84i{ZHz<&&BV^ zKRF`2BeyjEH}YRP?Z4yxr1k!eQ`PyOxc{g8{yXtc+T`!V2i^Zk{HId+@8~~?Fu$YC z4gWvT|CVI_9sBFz{<~iAI~K$EzhM947J~rzlbG#0rStQDp}dLm{63aH$zr}!TFw4M z`Aa18cgn9L`nx31I|bb7zfk@*mS2>=3zfW68eIPiG9#=<*A!1GU(zZ4gK zr~Ii~_npGy|4+(W@9yt_KQ$h_18SrG0r=I1@H^m7m16Gzq13+v{#7^jJK#@+GVg%E zjK2f^Dx3M8@aqHldv5lfpp^Fy!mmv2?|?t$kKO?nh5rEjJDc=7 @@ -158,13 +163,13 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP std::vector unpooledBufs; //Offsets to the relevant G1M subSections - std::vectorG1MSOffsets; - std::vectorG1MMOffsets; - std::vectorG1MGOffsets; - std::vectorNUNOOffsets; - std::vectorNUNVOffsets; - std::vectorNUNSOffsets; - std::vectorSOFTOffsets; + std::vectorG1MSOffsets; + std::vectorG1MMOffsets; + std::vectorG1MGOffsets; + std::vectorNUNOOffsets; + std::vectorNUNVOffsets; + std::vectorNUNSOffsets; + std::vectorSOFTOffsets; //Subsections data containers std::vector>G1MMs; @@ -184,8 +189,9 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP //NUN maps std::map> fileIndexToNUNO1Map; - std::map> fileIndexToNUNO3Map; + std::map>> fileIndexToNUNO3Map; //The second value is the subset index in the nunoSubsets array std::map> fileIndexToNUNV1Map; + std::vector> nunoSubsets; //MAX_CTRL_PTS is the max number of CPs in the shader for now, it may change eventually //Maps containers std::vector mapPositions; @@ -195,8 +201,9 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP //Meshes std::vector driverMeshes; - //Fixes + //Fixes and hacks bool bIsSkeletonOrigin = true; + bool bIsG1MSUnordered = false; //On recent games the skeleton is laid out such as the parent is always read before the child. Not the case in very old G1M. RichMat43 rootCoords; void* ctx = rapi->rpgCreateContext(); //Create context @@ -325,7 +332,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP //Going through all the sections and adding the chunks bHasParsedG1MS = false; - size_t chunkOffset = g1mHeader.firstChunkOffset; + uint32_t chunkOffset = g1mHeader.firstChunkOffset; for (auto i = 0; i < g1mHeader.chunkCount; i++) { GResourceHeader header = reinterpret_cast*>(fb + chunkOffset); @@ -372,13 +379,12 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP std::vector isOffsetInternal; for (auto i = 0; i< G1MSOffsets.size(); i++) { - G1MS temp = G1MS(fileBuffers[i], G1MSOffsets[i]); + G1MS temp = G1MS(fileBuffers[i], G1MSOffsets[i], bIsG1MSUnordered); if (temp.bIsInternal) { internalSkeletons.push_back(std::move(temp)); isOffsetInternal.push_back(true); } - else { externalSkeletons.push_back(std::move(temp)); @@ -607,7 +613,10 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP } else { - joint->eData.parent = joints + globalToFinal[s.localIDToGlobalID[parent]]; + if(bIsG1MSUnordered) + joint->eData.parent = joints + s.localIDToGlobalID[parent]; //avoid missing a global to final index, global indices don't exist anyways in these old g1m so it's equivalent to identity + else + joint->eData.parent = joints + globalToFinal[s.localIDToGlobalID[parent]]; } snprintf(joint->name, 128, "bone_%d", s.localIDToGlobalID[idx]); joint->mat = s.joints[idx].rotation.ToMat43().GetInverse().m; @@ -631,11 +640,18 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP uint32_t parent = s.joints[idx].parent; if (parent >> 31) //0x80000000 flag { - joint->eData.parent = joints + globalToFinal[internalSkeletons[0].localIDToGlobalID[parent]]; + if(bIsG1MSUnordered) + joint->eData.parent = joints + internalSkeletons[0].localIDToGlobalID[parent]; + else + joint->eData.parent = joints + globalToFinal[internalSkeletons[0].localIDToGlobalID[parent]]; + } else { - joint->eData.parent = joints + globalToFinal[s.localIDToGlobalID[parent]]; + if (bIsG1MSUnordered) + joint->eData.parent = joints + s.localIDToGlobalID[parent]; + else + joint->eData.parent = joints + globalToFinal[s.localIDToGlobalID[parent]]; } snprintf(joint->name, 128, "physbone_%d", s.localIDToGlobalID[idx]); joint->mat = s.joints[idx].rotation.ToMat43().GetInverse().m; @@ -731,13 +747,29 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP for (auto& nun3 : NUNOs[i].Nuno3s) { + + if (nun3.parentSetID>=0) + { + bNUNO5HasSubsets = true; + int jointStart = fileIndexToNUNO3Map[NUNOFileIDs[i]][nun3.parentSetID].first; + fileIndexToNUNO3Map[NUNOFileIDs[i]].push_back(std::make_pair(jointStart,nunoSubsets.size())); + std::array subset = std::array(); + subset.fill(0); + for (auto j = 0; j < nun3.controlPoints.size(); j++) + { + subset[j] = nun3.influences[j].P1; + } + nunoSubsets.push_back(subset); + continue; + } + uint32_t jointStart = jointIndex; uint32_t nunParentJointID; if (nun3.parentID >> 31) nunParentJointID = globalToFinal[nun3.parentID ^ 0x80000000]; else nunParentJointID = globalToFinal[nun3.parentID]; - fileIndexToNUNO3Map[NUNOFileIDs[i]].push_back(jointStart); + fileIndexToNUNO3Map[NUNOFileIDs[i]].push_back(std::make_pair(jointStart, -1)); //Prepare driverMeshes mesh_t dMesh; @@ -768,12 +800,19 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP RichMat43 mat1 = RichMat43(joints[nunParentJointID].mat); RichMat43 mat2 = RichMat43(joints[parentID].mat); jointMatrix = mat1 * mat2.GetInverse(); + if (bIsNUNO5Global && link.P5 == 0) + jointMatrix = mat1; p = jointMatrix.TransformPoint(p); g_mfn->Math_VecCopy(p.v, jointMatrix.m.o); } RichMat43 mat3 = RichMat43(joints[parentID].mat); joint->mat = (jointMatrix * mat3).m; - snprintf(joint->name, 128, "nuno3_p_%d_bone_%d", nunParentJointID,jointIndex); + if(bIsNUNO5Global && link.P5 == 0)//Model space coords if P5 is null, see 79d40f50 + joint->mat = jointMatrix.m; + if(!bIsNUNO5Global) + snprintf(joint->name, 128, "nuno3_p_%d_bone_%d", nunParentJointID,jointIndex); + else + snprintf(joint->name, 128, "nuno5_p_%d_bone_%d", nunParentJointID, jointIndex); joint->index = jointIndex; joint->eData.parent = joints + parentID; joint->eData.mpDebug = rapi->Noesis_AllocBoneDebugInfo(nullptr); @@ -800,7 +839,6 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP //Driver mesh indices createDriverIndexBuffers(dMesh, polys, unpooledBufs, rapi); - driverMeshes.push_back(dMesh); } } @@ -1088,6 +1126,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP std::map lodMap; std::map bIsPhysType1; //NUN meshes. We could replace maps by vectors but we're not sure if indices are always ordered std::map bIsPhysType2; //"Danglies" (hair strands etc) + std::map nunSubsetIndex; //NUN meshes using subsets of parent NUN. std::map nunMapJointIndex; if (bLoadAllLODs) @@ -1111,7 +1150,11 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP else if (mesh.externalID >= 10000 && mesh.externalID < 20000) nunMapJointIndex[index] = fileIndexToNUNV1Map[i][mesh.externalID % 10000]; else if (mesh.externalID >= 20000 && mesh.externalID < 30000) - nunMapJointIndex[index] = fileIndexToNUNO3Map[i][mesh.externalID % 10000]; + { + nunMapJointIndex[index] = fileIndexToNUNO3Map[i][mesh.externalID % 10000].first; + if (fileIndexToNUNO3Map[i][mesh.externalID % 10000].second >= 0) + nunSubsetIndex[index] = fileIndexToNUNO3Map[i][mesh.externalID % 10000].second; + } } } } @@ -1134,7 +1177,11 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP else if (mesh.externalID >= 10000 && mesh.externalID < 20000) nunMapJointIndex[index] = fileIndexToNUNV1Map[i][mesh.externalID % 10000]; else if (mesh.externalID >= 20000 && mesh.externalID < 30000) - nunMapJointIndex[index] = fileIndexToNUNO3Map[i][mesh.externalID % 10000]; + { + nunMapJointIndex[index] = fileIndexToNUNO3Map[i][mesh.externalID % 10000].first; + if (fileIndexToNUNO3Map[i][mesh.externalID % 10000].second >= 0) + nunSubsetIndex[index] = fileIndexToNUNO3Map[i][mesh.externalID % 10000].second; + } } } } @@ -1377,7 +1424,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP jointIdB2 = vbuf.bufferAdress + attribute.offset; jointIdB2Type = attribute.dataType; } - if (bIsPhysType1[smIdx]) + if (bIsPhysType1[smIdx] && (attribute.layer == 0)) //For some reason some cloth meshes have a second joint weight set too, only take the first one. { controlPointRelativeIndices1 = vbuf.bufferAdress + attribute.offset; cPIdx1Type = attribute.dataType; @@ -1396,7 +1443,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP jointWB2 = vbuf.bufferAdress + attribute.offset; jointWB2Type = attribute.dataType; } - if (bIsPhysType1[smIdx]) + if (bIsPhysType1[smIdx] && (attribute.layer == 0)) //For some reason some cloth meshes have a second joint weight set too, only take the first one. centerOfMassWeightsSet1 = vbuf.bufferAdress + attribute.offset; break; @@ -1512,7 +1559,22 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP //Transforming vertices for Cloth Type 1 if (bIsPhysType1[smIdx] && joints) { - modelBone_t* CPSet = joints + nunMapJointIndex[smIdx]; + modelBone_t* CPSet = nullptr; + if (nunSubsetIndex.count(smIdx) > 0) //If this physics mesh is associated to a subset, use the CPs from the parent entry + { + modelBone_t tempSet[MAX_CTRL_PTS]; + for (auto ctrl = 0; ctrl < MAX_CTRL_PTS; ctrl++) + { + tempSet[ctrl] = *(joints +nunMapJointIndex[smIdx] + nunoSubsets[nunSubsetIndex[smIdx]][ctrl]); + } + CPSet = tempSet; + } + else + { + CPSet = joints + nunMapJointIndex[smIdx]; + } + + float* posB = (float*)(controlPointsWeightsSet1); RichVec3 u1, u2, u3, u4, v1, v2, v3, v4; RichVec4 centerOfMassWVec1, centerOfMassWVec2, controlPCMWVec1, controlPCMWVec2; @@ -1548,7 +1610,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP switch (cPIdx1Type) { - case VADataType_UByte_x4: + case EG1MGVADatatype::VADataType_UByte_x4: { uint8_t* indexPointer = (uint8_t*)controlPointRelativeIndices1; u1 = RichMat43(CPSet[indexPointer[index]].mat.o, CPSet[indexPointer[index + 1]].mat.o, @@ -1557,9 +1619,15 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP CPSet[indexPointer[index + 2]].mat.o, CPSet[indexPointer[index + 3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec2).ToVec3(); break; } - case VADataType_UShort_x4: + case EG1MGVADatatype::VADataType_UShort_x4: { uint16_t* indexPointer = (uint16_t*)(controlPointRelativeIndices1+index); + //Check if there's a -1 (i.e. unused weight) + for (auto w = 0; w < 4; w++) + { + if (indexPointer[w] == 65535) + indexPointer[w] = 0;//Just put whatever that won't cause a crash + } u1 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, CPSet[indexPointer[2]].mat.o, CPSet[indexPointer[3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec1).ToVec3(); v1 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, @@ -1572,7 +1640,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP switch (cPIdx2Type) { - case VADataType_UByte_x4: + case EG1MGVADatatype::VADataType_UByte_x4: { uint8_t* indexPointer = (uint8_t*)controlPointRelativeIndices2; u2 = RichMat43(CPSet[indexPointer[index]].mat.o, CPSet[indexPointer[index + 1]].mat.o, @@ -1581,9 +1649,14 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP CPSet[indexPointer[index + 2]].mat.o, CPSet[indexPointer[index + 3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec2).ToVec3(); break; } - case VADataType_UShort_x4: + case EG1MGVADatatype::VADataType_UShort_x4: { uint16_t* indexPointer = (uint16_t*)(controlPointRelativeIndices2+index); + for (auto w = 0; w < 4; w++) + { + if (indexPointer[w] == 65535) + indexPointer[w] = 0;//Just put whatever that won't cause a crash + } u2 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, CPSet[indexPointer[2]].mat.o, CPSet[indexPointer[3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec1).ToVec3(); v2 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, @@ -1596,7 +1669,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP switch (cPIdx3Type) { - case VADataType_UByte_x4: + case EG1MGVADatatype::VADataType_UByte_x4: { uint8_t* indexPointer = (uint8_t*)controlPointRelativeIndices3; u3 = RichMat43(CPSet[indexPointer[index]].mat.o, CPSet[indexPointer[index + 1]].mat.o, @@ -1605,9 +1678,14 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP CPSet[indexPointer[index + 2]].mat.o, CPSet[indexPointer[index + 3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec2).ToVec3(); break; } - case VADataType_UShort_x4: + case EG1MGVADatatype::VADataType_UShort_x4: { uint16_t* indexPointer = (uint16_t*)(controlPointRelativeIndices3+index); + for (auto w = 0; w < 4; w++) + { + if (indexPointer[w] == 65535) + indexPointer[w] = 0;//Just put whatever that won't cause a crash + } u3 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, CPSet[indexPointer[2]].mat.o, CPSet[indexPointer[3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec1).ToVec3(); v3 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, @@ -1620,7 +1698,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP switch (cPIdx4Type) { - case VADataType_UByte_x4: + case EG1MGVADatatype::VADataType_UByte_x4: { uint8_t* indexPointer = (uint8_t*)controlPointRelativeIndices4; u4 = RichMat43(CPSet[indexPointer[index]].mat.o, CPSet[indexPointer[index + 1]].mat.o, @@ -1629,9 +1707,14 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP CPSet[indexPointer[index + 2]].mat.o, CPSet[indexPointer[index + 3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec2).ToVec3(); break; } - case VADataType_UShort_x4: + case EG1MGVADatatype::VADataType_UShort_x4: { uint16_t* indexPointer = (uint16_t*)(controlPointRelativeIndices4+index); + for (auto w = 0; w < 4; w++) + { + if (indexPointer[w] == 65535) + indexPointer[w] = 0;//Just put whatever that won't cause a crash + } u4 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, CPSet[indexPointer[2]].mat.o, CPSet[indexPointer[3]].mat.o).GetTranspose().TransformVec4(controlPCMWVec1).ToVec3(); v4 = RichMat43(CPSet[indexPointer[0]].mat.o, CPSet[indexPointer[1]].mat.o, @@ -1656,22 +1739,23 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP } if (c.Length() == 0) //Should probably check it at the start of the for loop for better performance. Oh well { - transformPosF(controlPointsWeightsSet1 + index, 1, phys1Stride, &CPSet->eData.parent->mat); + transformPosF(controlPointsWeightsSet1 + index, 1, phys1Stride, &(joints + nunMapJointIndex[smIdx])->eData.parent->mat); bHasSkinnedParts = true; for (auto k = 0; k < 4; k++) { dstW[4 * j + k] = centerOfMassWVec1[k]; - if (cPIdx1Type == VADataType_UByte_x4) + if (cPIdx1Type == EG1MGVADatatype::VADataType_UByte_x4) dstIB[4 * j + k] = *(uint8_t*)(controlPointRelativeIndices1 + index + k); - else if (cPIdx1Type == VADataType_UShort_x4) + else if (cPIdx1Type == EG1MGVADatatype::VADataType_UShort_x4) dstIS[4*j +k] = *(uint16_t*)(controlPointRelativeIndices1 + index + k); } } else { RichVec3 d = b.Cross(c); + if (bIsNUNO5Global) + d = d.Normalized(); c = d * depth + a; - d = c.Normalized(); if (bBigEndian) { c.ChangeEndian(); @@ -1683,17 +1767,19 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP } rapi->rpgBindPositionBuffer(controlPointsWeightsSet1, RPGEODATA_FLOAT, phys1Stride); rapi->rpgBindNormalBuffer(nullptr, RPGEODATA_FLOAT, phys1Stride); - if (bHasSkinnedParts) + if (bHasSkinnedParts && !bNUNO5HasSubsets) //temporary hack to disable anchored cloth for now when NUNO5 has subsets, treat it like the others { - rapi->rpgBindBoneIndexBuffer(jointIBFinal, cPIdx1Type == VADataType_UByte_x4 ? RPGEODATA_UBYTE : RPGEODATA_USHORT, cPIdx1Type == VADataType_UByte_x4 ? 4 : 8, 4); + rapi->rpgBindBoneIndexBuffer(jointIBFinal, cPIdx1Type == EG1MGVADatatype::VADataType_UByte_x4 ? RPGEODATA_UBYTE : RPGEODATA_USHORT, cPIdx1Type == EG1MGVADatatype::VADataType_UByte_x4 ? 4 : 8, 4); rapi->rpgBindBoneWeightBuffer(jointWBFinal, RPGEODATA_FLOAT, 16, 4); } } //Transforming vertices for Cloth type 2 if (bIsPhysType2[smIdx] && jStride>0 && joints) - { - for (auto j = 0; j < submesh.vertexCount; j++) + { + auto attribute = g1mg.vertexAttributeSets[submesh.vertexBufferIndex].attributes[0]; + int vCount = g1mg.vertexBuffers[attribute.bufferID].count; + for (auto j = 0; j < vCount; j++) { uint32_t bPID; //Read the jointIndex that will give us the jointPalette entry with all its info @@ -1713,7 +1799,7 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP } bPID /= 3; - uint32_t jID = g1mg.jointPalettes[submesh.bonePaletteIndex].entries[bPID].physicsIndex;; + uint32_t jID = g1mg.jointPalettes[submesh.bonePaletteIndex].entries[bPID].physicsIndex &0x7FFFFFFF; modelMatrix_t matrix = joints[jID].mat; //Here we assume that the first internal skel has the physics joint's parent (which is always the case on all the samples) transformPosF(posB + j*jStride, 1, jStride, &joints[jID].mat); @@ -1732,13 +1818,13 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP //Indices switch (jointIdBType) { - case VADataType_UByte_x4: + case EG1MGVADatatype::VADataType_UByte_x4: rapi->rpgBindBoneIndexBuffer(jointIdB, RPGEODATA_UBYTE, jStride, jidCount); break; - case VADataType_UShort_x4: + case EG1MGVADatatype::VADataType_UShort_x4: rapi->rpgBindBoneIndexBuffer(jointIdB, RPGEODATA_USHORT, jStride, jidCount); break; - case VADataType_UInt_x4: + case EG1MGVADatatype::VADataType_UInt_x4: rapi->rpgBindBoneIndexBuffer(jointIdB, RPGEODATA_UINT, jStride, jidCount); break; default: @@ -1750,14 +1836,14 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP //See if we can inject the weights directly switch (jointWBType) { - case VADataType_Float_x4: + case EG1MGVADatatype::VADataType_Float_x4: switch (jointWB2Type) { - case VADataType_Float_x4: + case EG1MGVADatatype::VADataType_Float_x4: rapi->rpgBindBoneWeightBuffer(jointWB, RPGEODATA_FLOAT, jStride, jidCount); bHasWBeenSet = true; break; - case VADataType_Dummy: + case EG1MGVADatatype::VADataType_Dummy: if (!jointIdB2) //necessary to avoid edge case mentionned above { rapi->rpgBindBoneWeightBuffer(jointWB, RPGEODATA_FLOAT, jStride, jidCount); @@ -1769,14 +1855,14 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP } break; - case VADataType_HalfFloat_x4: + case EG1MGVADatatype::VADataType_HalfFloat_x4: switch (jointWB2Type) { - case VADataType_HalfFloat_x4: + case EG1MGVADatatype::VADataType_HalfFloat_x4: rapi->rpgBindBoneWeightBuffer(jointWB, RPGEODATA_HALFFLOAT, jStride, jidCount); bHasWBeenSet = true; break; - case VADataType_Dummy: + case EG1MGVADatatype::VADataType_Dummy: if (!jointIdB2) //necessary to avoid edge case mentionned above { rapi->rpgBindBoneWeightBuffer(jointWB, RPGEODATA_HALFFLOAT, jStride, jidCount); @@ -1788,14 +1874,14 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP } break; - case VADataType_NormUByte_x4: + case EG1MGVADatatype::VADataType_NormUByte_x4: switch (jointWB2Type) { - case VADataType_NormUByte_x4: + case EG1MGVADatatype::VADataType_NormUByte_x4: rapi->rpgBindBoneWeightBuffer(jointWB, RPGEODATA_UBYTE, jStride, jidCount); bHasWBeenSet = true; break; - case VADataType_Dummy: + case EG1MGVADatatype::VADataType_Dummy: if (!jointIdB2) //necessary to avoid edge case mentionned above { rapi->rpgBindBoneWeightBuffer(jointWB, RPGEODATA_UBYTE, jStride, jidCount); @@ -1819,10 +1905,11 @@ noesisModel_t* ProcessModel(BYTE* fileBuffer, int bufferLen, int& numMdl, noeRAP } } - //Skinning for submesh type 55 - if (submesh.submeshType == 55) + //Skinning for rigid submeshes + if (submesh.submeshType & 0x2) //See 79d40f50.g1m from SoP for 63 { - int size = submesh.vertexCount; + auto attribute = g1mg.vertexAttributeSets[submesh.vertexBufferIndex].attributes[0]; + int size = g1mg.vertexBuffers[attribute.bufferID].count; BYTE* jointIBFinal = (BYTE*)rapi->Noesis_PooledAlloc(sizeof(char) * size); memset(jointIBFinal, 0, size); rapi->rpgBindBoneIndexBuffer(jointIBFinal, RPGEODATA_UBYTE, 1, 1); diff --git a/Source/Public/G1A.h b/Source/Public/G1A.h index 861a0ae..ffcd9dc 100644 --- a/Source/Public/G1A.h +++ b/Source/Public/G1A.h @@ -11,9 +11,11 @@ struct G1AHeader uint32_t dataSectionOffset; uint16_t boneInfoCount; uint16_t boneMaxID; + uint32_t chunkVersion; G1AHeader(BYTE* buffer, int bufferLen, uint32_t& offset) { GResourceHeader sectionHeader = reinterpret_cast*>(buffer); + chunkVersion = sectionHeader.chunkVersion; offset += sizeof(GResourceHeader); animationType = *(uint16_t*)(buffer + offset); offset += 4; //skip unknown @@ -126,26 +128,55 @@ struct G1A LITTLE_BIG_SWAP(dataOffset); } offset = checkpoint2 + dataOffset * 0x10; - for (auto k = 0; k < keyFrameCount; k++) - { - std::array temp; - memcpy(temp.data(), buffer + offset, 16); - if (bBigEndian) + + if (header.chunkVersion > 0x30303430) + { //components before timing + for (auto k = 0; k < keyFrameCount; k++) + { + std::array temp; + memcpy(temp.data(), buffer + offset, 16); + if (bBigEndian) + { + for (auto& e : temp) + LITTLE_BIG_SWAP(e); + } + data.push_back(std::move(temp)); + offset += 16; + } + for (auto k = 0; k < keyFrameCount; k++) { - for (auto& e : temp) - LITTLE_BIG_SWAP(e); + float temp = *(float*)(buffer + offset); + if (bBigEndian) + LITTLE_BIG_SWAP(temp); + times.push_back(temp); + offset += 4; } - data.push_back(std::move(temp)); - offset += 16; } - for (auto k = 0; k < keyFrameCount; k++) + else { - float temp = *(float*)(buffer + offset); - if (bBigEndian) - LITTLE_BIG_SWAP(temp); - times.push_back(temp); - offset += 4; + //timing before components + for (auto k = 0; k < keyFrameCount; k++) + { + float temp = *(float*)(buffer + offset); + if (bBigEndian) + LITTLE_BIG_SWAP(temp); + times.push_back(temp); + offset += 4; + } + for (auto k = 0; k < keyFrameCount; k++) + { + std::array temp; + memcpy(temp.data(), buffer + offset, 16); + if (bBigEndian) + { + for (auto& e : temp) + LITTLE_BIG_SWAP(e); + } + data.push_back(std::move(temp)); + offset += 16; + } } + chanValues.push_back(std::move(data)); chanTimes.push_back(std::move(times)); } diff --git a/Source/Public/G1M/G1MG.h b/Source/Public/G1M/G1MG.h index 0c268d7..aa8502f 100644 --- a/Source/Public/G1M/G1MG.h +++ b/Source/Public/G1M/G1MG.h @@ -81,15 +81,15 @@ struct G1MG std::vector> submeshes; std::vector> meshGroups; - G1MG(BYTE* buffer, size_t startOffset, G1MS* internalSkel, G1MS* externalSkel, std::map* globalToFinal) + G1MG(BYTE* buffer, uint32_t startOffset, G1MS* internalSkel, G1MS* externalSkel, std::map* globalToFinal) { - size_t offset = startOffset; + uint32_t offset = startOffset; //Read headers GResourceHeader sectionHeader = reinterpret_cast*>(buffer + offset); offset = startOffset + 12; G1MGHeader g1mgHeader = reinterpret_cast*>(buffer + offset); offset += sizeof(G1MGHeader); - size_t checkpoint = offset; + uint32_t checkpoint = offset; for (auto i = 0; i < g1mgHeader.sectionCount; i++) { offset = checkpoint; diff --git a/Source/Public/G1M/G1MG/G1MGVertexAttributes.h b/Source/Public/G1M/G1MG/G1MGVertexAttributes.h index 5b1a91e..7bfc0a2 100644 --- a/Source/Public/G1M/G1MG/G1MGVertexAttributes.h +++ b/Source/Public/G1M/G1MG/G1MGVertexAttributes.h @@ -2,7 +2,7 @@ #ifndef G1M_G_VA #define G1M_G_VA -enum EG1MGVADatatype : uint8_t +enum class EG1MGVADatatype : uint8_t { VADataType_Float_x1 = 0x00, VADataType_Float_x2 = 0x01, @@ -17,7 +17,7 @@ enum EG1MGVADatatype : uint8_t VADataType_Dummy = 0xFF }; -enum EG1MGVASemantic : uint8_t +enum class EG1MGVASemantic : uint8_t { Position = 0x00, JointWeight, diff --git a/Source/Public/G1M/G1MM.h b/Source/Public/G1M/G1MM.h index 2a31013..dd49940 100644 --- a/Source/Public/G1M/G1MM.h +++ b/Source/Public/G1M/G1MM.h @@ -8,9 +8,9 @@ struct G1MM { uint32_t matrixCount; std::vector matrices; - G1MM(BYTE* buffer, size_t startOffset) + G1MM(BYTE* buffer, uint32_t startOffset) { - size_t offset = startOffset; + uint32_t offset = startOffset; GResourceHeader sectionHeader = reinterpret_cast*>(buffer + offset); offset = startOffset + 12; matrixCount = *reinterpret_cast(buffer+offset); diff --git a/Source/Public/G1M/G1MS.h b/Source/Public/G1M/G1MS.h index 7deec36..e1ed92c 100644 --- a/Source/Public/G1M/G1MS.h +++ b/Source/Public/G1M/G1MS.h @@ -43,9 +43,9 @@ struct G1MS std::map globalIDToLocalID; std::vector joints; std::vector jointLocalIndexToExtract; - G1MS(BYTE* buffer, size_t startOffset) + G1MS(BYTE* buffer, uint32_t startOffset, bool& bIsG1MSUnordered) { - size_t offset = startOffset; + uint32_t offset = startOffset; GResourceHeader sectionHeader = reinterpret_cast*>(buffer + offset); offset += 12; //Read header @@ -75,6 +75,7 @@ struct G1MS else { offset = startOffset + header.jointInfoOffset; + bIsG1MSUnordered = true; for (auto i = 0; i < header.jointCount; i++) { localIDToGlobalID[i] = i; diff --git a/Source/Public/G1M/NUNO.h b/Source/Public/G1M/NUNO.h index 50eadf2..df9f985 100644 --- a/Source/Public/G1M/NUNO.h +++ b/Source/Public/G1M/NUNO.h @@ -7,6 +7,7 @@ #define NUNO2_MAGIC 0x00030002 #define NUNO3_MAGIC 0x00030003 #define NUNO4_MAGIC 0x00030004 +#define NUNO5_MAGIC 0x00030005 struct NunInfluence { @@ -16,6 +17,10 @@ struct NunInfluence int P4; float P5; float P6; + NunInfluence(){} + NunInfluence(NunInfluence* ptr) : NunInfluence(*ptr) + { + } }; template @@ -39,15 +44,15 @@ template struct NUNO1 { uint32_t parentID; - size_t entrySize; + uint32_t entrySize; std::vector controlPoints; std::vector influences; - NUNO1(BYTE* buffer, size_t startOffset, uint32_t version) + NUNO1(BYTE* buffer, uint32_t startOffset, uint32_t version) { - size_t offset = startOffset; + uint32_t offset = startOffset; parentID = *reinterpret_cast(buffer + offset); - size_t controlPointCount = *reinterpret_cast(buffer + offset + 4); - size_t unknownSectionCount = *reinterpret_cast(buffer + offset + 8); + uint32_t controlPointCount = *reinterpret_cast(buffer + offset + 4); + uint32_t unknownSectionCount = *reinterpret_cast(buffer + offset + 8); uint32_t skip1 = *reinterpret_cast(buffer + offset + 12); uint32_t skip2 = *reinterpret_cast(buffer + offset + 16); uint32_t skip3 = *reinterpret_cast(buffer + offset + 20); @@ -107,79 +112,169 @@ template struct NUNO3 { uint32_t parentID; - size_t entrySize; + uint32_t entrySize; + uint32_t entryID; + int parentSetID; std::vector controlPoints; std::vector influences; - NUNO3(BYTE* buffer, size_t startOffset, uint32_t version) + NUNO3(BYTE* buffer, uint32_t startOffset, uint32_t version, bool bIsNuno5, std::map* entryIDToNunoID) { - size_t offset = startOffset; + uint32_t offset = startOffset; parentID = *reinterpret_cast(buffer + offset); - size_t controlPointCount = *reinterpret_cast(buffer + offset + 4); - size_t unknownSectionCount = *reinterpret_cast(buffer + offset + 8); - uint32_t skip1 = *reinterpret_cast(buffer + offset + 12); - uint32_t skip2 = *reinterpret_cast(buffer + offset + 20); - uint32_t skip3 = *reinterpret_cast(buffer + offset + 24); - uint32_t skip4 = *reinterpret_cast(buffer + offset + 28); - - if (bBigEndian) - { - LITTLE_BIG_SWAP(parentID); - LITTLE_BIG_SWAP(controlPointCount); - LITTLE_BIG_SWAP(unknownSectionCount); - LITTLE_BIG_SWAP(skip1); - LITTLE_BIG_SWAP(skip2); - LITTLE_BIG_SWAP(skip3); - LITTLE_BIG_SWAP(skip4); - } - - offset += 32; - if (version < 0x30303330) + parentSetID = -1; + if (!bIsNuno5) //"Classic" NUNO3 { - offset += 0xA8; - if (version >= 0x30303235) - offset += 0x10; - } + uint32_t controlPointCount = *reinterpret_cast(buffer + offset + 4); + uint32_t unknownSectionCount = *reinterpret_cast(buffer + offset + 8); + uint32_t skip1 = *reinterpret_cast(buffer + offset + 12); + uint32_t skip2 = *reinterpret_cast(buffer + offset + 20); + uint32_t skip3 = *reinterpret_cast(buffer + offset + 24); + uint32_t skip4 = *reinterpret_cast(buffer + offset + 28); - else - { - offset += 8; - uint32_t temp = *reinterpret_cast(buffer + offset); if (bBigEndian) - LITTLE_BIG_SWAP(temp); - offset += temp; - } + { + LITTLE_BIG_SWAP(parentID); + LITTLE_BIG_SWAP(controlPointCount); + LITTLE_BIG_SWAP(unknownSectionCount); + LITTLE_BIG_SWAP(skip1); + LITTLE_BIG_SWAP(skip2); + LITTLE_BIG_SWAP(skip3); + LITTLE_BIG_SWAP(skip4); + } - controlPoints.resize(controlPointCount); - influences.resize(controlPointCount); + offset += 32; + if (version < 0x30303330) + { + offset += 0xA8; + if (version >= 0x30303235) + offset += 0x10; + } - //ControlPoints - memcpy(controlPoints.data(), buffer + offset, controlPointCount * sizeof(RichVec4)); - if (bBigEndian) - { - for (auto& cp : controlPoints) + else { - cp.ChangeEndian(); + offset += 8; + uint32_t temp = *reinterpret_cast(buffer + offset); + if (bBigEndian) + LITTLE_BIG_SWAP(temp); + offset += temp; + } + + controlPoints.resize(controlPointCount); + influences.resize(controlPointCount); + + //ControlPoints + memcpy(controlPoints.data(), buffer + offset, controlPointCount * sizeof(RichVec4)); + if (bBigEndian) + { + for (auto& cp : controlPoints) + { + cp.ChangeEndian(); + } } + offset += controlPointCount * sizeof(RichVec4); + //Influences + memcpy(influences.data(), buffer + offset, controlPointCount * sizeof(NunInfluence)); + if (bBigEndian) + { + for (auto& infl : influences) + { + LITTLE_BIG_SWAP(infl.P1); + LITTLE_BIG_SWAP(infl.P2); + LITTLE_BIG_SWAP(infl.P3); + LITTLE_BIG_SWAP(infl.P4); + LITTLE_BIG_SWAP(infl.P5); + LITTLE_BIG_SWAP(infl.P6); + } + } + + offset += controlPointCount * sizeof(NunInfluence); + offset += 48 * unknownSectionCount; + offset += (4 * skip1 + 8 * skip2 + 12 * skip3 + 8 * skip4); } - offset += controlPointCount * sizeof(RichVec4); - //Influences - memcpy(influences.data(), buffer + offset, controlPointCount * sizeof(NunInfluence)); - if (bBigEndian) + else //NUNO5 { - for (auto& infl : influences) + //See the BT for more info, I'll just take the necessary values here. + uint32_t lodCount = *reinterpret_cast(buffer + offset + 8); //Not sure if this is the actual count or if the value is hardcoded + entryID = *reinterpret_cast(buffer + offset + 20); + if (*reinterpret_cast(buffer + offset + 22) & 0x7FF) + parentSetID = (*entryIDToNunoID)[entryID]; + + offset += 0x24; + for (auto l = 0; l < lodCount; l++) { - LITTLE_BIG_SWAP(infl.P1); - LITTLE_BIG_SWAP(infl.P2); - LITTLE_BIG_SWAP(infl.P3); - LITTLE_BIG_SWAP(infl.P4); - LITTLE_BIG_SWAP(infl.P5); - LITTLE_BIG_SWAP(infl.P6); + uint32_t controlPointCount = *reinterpret_cast(buffer + offset); + uint32_t cpSectionRelatedCount = *reinterpret_cast(buffer + offset + 4); + uint32_t skip1 = *reinterpret_cast(buffer + offset + 8); + uint32_t skip2 = *reinterpret_cast(buffer + offset + 12); + uint32_t skip3 = *reinterpret_cast(buffer + offset + 16); + uint32_t skip4 = *reinterpret_cast(buffer + offset + 20); + uint32_t skip5 = *reinterpret_cast(buffer + offset + 24); + uint32_t skip6 = *reinterpret_cast(buffer + offset + 28); + uint32_t skip7 = *reinterpret_cast(buffer + offset + 32); + uint32_t skip8 = *reinterpret_cast(buffer + offset + 36); + uint32_t skip9 = *reinterpret_cast(buffer + offset + 40); + + if (bBigEndian) + { + LITTLE_BIG_SWAP(lodCount); + LITTLE_BIG_SWAP(controlPointCount); + LITTLE_BIG_SWAP(cpSectionRelatedCount); + LITTLE_BIG_SWAP(skip1); + LITTLE_BIG_SWAP(skip2); + LITTLE_BIG_SWAP(skip3); + LITTLE_BIG_SWAP(skip4); + LITTLE_BIG_SWAP(skip5); + LITTLE_BIG_SWAP(skip6); + LITTLE_BIG_SWAP(skip7); + LITTLE_BIG_SWAP(skip8); + LITTLE_BIG_SWAP(skip9); + } + offset += 0x30; + uint32_t cpOffset = *reinterpret_cast(buffer + offset); + if (bBigEndian) + LITTLE_BIG_SWAP(cpOffset); + offset += cpOffset; + if (!l) //First LOD, we grab its info. Otherwise, just skip. + { + for (auto i = 0; i < controlPointCount; i++) + { + controlPoints.push_back(RichVec3((float*)(buffer + offset)).ToVec4()); + offset += 0x18; + influences.push_back(reinterpret_cast(buffer + offset)); //P5/P6 will get a wrong value but we don't use them anywhere anyways so that's fine + offset += 0x14; + } + if (bBigEndian) + { + for (auto& cp : controlPoints) + { + cp.ChangeEndian(); + } + for (auto& infl : influences) + { + LITTLE_BIG_SWAP(infl.P1); + LITTLE_BIG_SWAP(infl.P2); + LITTLE_BIG_SWAP(infl.P3); + LITTLE_BIG_SWAP(infl.P4); + LITTLE_BIG_SWAP(infl.P5); + LITTLE_BIG_SWAP(infl.P6); + } + } + } + else + offset += controlPointCount * 0x2C; + + //Skipping the other parts, that define physics parameters and other info that we don't need + offset += 0x20 * controlPointCount; + if (cpSectionRelatedCount == 3) + offset += 0x18 * controlPointCount; + offset += (skip1 * 4 + skip2 * 12 + skip3 * 16 + skip4 * 12 + skip5 * 8 + skip6 * 0x30 + skip7 * 0x48 + skip8*0x20); + for (auto u = 0; u < skip9; u++) + { + uint32_t tempCount = *reinterpret_cast(buffer + offset); + offset += (tempCount * 4 + 0x10); + } } } - - offset += controlPointCount * sizeof(NunInfluence); - offset += 48 * unknownSectionCount; - offset += (4 * skip1 + 8 * skip2 + 12 * skip3 + 8 * skip4); entrySize = offset - startOffset; } }; @@ -189,9 +284,9 @@ struct NUNO { std::vector> Nuno1s; std::vector> Nuno3s; - NUNO(BYTE* buffer, size_t startOffset) + NUNO(BYTE* buffer, uint32_t startOffset) { - size_t offset = startOffset; + uint32_t offset = startOffset; //Reading the header, we want the version GResourceHeader header = reinterpret_cast*>(buffer + startOffset); offset += sizeof(GResourceHeader); @@ -206,7 +301,7 @@ struct NUNO { NunHeader subHeader = reinterpret_cast*>(buffer + offset); offset += 12; - size_t checkpoint; + uint32_t checkpoint; switch (subHeader.magic) { case NUNO1_MAGIC: @@ -222,11 +317,60 @@ struct NUNO checkpoint = 0; for (auto i = 0; i < subHeader.entryCount; i++) { - Nuno3s.push_back(std::move(NUNO3(buffer, offset + checkpoint, header.chunkVersion))); + Nuno3s.push_back(std::move(NUNO3(buffer, offset + checkpoint, header.chunkVersion,false, nullptr))); checkpoint += Nuno3s.back().entrySize; } offset += checkpoint; break; + case NUNO5_MAGIC: + { + checkpoint = 0; + bIsNUNO5Global = true; //Set the NUNO5 boolean to true for cloth processing. If I ever see NUNO3 and NUNO5 in the same model I'll change the logic. + std::map entryIDToNunoID; + std::map> nunoIDToSubsetMap; + for (auto i = 0; i < subHeader.entryCount; i++) + { + //This is not a mistake. Seems like these are registered to NUNO3, contrary to the externalIDs distinction between NUN01,NUNV and NUNO3. + //So for now we'll consider these as NUNO3, with a different parsing logic + Nuno3s.push_back(std::move(NUNO3(buffer, offset + checkpoint, header.chunkVersion, true, &entryIDToNunoID))); + if (entryIDToNunoID.count(Nuno3s.back().entryID) ==0) + entryIDToNunoID[Nuno3s.back().entryID] = i; + checkpoint += Nuno3s.back().entrySize; + } + //We parsed all the chunks, time to check which ones have subsets and create them (is there any other way than manually matching the coords ?) + //A previous attempt was made to use P1 and P2 but it breaks (see Queen from SoP for an example) + for (auto j= 0; j& nun3 = Nuno3s[j]; + if (nun3.parentSetID >= 0 && nunoIDToSubsetMap.count(nun3.parentSetID) == 0) //This entry has a parent entry and its map isn't there yet + { + //Create the map + std::map tempMap; + NUNO3& parentNunoEntry = Nuno3s[nun3.parentSetID]; + for (i = 0; i < parentNunoEntry.controlPoints.size(); i++) + tempMap[parentNunoEntry.controlPoints[i].v[0] + parentNunoEntry.controlPoints[i].v[1] + parentNunoEntry.controlPoints[i].v[2] + parentNunoEntry.controlPoints[i].v[0]] = i; + nunoIDToSubsetMap[nun3.parentSetID] = tempMap; + } + + if (nun3.parentSetID >= 0 && nunoIDToSubsetMap.count(nun3.parentSetID)) //This entry has a parent entry and its map has been created + { + auto& tempMap = nunoIDToSubsetMap[nun3.parentSetID]; + //Since we don't use the influences for the subsets to build drivers, we can safely override them to store the index info + for (i = 0; i < nun3.controlPoints.size(); i++) + { + const RichVec4& cp = nun3.controlPoints[i]; + if (tempMap.count(cp.v[0] + cp.v[1] + cp.v[2] + cp.v[0])) + nun3.influences[i].P1 = tempMap[cp.v[0] + cp.v[1] + cp.v[2] + cp.v[0]]; + else + assert(0); + } + } + } + + + offset += checkpoint; + break; + } default: offset += subHeader.chunkSize - 12; break; diff --git a/Source/Public/G1M/NUNS.h b/Source/Public/G1M/NUNS.h index eb99be5..27c2ce3 100644 --- a/Source/Public/G1M/NUNS.h +++ b/Source/Public/G1M/NUNS.h @@ -22,14 +22,14 @@ template struct NUNS1 { uint32_t parentID; - size_t entrySize; + uint32_t entrySize; std::vector controlPoints; std::vector influences; - NUNS1(BYTE* buffer, size_t startOffset, uint32_t version) + NUNS1(BYTE* buffer, uint32_t startOffset, uint32_t version) { - size_t offset = startOffset; + uint32_t offset = startOffset; parentID = *reinterpret_cast(buffer + offset); - size_t controlPointCount = *reinterpret_cast(buffer + offset + 4); + uint32_t controlPointCount = *reinterpret_cast(buffer + offset + 4); if (bBigEndian) { @@ -76,7 +76,7 @@ struct NUNS1 offset += 4; } offset += 4; - size_t blwoSize = *reinterpret_cast(buffer + offset); + uint32_t blwoSize = *reinterpret_cast(buffer + offset); offset += 16 + blwoSize; entrySize = offset - startOffset; } @@ -86,9 +86,9 @@ template struct NUNS { std::vector> Nuns1s; - NUNS(BYTE* buffer, size_t startOffset) + NUNS(BYTE* buffer, uint32_t startOffset) { - size_t offset = startOffset; + uint32_t offset = startOffset; //Reading the header, we want the version GResourceHeader header = reinterpret_cast*>(buffer + startOffset); offset += sizeof(GResourceHeader); @@ -103,7 +103,7 @@ struct NUNS { NunHeader subHeader = reinterpret_cast*>(buffer + offset); offset += 12; - size_t checkpoint; + uint32_t checkpoint; switch (subHeader.magic) { case NUNS1_MAGIC: diff --git a/Source/Public/G1M/NUNV.h b/Source/Public/G1M/NUNV.h index 7803cab..c32e4fa 100644 --- a/Source/Public/G1M/NUNV.h +++ b/Source/Public/G1M/NUNV.h @@ -9,15 +9,15 @@ template struct NUNV1 { uint32_t parentID; - size_t entrySize; + uint32_t entrySize; std::vector controlPoints; std::vector influences; - NUNV1(BYTE* buffer, size_t startOffset, uint32_t version) + NUNV1(BYTE* buffer, uint32_t startOffset, uint32_t version) { - size_t offset = startOffset; + uint32_t offset = startOffset; parentID = *reinterpret_cast(buffer + offset); - size_t controlPointCount = *reinterpret_cast(buffer + offset + 4); - size_t unknownSectionCount = *reinterpret_cast(buffer + offset + 8); + uint32_t controlPointCount = *reinterpret_cast(buffer + offset + 4); + uint32_t unknownSectionCount = *reinterpret_cast(buffer + offset + 8); uint32_t skip1 = *reinterpret_cast(buffer + offset + 12); if (bBigEndian) @@ -72,9 +72,9 @@ template struct NUNV { std::vector> Nunv1s; - NUNV(BYTE* buffer, size_t startOffset) + NUNV(BYTE* buffer, uint32_t startOffset) { - size_t offset = startOffset; + uint32_t offset = startOffset; //Reading the header, we want the version GResourceHeader header = reinterpret_cast*>(buffer + startOffset); offset += sizeof(GResourceHeader); @@ -89,7 +89,7 @@ struct NUNV { NunHeader subHeader = reinterpret_cast*>(buffer + offset); offset += 12; - size_t checkpoint; + uint32_t checkpoint; switch (subHeader.magic) { case NUNV1_MAGIC: diff --git a/Source/Public/G1M/SOFT.h b/Source/Public/G1M/SOFT.h index ee585d6..0027033 100644 --- a/Source/Public/G1M/SOFT.h +++ b/Source/Public/G1M/SOFT.h @@ -86,11 +86,11 @@ template struct SOFT1 { uint32_t parentID; - size_t entrySize; + uint32_t entrySize; std::vector> softNodes; - SOFT1(BYTE* buffer, size_t startOffset, uint32_t version) + SOFT1(BYTE* buffer, uint32_t startOffset, uint32_t version) { - size_t offset = startOffset; + uint32_t offset = startOffset; SoftNodeEntryHeader entryHeader = *reinterpret_cast*>(buffer + offset); offset += sizeof(SoftNodeEntryHeader) + sizeof(SoftNodeEntryUnk1); @@ -121,9 +121,9 @@ template struct SOFT { std::vector> Soft1s; - SOFT(BYTE* buffer, size_t startOffset) + SOFT(BYTE* buffer, uint32_t startOffset) { - size_t offset = startOffset; + uint32_t offset = startOffset; //Reading the header, we want the version GResourceHeader header = reinterpret_cast*>(buffer + startOffset); offset += sizeof(GResourceHeader); @@ -138,7 +138,7 @@ struct SOFT { NunHeader subHeader = reinterpret_cast*>(buffer + offset); //Not a typo, exact same struct offset += 12; - size_t checkpoint; + uint32_t checkpoint; switch (subHeader.magic) { case SOFT1_MAGIC: diff --git a/Source/Public/G1T.h b/Source/Public/G1T.h index 367e094..eb27a98 100644 --- a/Source/Public/G1T.h +++ b/Source/Public/G1T.h @@ -3,7 +3,7 @@ #ifndef G1T_H #define G1T_H -enum EG1TPlatform : uint32_t +enum class EG1TPlatform : uint32_t { PS2 = 0x0, PS3 = 0x1, @@ -17,7 +17,8 @@ enum EG1TPlatform : uint32_t NWiiU = 0x9, Windows = 0xA, PS4 = 0xB, - XOne = 0xC + XOne = 0xC, + NSwitch = 0x10, }; template @@ -134,8 +135,9 @@ struct G1T bool bSpecialCaseETC = false; bool b3DSAlpha = false; bool bETCAlpha = false; + bool bNeedsX360EndianSwap = false; std::string rawFormat =""; - int fourccFormat = -1; + int fourccFormat = -1; int32_t computedSize = -1; uint32_t mortonWidth = 0; @@ -166,6 +168,7 @@ struct G1T break; case 0x6: fourccFormat = FOURCC_DXT1; + bNeedsX360EndianSwap = true; break; case 0x7: fourccFormat = FOURCC_DXT3; @@ -310,13 +313,15 @@ struct G1T dataSize = bufferLen - offsetList[i] - texHeader.headerSize - header.tableOffset; } - //Swap endian for x360 textures if (header.platform == EG1TPlatform::X360) { - uint16_t* tmp = (uint16_t*)(buffer + offset); - for (auto i = 0; i < dataSize / 2; i++) - { - LITTLE_BIG_SWAP(tmp[i]); + if (bNeedsX360EndianSwap || mortonWidth > 0) + { //Swap endian for x360 textures + uint16_t* tmp = (uint16_t*)(buffer + offset); + for (auto i = 0; i < dataSize / 2; i++) + { + LITTLE_BIG_SWAP(tmp[i]); + } } } @@ -325,6 +330,15 @@ struct G1T { switch (header.platform) { + case EG1TPlatform::X360: + { + untiledTexData = (BYTE*)rapi->Noesis_UnpooledAlloc(dataSize); + if (bRaw) + rapi->Noesis_UntileImageRAW(untiledTexData, buffer + offset, dataSize, width, height, mortonWidth); + else + rapi->Noesis_UntileImageDXT(untiledTexData, buffer + offset, dataSize, width, height, mortonWidth * 2); + break; + } case EG1TPlatform::PS4: untiledTexData = (BYTE*)rapi->Noesis_UnpooledAlloc(dataSize); if (bRaw) @@ -335,6 +349,20 @@ struct G1T case EG1TPlatform::NWiiU: untiledTexData = (BYTE*)rapi->Noesis_UnpooledAlloc(dataSize); break; + case EG1TPlatform::NSwitch: + { + untiledTexData = (BYTE*)rapi->Noesis_UnpooledAlloc(dataSize*4); + int blockWidth = 4; + int blockHeight = (height <= 256) ? 3 : 4; + blockHeight = (height <= 128) ? 2 : blockHeight; + blockHeight = (height <= 64) ? 1 : blockHeight; + int widthInBlocks = (width + (blockWidth - 1)) / blockWidth; + int temp = (height + (blockHeight - 1)); + int heightInBlocks = (height + (blockHeight - 1)) / blockHeight; + int maxBlockHeight = rapi->Image_TileCalculateBlockLinearGOBBlockHeight(height, blockHeight); + rapi->Image_UntileBlockLinearGOBs(untiledTexData, dataSize, buffer + offset, dataSize, widthInBlocks, heightInBlocks, maxBlockHeight, mortonWidth*2); + break; + } default: untiledTexData = (BYTE*)rapi->Noesis_UnpooledAlloc(dataSize); if (bRaw) diff --git a/Source/Public/OBJD.h b/Source/Public/OBJD.h index 37ea1f3..867f86b 100644 --- a/Source/Public/OBJD.h +++ b/Source/Public/OBJD.h @@ -88,7 +88,7 @@ void DirtyAlign(BYTE* buffer, uint32_t& offset) template bool UnpackBundles(BYTE* buffer, int bufferLen, std::map>& bundleIDtoG1mOffsets, std::map>& bundleIDtoG1mSizes) { - size_t offset = 0; + uint32_t offset = 0; bool bSuccess = true; //Check entry count first, should be 2 in most cases diff --git a/Source/Public/Oid.h b/Source/Public/Oid.h index 8180de0..5959171 100644 --- a/Source/Public/Oid.h +++ b/Source/Public/Oid.h @@ -31,7 +31,7 @@ struct OID std::string s = std::string(reinterpret_cast(buffer), bufferLen); std::string delimiter = "\r\n"; - size_t pos = 0; + uint32_t pos = 0; std::string token; while ((pos = s.find(delimiter)) != std::string::npos) { token = s.substr(0, pos); @@ -74,7 +74,7 @@ struct OID if (bHasIDs) { std::string delimiter = ","; - size_t pos = 0; + uint32_t pos = 0; uint32_t id; for (std::string& name : nameList) {