From c6aac86e69e8e2f0966d8f564e21803d6a26f5e4 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Sun, 8 Mar 2026 18:24:51 -0500 Subject: [PATCH] feat(modloader): add managed block callbacks and ruby block examples --- ExampleMod/ExampleMod.cs | 185 +++++++ ExampleMod/assets/blocks/ruby_lamp.png | Bin 0 -> 2469 bytes ExampleMod/assets/blocks/ruby_lamp_on.png | Bin 0 -> 2426 bytes ExampleMod/assets/blocks/ruby_sand.png | Bin 0 -> 3803 bytes ExampleMod/assets/blocks/ruby_stone.png | Bin 0 -> 2237 bytes ExampleMod/assets/blocks/ruby_wood_planks.png | Bin 0 -> 5741 bytes ExampleMod/assets/lang/en-GB.lang | 10 + WeaveLoader.API/Block/BlockProperties.cs | 3 + WeaveLoader.API/Block/BlockRegistry.cs | 150 +++++- WeaveLoader.API/Block/CustomBlock.cs | 226 ++++++++ WeaveLoader.API/NativeInterop.cs | 67 ++- WeaveLoader.API/Registry.cs | 9 + WeaveLoader.Core/WeaveLoaderCore.cs | 40 ++ .../WeaveLoader.Launcher.csproj | 5 +- WeaveLoaderRuntime/CMakeLists.txt | 3 + WeaveLoaderRuntime/src/CrashHandler.cpp | 14 +- .../src/CustomBlockRegistry.cpp | 3 +- WeaveLoaderRuntime/src/CustomBlockRegistry.h | 3 +- WeaveLoaderRuntime/src/CustomSlabRegistry.cpp | 50 ++ WeaveLoaderRuntime/src/CustomSlabRegistry.h | 18 + WeaveLoaderRuntime/src/DotNetHost.cpp | 27 + WeaveLoaderRuntime/src/DotNetHost.h | 3 + WeaveLoaderRuntime/src/GameHooks.cpp | 485 +++++++++++++++++- WeaveLoaderRuntime/src/GameHooks.h | 63 +++ WeaveLoaderRuntime/src/GameObjectFactory.cpp | 191 ++++++- WeaveLoaderRuntime/src/GameObjectFactory.h | 8 +- WeaveLoaderRuntime/src/HookManager.cpp | 277 ++++++++++ .../src/ManagedBlockRegistry.cpp | 35 ++ WeaveLoaderRuntime/src/ManagedBlockRegistry.h | 15 + WeaveLoaderRuntime/src/NativeExports.cpp | 241 ++++++++- WeaveLoaderRuntime/src/NativeExports.h | 56 +- WeaveLoaderRuntime/src/PdbParser.cpp | 92 ++++ WeaveLoaderRuntime/src/PdbParser.h | 5 +- WeaveLoaderRuntime/src/SymbolResolver.cpp | 108 ++++ WeaveLoaderRuntime/src/SymbolResolver.h | 27 + WeaveLoaderRuntime/src/dllmain.cpp | 3 + 36 files changed, 2364 insertions(+), 58 deletions(-) create mode 100644 ExampleMod/assets/blocks/ruby_lamp.png create mode 100644 ExampleMod/assets/blocks/ruby_lamp_on.png create mode 100644 ExampleMod/assets/blocks/ruby_sand.png create mode 100644 ExampleMod/assets/blocks/ruby_stone.png create mode 100644 ExampleMod/assets/blocks/ruby_wood_planks.png create mode 100644 WeaveLoader.API/Block/CustomBlock.cs create mode 100644 WeaveLoaderRuntime/src/CustomSlabRegistry.cpp create mode 100644 WeaveLoaderRuntime/src/CustomSlabRegistry.h create mode 100644 WeaveLoaderRuntime/src/ManagedBlockRegistry.cpp create mode 100644 WeaveLoaderRuntime/src/ManagedBlockRegistry.h diff --git a/ExampleMod/ExampleMod.cs b/ExampleMod/ExampleMod.cs index 4f51280..31ed0dc 100644 --- a/ExampleMod/ExampleMod.cs +++ b/ExampleMod/ExampleMod.cs @@ -10,6 +10,13 @@ namespace ExampleMod; public class ExampleMod : IMod { public static RegisteredBlock? RubyOre; + public static RegisteredBlock? RubyStone; + public static RegisteredBlock? RubyWoodPlanks; + public static RegisteredBlock? RubySand; + public static RegisteredSlabBlock? RubyStoneSlab; + public static RegisteredSlabBlock? RubyWoodSlab; + public static RegisteredBlock? RubyLamp; + public static RegisteredBlock? RubyLampLit; public static RegisteredBlock? Orichalcum; public static RegisteredItem? Ruby; public static RegisteredItem? RubyPickaxeItem; @@ -91,6 +98,95 @@ public class ExampleMod : IMod { } + private sealed class RubyLampBlock : WeaveLoader.API.Block.Block + { + private readonly bool _isLit; + + public RubyLampBlock(bool isLit) + { + _isLit = isLit; + } + + public override void OnPlace(BlockUpdateContext context) + { + if (context.IsClientSide) + return; + + if (_isLit) + { + if (!context.HasNeighborSignal()) + context.ScheduleTick(4); + } + else if (context.HasNeighborSignal()) + { + context.SetBlock(new Identifier("examplemod:ruby_lamp_lit")); + } + } + + public override void OnNeighborChanged(BlockNeighborChangedContext context) + { + if (context.Block.IsClientSide) + return; + + if (_isLit) + { + if (!context.Block.HasNeighborSignal()) + context.Block.ScheduleTick(4); + } + else if (context.Block.HasNeighborSignal()) + { + context.Block.SetBlock(new Identifier("examplemod:ruby_lamp_lit")); + } + } + + public override void OnScheduledTick(BlockTickContext context) + { + if (!_isLit || context.Block.IsClientSide) + return; + + if (!context.Block.HasNeighborSignal()) + context.Block.SetBlock(new Identifier("examplemod:ruby_lamp")); + } + } + + private sealed class RubySandBlock : FallingBlock + { + private static readonly int FlowingLavaId = IdHelper.GetBlockNumericId("minecraft:flowing_lava"); + private static readonly int LavaId = IdHelper.GetBlockNumericId("minecraft:lava"); + + public override void OnPlace(BlockUpdateContext context) + { + TryHarden(context); + } + + public override void OnNeighborChanged(BlockNeighborChangedContext context) + { + TryHarden(context.Block); + } + + private static void TryHarden(BlockUpdateContext context) + { + if (context.IsClientSide || RubyStone == null) + return; + + if (HasLavaAtOrAdjacent(context)) + context.SetBlock(RubyStone.NumericId); + } + + private static bool HasLavaAtOrAdjacent(BlockUpdateContext context) + { + static bool IsLava(int blockId) => blockId == FlowingLavaId || blockId == LavaId; + + return IsLava(context.GetBlockId()) || + IsLava(context.GetBlockId(-1, 0, 0)) || + IsLava(context.GetBlockId(1, 0, 0)) || + IsLava(context.GetBlockId(0, -1, 0)) || + IsLava(context.GetBlockId(0, 1, 0)) || + IsLava(context.GetBlockId(0, 0, -1)) || + IsLava(context.GetBlockId(0, 0, 1)); + } + } + public void OnInitialize() { RubyOre = Registry.Block.Register("examplemod:ruby_ore", @@ -105,6 +201,95 @@ public class ExampleMod : IMod .RequiredTool(ToolType.Pickaxe) .InCreativeTab(CreativeTab.BuildingBlocks)); + RubyStone = Registry.Block.Register("examplemod:ruby_stone", + new BlockProperties() + .Material(MaterialType.Stone) + .Hardness(1.5f) + .Resistance(10f) + .Sound(SoundType.Stone) + .Icon("examplemod:ruby_stone") + .Name("Ruby Stone") + .RequiredHarvestLevel(1) + .RequiredTool(ToolType.Pickaxe) + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyWoodPlanks = Registry.Block.Register("examplemod:ruby_wood_planks", + new BlockProperties() + .Material(MaterialType.Wood) + .Hardness(2.0f) + .Resistance(5f) + .Sound(SoundType.Wood) + .Icon("examplemod:ruby_wood_planks") + .Name("Ruby Wood Planks") + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubySand = Registry.Block.Register("examplemod:ruby_sand", + new RubySandBlock(), + new BlockProperties() + .Material(MaterialType.Sand) + .Hardness(0.5f) + .Resistance(2.5f) + .Sound(SoundType.Sand) + .Icon("examplemod:ruby_sand") + .Name("Ruby Sand") + .RequiredTool(ToolType.Shovel) + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyStoneSlab = (RegisteredSlabBlock)Registry.Block.Register("examplemod:ruby_stone_slab", + new SlabBlock(), + new BlockProperties() + .Material(MaterialType.Stone) + .Hardness(1.5f) + .Resistance(10f) + .Sound(SoundType.Stone) + .Icon("examplemod:ruby_stone") + .Name("Ruby Stone Slab") + .RequiredHarvestLevel(1) + .RequiredTool(ToolType.Pickaxe) + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyWoodSlab = (RegisteredSlabBlock)Registry.Block.Register("examplemod:ruby_wood_slab", + new SlabBlock(), + new BlockProperties() + .Material(MaterialType.Wood) + .Hardness(2.0f) + .Resistance(5f) + .Sound(SoundType.Wood) + .Icon("examplemod:ruby_wood_planks") + .Name("Ruby Wood Slab") + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyLamp = Registry.Block.Register("examplemod:ruby_lamp", new RubyLampBlock(false), + new BlockProperties() + .Material(MaterialType.Glass) + .Hardness(0.3f) + .Resistance(1.5f) + .Sound(SoundType.Glass) + .Icon("examplemod:ruby_lamp") + .Name("Ruby Lamp") + .RequiredHarvestLevel(0) + .RequiredTool(ToolType.Pickaxe) + .AcceptsRedstonePower() + .InCreativeTab(CreativeTab.BuildingBlocks)); + + RubyLampLit = Registry.Block.Register("examplemod:ruby_lamp_lit", + new RubyLampBlock(true) + { + DropAsBlockId = new Identifier("examplemod:ruby_lamp"), + CloneAsBlockId = new Identifier("examplemod:ruby_lamp") + }, + new BlockProperties() + .Material(MaterialType.Glass) + .Hardness(0.3f) + .Resistance(1.5f) + .Sound(SoundType.Glass) + .Icon("examplemod:ruby_lamp_on") + .LightLevel(1.0f) + .Name("Ruby Lamp") + .RequiredHarvestLevel(0) + .RequiredTool(ToolType.Pickaxe) + .AcceptsRedstonePower()); + Orichalcum = Registry.Block.Register("examplemod:orichalcum_ore", new BlockProperties() .Material(MaterialType.Metal) diff --git a/ExampleMod/assets/blocks/ruby_lamp.png b/ExampleMod/assets/blocks/ruby_lamp.png new file mode 100644 index 0000000000000000000000000000000000000000..c53570878073e6f996a10bdc6d7cf3b6f8c9c727 GIT binary patch literal 2469 zcmb7Ge^^rY9zGV$VN%|#+0z)HnD%#=1~+n~EU8X_WTK*cJrbWED_7=cD}z;FSg0G{ z#3K@D5L`hwX>zm%o(Tr+d3nTV(hPxgy9=HTgGo{uU4ZEkI)}+&vcOO`I$y8K;Kd1} zXM%|n3})iEjt4{`Rj_yiFUi9i8N7WAmQE)&BsLnfx1?dY+XS$Iw$ zj%NHq9TJ)2PSE%C3MPf_Y0gsVue)nBkBwo#qqY)6%#ixn2tOAv$jv$gibD)oo?eMW zZAG-WpPdPk7oGsi!SwNjG!PgJaGGVP3CT$kq8dbNAV`NnZqO58@?3D1G)Gn`rXrFC z+T)y%SRjz;u?#eazzlIQk#upCfEy6Z3z=(s)ur-6{pH5Grt@Q#OmuWv~JntXJd# zTwWlD#|dJvI6M~1Ud8T@sn87bZ+Vl7rt=AZsgx%|4LGJZ+f5Fc$0NCpljE3!+VxE; zob*>H$?0HFr^i%zN*!!}E|XNTbSw_BmEFrSZF7kW3>C9x`x`*rxQ*#140+0;GNEm%J7aaPuS$o2AR z>cGYX#_5G#DVIl&M4uFi|6P&dNi~I(z0ErLW%$*&7WH0B>xdsOs!o(_0Dy&_>8Ss39F^~Z6G&oW8W zQkwfxiF;+2L^A3{XSa;T`4ewA0$e*v^6v zvhVjEOKYs;T$}E>y|X@_GoJ&xWX;P8`f+hJgBx1>!AN22u9$;6({M4O-VcABWFZuLM&PT|8%zR+PfV*Tdw zrj;(c0gel2$>LjQg14LZ+jO)8zSi+7)=<3dJoSsFMYXQ{ssEXpkN(Hf`b4fz(c$-Eed7BmlLBYyTj-}LRq?hE!3D}s z{O0;K9*4$T5*3o7&#OvAB?n5pD_ng;ubX_me++7aGnH9W-~Mf%2dr+&>c9Ew$auj> zwVn~_OcpLJ7({!- zaLkXsq;_>}beZwBv2xA)o(H}B)+?B@#`AsV@DE<8eG>&n?ww@6@xV*HIqscrghbjY zd5c$~w)ECq4-FFUimU!)(}kh_Gnb~R>ur_P_bwe}5gG+GtBn zF->1Aq6p`8QMyF@l2d^k>nM*imcHJLedfij1WXCF`2*s$;4!UB;^sM zwy02RQHrTY(X%Ub)ukwX2?`aJ(<2pBsHF-hZl%^@^iXO6EuBdaRBU_t$0V8Gz2EE3 z&6jMAUlHr!=IaIkfQLjJok+b$($|casMr7E8+!r3IUJYCNx3u*RT3JOLPc!ASVoPG z!T~@KVbm#GgVK^@@kCn$|5&CqP9TpY_BOI8+VNwjH!K5V>MyA%_N2hS)h{g;`J+32h zLJQJ46&r{gQV2m54o)P;jQCV`tzlw(N(h)nU^WYZpQP(D2$C>l5W2~Zo{E`-rv;L@ z`VZ>R$TT`7-}5UN6}sn@Yr`$zeGfpmjtg8bvx5hiTKvjLG6tKr*g+_M9Ft zJBN12YAsGl!m-!2hsdWeAr#7ipD2^^T!?yTsFZTIN%U zQRyZ})j|M-t8N22Fuo(yXx0-Q;Jk!iHohO?kaA{FmOCxWxbqQdRp-zXYHr9!jCkk3Yy9*4(rz?^!H!c)MPoVPueE_D&^fX zTZV^6w1*|Xm_`NruM8FLYp)&{5u|ziXLsov1DoRb)y@y!XkBK!c=6u0vKaLCm$MSc z9R89y2M-nWD%m%WUOlpU>{`<|bGP4dllyLIowfSB>q^)5BLlMXytkI_lKBVZ2VATD z%6W?|PPpclPkA}^8$ZR}IX_L~o$Ft;@n@glAg149xOI=DHfZ-)C0y5fd?;$`$^M=n zUc7hn?3W?lzs^>?$KDgGG67mo}#0=KG%&8wmm+Qq=#NWDVf^xB?u$g;z z%rmry8`)NOt?z43U#_Q?PzhCHlu~TOPdXJuM_{`|p ze%Am0{X5{gLZ|bC^ET)0^6IL!kQS@Om*tr@KIe49nH^Zmw?9Qh8!O&PGtIb@z&TYF yw#UVuL_V5*Z@(X}%Ixy<>p?3s|G8{&AtTPOe_Yymnf^}&NS3dN{@=2+&A$WGW`&;s literal 0 HcmV?d00001 diff --git a/ExampleMod/assets/blocks/ruby_sand.png b/ExampleMod/assets/blocks/ruby_sand.png new file mode 100644 index 0000000000000000000000000000000000000000..3d333e49e80a75be10b9cf55036f3afede082913 GIT binary patch literal 3803 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 V3u)JCI;ejg;-K)r!~RRx`~&iSJSPAE literal 0 HcmV?d00001 diff --git a/ExampleMod/assets/blocks/ruby_wood_planks.png b/ExampleMod/assets/blocks/ruby_wood_planks.png new file mode 100644 index 0000000000000000000000000000000000000000..e7fe9c0042c967383ff8979b594bf4cf65cb2ef8 GIT binary patch literal 5741 zcmb7Ic{r4P+rF*YBDA2?EM#YlEi%~#qioqCVk|REm>GeWZ>Oy=L+i7o^Z!OMj}{N2%CVt&Ja z3nbyN|E10z@;7%Heg9m+&z|^C&IShmySuylU&invY5UNK_=D8HjPS1lUS|G8jEo7! zi{S0)h|%`J;7QP*OziQZqD62cc$(6rfk8lI|1=xp{4j2oNSr$c??odW0a29y576rG z;BV4?Wi=o;(UQvjjQbPP)6z2bBw%rFG|bCHR}-YCt)(cdtf(jjm;Tkj9*-(|?)J_Y zeGJ~2k>5j_2WntsXL6yx#h^y}z``>F3eh41;>JMK+~fDkVFUQB4A0r z_MR9GXW9csKs2ye9E}Hmknx_-L2wXUT2WfzuOYrJ7+SjDqbZuf;VSZSDsl=^FvY#m zWd5<4pOX8p&HO_DU(Ngq`HyD)#`7P|sLJfU|NkONRpxJ0{&M)Yg+UwQmyNcFXd6xD z_e!H3elJA~p0)`+X)BLzt85+s=&s>tGX0x80YEgtJE2$Nh5fNMJ4!^FX!7+JE?)5c z!qS}9G0*KY;WibES01`I1UDw76nb7d|MBC{)kbomr$rR%l~wG^u+`N~e4*Z&|991# zp_MDyz4w_Z>gqRdmQl>3kuUPEXR+`MSw$c@i$oh|Cvy4lVEzR=08+`Whvgx`+ zxtHX}1qq8{)4k8ctE0Na{pBrCi4fh!0#_4+v@AHlMkk|gc(*ZlfcJ&g^k*NLw`xaQ zYuN=V?pnfPilHw=k6)i>l`b9F9?;_ZaiHFKAZy?28rGQ$=2A86N33737U-0r^hzSF zd0G8Vho!*ECe+`UR6E}aHare01KWA;58Qe!GGm<@0t8DqFR2|7y-WM?VkM%iya0fm zckiMDGP3vpfJ;{osbS`FwdEouR;bMbAp$9MPN}sqk#|Dfk&eGB@m^&22HkGtZr23V zG9JSaDr`M?v-9?}zAxtGvX+y@h4n5;v0*ZO^zDU5Pael?MMg}?>}=*Ff~1mjCdTY_ zPpY0fn-To&^T6b3Mx%V*SKq%=JAHL{)!!d2Rtvh)qIUhv?nkK@tIX{XtL0);(GJR_ z!_y|08~CgSms5(2?VX(4NJ^)erl<4Fu#XB}gEP5E`fHrc6~f_)j;ER}-kMq{uNTQ4 zl>_cb&A;HXbKgcfxfiVOP$qWP%AR=71y1~sZjZTzMixisk8jnav~WkT`1T$I_*|8! zJ8S(&*^tGBA}NFlIeaVJ0ca|byapT)QhpiGqVBn%{CtKBrNs!nzBJuZYkF>+=fmdH zvKv>DWq`}k^rMs?FZx`7eqcSzx)N+C3f*|g$zd6E*9!O0`LbwQv~?Z}i`JpVH%*Qi zJ|ImbpF>dFs?ORtUULC2vaXc#?D{SPqFdb~D2h|mp&sXBGla~52=k|pRG7B9mpPoP zi{QAIs-TN3U148S6_XQIq9?8!)3G5RV32r{y<{zswf*&8wjqGrb9G1O%X{JQV!tP;gfSm*N9ilO@ zlYPXtKIBNw+2YOFjyr47cELX7w)CLbxsfiw&vl^(Ox8VY;*c;pF3wZMqLD@qR{yAW zW~+BuG@Y9pxKVvhPXIVNyrjt~)a8OLM`Pb!2S$RkhNbvL`s8khuO5V`dc2YJ6kHs9 zN{Q~J8Y{}N;U;bCMR^JT^F0|^ZAAHFCIB+E|>vVBNI$f*j$2>@nI!fbh8J5Qi?B3wJ zQYQo-Nb?yiRhLr{yJV%l^(Gmq3`%On(LtA{>QF}TY&8-D2(T8fVeQ32(XY@qOv^0@LYHv zQR7-CdUF*<#xjhK>b>g205d(_pC04}Zt4^g1FDAvXUY62!8S5?ixOrzIX$WmCz)9s z@^wsz63OMawwzE`Z!HmNjF}8&Ec1Mn!j9rOa4`~jrtaB2GxkvZJImRa1 zyi}1D+c!clyT3A=YcO199xcqu5*GagG(Hgiee}$gAAJj&hMlEG4a>5(?qsP`N6Ztd zHcPcflZN8jCOPj9F2Gkd)OLTg^=MDs-82f>zE07KK1d!q(QpHKhWUY!X`yk|yhDSy z+lZf&&f?L-0r>m9=X>O<>hA>UyCHd>uFcj5+TOE7MCTOG>gg~szCQmE&i=hLI9)AM zyeSQRfCJJWzgp8szDV@{B7BPo2cr@z86Nc6EitLjG9Ii7E0|{hw4g=_V03M%^-*%4 zKHCO8V>>UZ)?K{H&JtS5B$iQ49(Le&9jMt(4cQKFbJKQOEVMS=%z4QgcXHh7>GjYf z@rKVN$Dt{hbdSput@=&Sg9tXjUkj@=dFH!mY;%xyN|I8eIL{5{L?h+gbCG0srUC56 zo1%ja9Ln>ymG9CF?IfQXCOymX+QB#NB1o>?N*l&!|JSoK5P^2$E zPc-uI#-c+3YzBW~sRkVjeqifeg>HYB36dhe{_gpy=%#fIaf8nKo<1g`!CObkEROe2CBw z835;?Aur>Zn0?Y@*IeH`eVaHN$TOs4eSQ1$<(CEG#e)Ul3CG+j|Hpcuw_GnjeM3`` zt)Eb)H#0|r=L;D>Ckjg(5h1UAU)2S=__*Va?jCioUVADDs2|c1*xDCp7uU8=RUCse z>aGaISlLRkr*ZxG5IC{1itHW`_E zQ_3)-ad=TK8DWZ_?1_!6w{SP?VpMRPVzXanB7cmi&wkK16LCK0i(mLrQ#LmBQr2wI zW!HDFC&e^M(DiIop$(g3s5=*j5)+q-9ZTXOg~8?(c${H8`dtP?bM$*rCY>IUW+!@= zK_PdPpgC7l7iU8KS7GbuS^P|jdwn_|d!BqoXQ@X-`m&4uv?R1=h^fz#!Z2FbT5?%! zx3U-DIdER$-Y;l5Ws$RAJB?-RrK5vAtJ!%VvCjy5lXy3; zj+r7{`@=4MfXjI7Kw(`c zp7&_Hg^hsmb8=c=KFJTS)74>F%5_+ai6hqTL2~IyW*ttdP2Ja=B$GV4`FyqQD`1}F zH-~jVdJMkhJy~bWpP=_6$Lo(iir$z?G74Y>?3P+Gl}uvs&jdu}7&hkYcIn<6YgnCc z%suWi$aFrX%8HYdK0C5v<6vJkq0c7)V7#4}l=)0hg=<#fXHW9Xx89bArFF{+gl~O8xrDUeP>wbbxUShj|CCX} zP=fb>3|F+%t=pj<9HwcGoK5tW71fP&mVruM9JSmLZ@U7$?4sRJe6t;0PE$);vKDFM z3((tXuVE(zQNkZUjCsg_?q}-qm33~n6xJte29p^To{W5sl{ic1a({?lfdcJof8+su zj*^>W;pK_^7z&6%VHXQ2<|8d&(UsD!znt`WC?w0e(N0+NQR>V?>Px+cx+uj~O+3 zdt>bpMd6sujr(yOW%tygHEcsR1jx0{t(~bSQnfhMVuARMh9_KY_fH6r!zcTi+S|=s zi?Acdvz_Lp6F-FxFW3guE-?cc?`rZw_0;0HiVHr^ zIbW`1XTCh1cln?hsy^ay6YpT9g2r)?sX__&H3scw-I=Zcq0*s;Oiz0}Z2WB2Vw65+UG*n4Jrq-!RTEbiD+;qV{ zonVA49RMjkk&qu{USy?vB7U<={fsZ$qTbo*<0vy0&gYDY-D92{3VZ=U?Vqv=D+XXO zpK3}^pXgrsvY#=ls;r@r<&|9mS!kq3B>Vnc!~0kPpHCKVa_;R@_?|5$!2g<0Ith|d zx5-2KbXX4RJR_;m_T>mN`@ZhoKr=|Lajz7P8%-jg=m!nPgs#Z9_o$hRf}V`oIep^B)hVh$ry!N{*Wh zd?}AP)zX1P(r--85Y#iOl?enqp1NT*z?FJv7mKwJ^Yr(?yBQd)n!(wznf~C~sB8Dn zmY*bRNyGM0?tayDV(y;B%r+d;;ngVcXCRj!LHj^el5M))ZG(vZ&8WnbVakUA`El8s z#rsG4GnBwU`AYM>;G~=^KZbOdqG>0SZ=)l6a=Iv@HJqoZKP51$NhSA&c>$dNl*kK} zLi6*_q_Xpq;_Y>twKQB2wWr?-`KT*Mj9HzX8@R?yX=%=KzT`D`uUAlYTool type required to harvest this block (e.g. Pickaxe for stone-like blocks). public BlockProperties RequiredTool(ToolType tool) { RequiredToolValue = tool; return this; } + /// Marks the block as one that can receive redstone power. Stored for future block callbacks. + public BlockProperties AcceptsRedstonePower(bool accepts = true) { AcceptsRedstonePowerValue = accepts; return this; } } diff --git a/WeaveLoader.API/Block/BlockRegistry.cs b/WeaveLoader.API/Block/BlockRegistry.cs index 8fd8434..c9a35b7 100644 --- a/WeaveLoader.API/Block/BlockRegistry.cs +++ b/WeaveLoader.API/Block/BlockRegistry.cs @@ -20,6 +20,19 @@ public class RegisteredBlock } } +public class RegisteredSlabBlock : RegisteredBlock +{ + public Identifier DoubleStringId { get; } + public int DoubleNumericId { get; } + + internal RegisteredSlabBlock(Identifier id, Identifier doubleId, int numericId, int doubleNumericId) + : base(id, numericId) + { + DoubleStringId = doubleId; + DoubleNumericId = doubleNumericId; + } +} + /// /// Block registration via the WeaveLoader registry. /// Accessed through . @@ -48,7 +61,8 @@ public static class BlockRegistry properties.LightBlockValue, properties.NameValue ?? "", properties.RequiredHarvestLevelValue, - (int)properties.RequiredToolValue); + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0); if (numericId < 0) throw new InvalidOperationException($"Failed to register block '{id}'. No free IDs or invalid parameters."); @@ -67,6 +81,140 @@ public static class BlockRegistry return new RegisteredBlock(id, numericId); } + public static RegisteredBlock Register(Identifier id, Block managedBlock, BlockProperties properties) + { + if (managedBlock is SlabBlock) + return RegisterSlab(id, properties); + + if (managedBlock is FallingBlock) + return RegisterFalling(id, managedBlock, properties); + + int numericId = NativeInterop.native_register_managed_block( + id.ToString(), + (int)properties.MaterialValue, + properties.HardnessValue, + properties.ResistanceValue, + (int)properties.SoundValue, + properties.IconValue, + properties.LightEmissionValue, + properties.LightBlockValue, + properties.NameValue ?? "", + properties.RequiredHarvestLevelValue, + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0); + + if (numericId < 0) + throw new InvalidOperationException($"Failed to register managed block '{id}'."); + + if (properties.CreativeTabValue != CreativeTab.None) + { + NativeInterop.native_add_to_creative(numericId, 1, 0, (int)properties.CreativeTabValue); + } + + ManagedBlockDispatcher.RegisterBlock(id, numericId, managedBlock); + + int dropNumericId = -1; + if (managedBlock.DropAsBlockId is Identifier dropId) + dropNumericId = IdHelper.GetBlockNumericId(dropId); + + int cloneNumericId = -1; + if (managedBlock.CloneAsBlockId is Identifier cloneId) + cloneNumericId = IdHelper.GetBlockNumericId(cloneId); + NativeInterop.native_configure_managed_block(numericId, dropNumericId, cloneNumericId); + + lock (s_lock) + { + s_idByNumeric[numericId] = id; + } + + return new RegisteredBlock(id, numericId); + } + + public static RegisteredBlock RegisterFalling(Identifier id, BlockProperties properties) + => RegisterFalling(id, null, properties); + + private static RegisteredBlock RegisterFalling(Identifier id, Block? managedBlock, BlockProperties properties) + { + int numericId = NativeInterop.native_register_falling_block( + id.ToString(), + (int)properties.MaterialValue, + properties.HardnessValue, + properties.ResistanceValue, + (int)properties.SoundValue, + properties.IconValue, + properties.LightEmissionValue, + properties.LightBlockValue, + properties.NameValue ?? "", + properties.RequiredHarvestLevelValue, + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0); + + if (numericId < 0) + throw new InvalidOperationException($"Failed to register falling block '{id}'."); + + if (properties.CreativeTabValue != CreativeTab.None) + { + NativeInterop.native_add_to_creative(numericId, 1, 0, (int)properties.CreativeTabValue); + } + + if (managedBlock != null) + { + ManagedBlockDispatcher.RegisterBlock(id, numericId, managedBlock); + + int dropNumericId = -1; + if (managedBlock.DropAsBlockId is Identifier dropId) + dropNumericId = IdHelper.GetBlockNumericId(dropId); + + int cloneNumericId = -1; + if (managedBlock.CloneAsBlockId is Identifier cloneId) + cloneNumericId = IdHelper.GetBlockNumericId(cloneId); + + NativeInterop.native_configure_managed_block(numericId, dropNumericId, cloneNumericId); + } + + lock (s_lock) + { + s_idByNumeric[numericId] = id; + } + return new RegisteredBlock(id, numericId); + } + + public static RegisteredSlabBlock RegisterSlab(Identifier id, BlockProperties properties) + { + Identifier doubleId = new($"{id}_double"); + int numericId = NativeInterop.native_register_slab_block( + id.ToString(), + (int)properties.MaterialValue, + properties.HardnessValue, + properties.ResistanceValue, + (int)properties.SoundValue, + properties.IconValue, + properties.LightEmissionValue, + properties.LightBlockValue, + properties.NameValue ?? "", + properties.RequiredHarvestLevelValue, + (int)properties.RequiredToolValue, + properties.AcceptsRedstonePowerValue ? 1 : 0, + out int doubleNumericId); + + if (numericId < 0) + throw new InvalidOperationException($"Failed to register slab block '{id}'."); + if (doubleNumericId < 0) + throw new InvalidOperationException($"Failed to resolve generated slab pair '{doubleId}'."); + + if (properties.CreativeTabValue != CreativeTab.None) + { + NativeInterop.native_add_to_creative(numericId, 1, 0, (int)properties.CreativeTabValue); + } + + lock (s_lock) + { + s_idByNumeric[numericId] = id; + s_idByNumeric[doubleNumericId] = doubleId; + } + + return new RegisteredSlabBlock(id, doubleId, numericId, doubleNumericId); + } internal static bool TryGetIdentifier(int numericId, out Identifier id) { lock (s_lock) diff --git a/WeaveLoader.API/Block/CustomBlock.cs b/WeaveLoader.API/Block/CustomBlock.cs new file mode 100644 index 0000000..2c49757 --- /dev/null +++ b/WeaveLoader.API/Block/CustomBlock.cs @@ -0,0 +1,226 @@ +using System.Runtime.InteropServices; + +namespace WeaveLoader.API.Block; + +public abstract class Block +{ + public Identifier? Id { get; internal set; } + public int NumericId { get; internal set; } = -1; + + public Identifier? DropAsBlockId { get; init; } + public Identifier? CloneAsBlockId { get; init; } + + public virtual void OnPlace(BlockUpdateContext context) + { + } + + public virtual void OnNeighborChanged(BlockNeighborChangedContext context) + { + } + + public virtual void OnScheduledTick(BlockTickContext context) + { + } +} + +public class FallingBlock : Block +{ +} + +public class SlabBlock : Block +{ +} + +public readonly struct BlockUpdateContext +{ + public int BlockId { get; } + public bool IsClientSide { get; } + public nint NativeLevelPtr { get; } + public int X { get; } + public int Y { get; } + public int Z { get; } + + internal BlockUpdateContext(int blockId, bool isClientSide, nint nativeLevelPtr, int x, int y, int z) + { + BlockId = blockId; + IsClientSide = isClientSide; + NativeLevelPtr = nativeLevelPtr; + X = x; + Y = y; + Z = z; + } + + public bool HasNeighborSignal() + { + if (NativeLevelPtr == 0) + return false; + + return NativeInterop.native_level_has_neighbor_signal(NativeLevelPtr, X, Y, Z) != 0; + } + + public bool SetBlock(Identifier id, int data = 0, int flags = 2) + { + int numericId = IdHelper.GetBlockNumericId(id); + if (numericId < 0) + return false; + + return SetBlock(numericId, data, flags); + } + + public bool SetBlock(int numericBlockId, int data = 0, int flags = 2) + { + if (NativeLevelPtr == 0 || numericBlockId < 0) + return false; + + return NativeInterop.native_level_set_tile(NativeLevelPtr, X, Y, Z, numericBlockId, data, flags) != 0; + } + + public bool ScheduleTick(int delay) + { + if (NativeLevelPtr == 0 || delay < 0) + return false; + + return NativeInterop.native_level_schedule_tick(NativeLevelPtr, X, Y, Z, BlockId, delay) != 0; + } + + public int GetBlockId(int offsetX = 0, int offsetY = 0, int offsetZ = 0) + { + if (NativeLevelPtr == 0) + return -1; + + return NativeInterop.native_level_get_tile(NativeLevelPtr, X + offsetX, Y + offsetY, Z + offsetZ); + } +} + +public readonly struct BlockNeighborChangedContext +{ + public BlockUpdateContext Block { get; } + public int NeighborBlockId { get; } + + internal BlockNeighborChangedContext(BlockUpdateContext block, int neighborBlockId) + { + Block = block; + NeighborBlockId = neighborBlockId; + } +} + +public readonly struct BlockTickContext +{ + public BlockUpdateContext Block { get; } + + internal BlockTickContext(BlockUpdateContext block) + { + Block = block; + } +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockUpdateNativeArgs +{ + public int BlockId; + public int IsClientSide; + public nint LevelPtr; + public int X; + public int Y; + public int Z; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct BlockNeighborChangedNativeArgs +{ + public BlockUpdateNativeArgs Block; + public int NeighborBlockId; +} + +internal static class ManagedBlockDispatcher +{ + private static readonly object s_lock = new(); + private static readonly Dictionary s_blocks = new(); + + internal static void RegisterBlock(Identifier id, int numericId, Block block) + { + block.Id = id; + block.NumericId = numericId; + + lock (s_lock) + { + s_blocks[numericId] = block; + } + } + + internal static int HandlePlace(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockUpdateNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.BlockId, out block); + } + + if (block == null) + return 0; + + block.OnPlace(new BlockUpdateContext( + nativeArgs.BlockId, + nativeArgs.IsClientSide != 0, + nativeArgs.LevelPtr, + nativeArgs.X, + nativeArgs.Y, + nativeArgs.Z)); + return 1; + } + + internal static int HandleNeighborChanged(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockNeighborChangedNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block); + } + + if (block == null) + return 0; + + var update = new BlockUpdateContext( + nativeArgs.Block.BlockId, + nativeArgs.Block.IsClientSide != 0, + nativeArgs.Block.LevelPtr, + nativeArgs.Block.X, + nativeArgs.Block.Y, + nativeArgs.Block.Z); + block.OnNeighborChanged(new BlockNeighborChangedContext(update, nativeArgs.NeighborBlockId)); + return 1; + } + + internal static int HandleScheduledTick(IntPtr args, int sizeBytes) + { + if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf()) + return 0; + + BlockUpdateNativeArgs nativeArgs = Marshal.PtrToStructure(args); + Block? block; + lock (s_lock) + { + s_blocks.TryGetValue(nativeArgs.BlockId, out block); + } + + if (block == null) + return 0; + + block.OnScheduledTick(new BlockTickContext(new BlockUpdateContext( + nativeArgs.BlockId, + nativeArgs.IsClientSide != 0, + nativeArgs.LevelPtr, + nativeArgs.X, + nativeArgs.Y, + nativeArgs.Z))); + return 1; + } +} diff --git a/WeaveLoader.API/NativeInterop.cs b/WeaveLoader.API/NativeInterop.cs index d09cd0d..4fcfd07 100644 --- a/WeaveLoader.API/NativeInterop.cs +++ b/WeaveLoader.API/NativeInterop.cs @@ -22,7 +22,54 @@ internal static class NativeInterop int lightBlock, string displayName, int requiredHarvestLevel, - int requiredTool); + int requiredTool, + int acceptsRedstonePower); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern int native_register_managed_block( + string namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + string iconName, + float lightEmission, + int lightBlock, + string displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern int native_register_falling_block( + string namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + string iconName, + float lightEmission, + int lightBlock, + string displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern int native_register_slab_block( + string namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + string iconName, + float lightEmission, + int lightBlock, + string displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower, + out int doubleNumericBlockId); [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int native_register_item( @@ -78,6 +125,12 @@ internal static class NativeInterop int harvestLevel, float destroySpeed); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern void native_configure_managed_block( + int numericBlockId, + int dropNumericBlockId, + int cloneNumericBlockId); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int native_configure_custom_tool_item( int numericItemId, @@ -145,6 +198,18 @@ internal static class NativeInterop double spawnForward, double spawnUp); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_has_neighbor_signal(nint levelPtr, int x, int y, int z); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_set_tile(nint levelPtr, int x, int y, int z, int blockId, int data, int flags); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_schedule_tick(nint levelPtr, int x, int y, int z, int blockId, int delay); + + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int native_level_get_tile(nint levelPtr, int x, int y, int z); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern void native_subscribe_event(string eventName, IntPtr managedFnPtr); diff --git a/WeaveLoader.API/Registry.cs b/WeaveLoader.API/Registry.cs index a2c0807..407047a 100644 --- a/WeaveLoader.API/Registry.cs +++ b/WeaveLoader.API/Registry.cs @@ -17,6 +17,15 @@ public static class Registry { public static RegisteredBlock Register(Identifier id, BlockProperties properties) => BlockRegistry.Register(id, properties); + + public static RegisteredBlock Register(Identifier id, WeaveLoader.API.Block.Block block, BlockProperties properties) + => BlockRegistry.Register(id, block, properties); + + public static RegisteredBlock RegisterFalling(Identifier id, BlockProperties properties) + => BlockRegistry.Register(id, new FallingBlock(), properties); + + public static RegisteredSlabBlock RegisterSlab(Identifier id, BlockProperties properties) + => (RegisteredSlabBlock)BlockRegistry.Register(id, new SlabBlock(), properties); } /// Item registration. Call Register() with a namespaced ID and ItemProperties. diff --git a/WeaveLoader.Core/WeaveLoaderCore.cs b/WeaveLoader.Core/WeaveLoaderCore.cs index be06be7..db23e6d 100644 --- a/WeaveLoader.Core/WeaveLoaderCore.cs +++ b/WeaveLoader.Core/WeaveLoaderCore.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using WeaveLoader.API; using WeaveLoader.API.Events; +using WeaveLoader.API.Block; using WeaveLoader.API.Item; namespace WeaveLoader.Core; @@ -121,6 +122,45 @@ public static class WeaveLoaderCore } } + public static int OnBlockPlace(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandlePlace(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockPlace EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockNeighborChanged(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleNeighborChanged(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockNeighborChanged EXCEPTION: {ex}"); + return 0; + } + } + + public static int OnBlockTick(IntPtr args, int sizeBytes) + { + try + { + return ManagedBlockDispatcher.HandleScheduledTick(args, sizeBytes); + } + catch (Exception ex) + { + Logger.Error($"OnBlockTick EXCEPTION: {ex}"); + return 0; + } + } + [StructLayout(LayoutKind.Sequential)] private struct WorldLoadedNativeArgs { diff --git a/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj b/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj index ba7098b..966a738 100644 --- a/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj +++ b/WeaveLoader.Launcher/WeaveLoader.Launcher.csproj @@ -12,15 +12,16 @@ ..\build false false + $(MSBuildThisFileDirectory)..\WeaveLoaderRuntime\build\$(Configuration) - - diff --git a/WeaveLoaderRuntime/CMakeLists.txt b/WeaveLoaderRuntime/CMakeLists.txt index d7a073c..d55a874 100644 --- a/WeaveLoaderRuntime/CMakeLists.txt +++ b/WeaveLoaderRuntime/CMakeLists.txt @@ -90,6 +90,8 @@ add_library(WeaveLoaderRuntime SHARED src/CustomPickaxeRegistry.cpp src/CustomToolMaterialRegistry.cpp src/CustomBlockRegistry.cpp + src/CustomSlabRegistry.cpp + src/ManagedBlockRegistry.cpp src/CreativeInventory.cpp src/FurnaceRecipeRegistry.cpp src/MainMenuOverlay.cpp @@ -120,6 +122,7 @@ endif() target_compile_definitions(WeaveLoaderRuntime PRIVATE WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS + $<$:WEAVELOADER_DEBUG_BUILD> ) if(MSVC) diff --git a/WeaveLoaderRuntime/src/CrashHandler.cpp b/WeaveLoaderRuntime/src/CrashHandler.cpp index cbf6fb2..a97731c 100644 --- a/WeaveLoaderRuntime/src/CrashHandler.cpp +++ b/WeaveLoaderRuntime/src/CrashHandler.cpp @@ -372,14 +372,16 @@ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep) return EXCEPTION_CONTINUE_SEARCH; // TODO: Remove this suppression once the animated texture pack fault is fixed - // at the source. For now, a vectored handler sees first-chance exceptions - // before local __try/__except blocks run, and logging those handled faults - // as crashes causes severe log spam and stalls under Windows. - // - // Keep vectored reporting only for fail-fast/noncontinuable cases that will - // not normally make it to the top-level unhandled filter. + // at the source. For release builds, keep vectored reporting only for + // fail-fast/noncontinuable cases so handled first-chance faults do not spam + // logs and stall the process. In debug builds, log all fatal exceptions to + // make crash investigation easier. +#ifdef _DEBUG + WriteCrashReport("VectoredExceptionHandler", ep->ExceptionRecord, ep->ContextRecord); +#else if (ShouldWriteVectoredReport(ep->ExceptionRecord)) WriteCrashReport("VectoredExceptionHandler", ep->ExceptionRecord, ep->ContextRecord); +#endif return EXCEPTION_CONTINUE_SEARCH; } diff --git a/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp b/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp index 0e668af..fbc0cd4 100644 --- a/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp +++ b/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp @@ -8,11 +8,12 @@ namespace namespace CustomBlockRegistry { - void Register(int blockId, int requiredHarvestLevel, int requiredTool) + void Register(int blockId, int requiredHarvestLevel, int requiredTool, bool acceptsRedstonePower) { Definition def; def.requiredHarvestLevel = requiredHarvestLevel; def.requiredTool = static_cast(requiredTool); + def.acceptsRedstonePower = acceptsRedstonePower; g_definitions[blockId] = def; } diff --git a/WeaveLoaderRuntime/src/CustomBlockRegistry.h b/WeaveLoaderRuntime/src/CustomBlockRegistry.h index de35d0a..fd3941d 100644 --- a/WeaveLoaderRuntime/src/CustomBlockRegistry.h +++ b/WeaveLoaderRuntime/src/CustomBlockRegistry.h @@ -14,8 +14,9 @@ namespace CustomBlockRegistry { int requiredHarvestLevel = -1; ToolType requiredTool = ToolType::None; + bool acceptsRedstonePower = false; }; - void Register(int blockId, int requiredHarvestLevel, int requiredTool); + void Register(int blockId, int requiredHarvestLevel, int requiredTool, bool acceptsRedstonePower); const Definition* Find(int blockId); } diff --git a/WeaveLoaderRuntime/src/CustomSlabRegistry.cpp b/WeaveLoaderRuntime/src/CustomSlabRegistry.cpp new file mode 100644 index 0000000..ca9148a --- /dev/null +++ b/WeaveLoaderRuntime/src/CustomSlabRegistry.cpp @@ -0,0 +1,50 @@ +#include "CustomSlabRegistry.h" + +#include +#include + +namespace +{ + std::unordered_map g_definitions; +} + +namespace CustomSlabRegistry +{ + void Register(int halfBlockId, int fullBlockId, int descriptionId, bool woodFamily, const wchar_t* iconName) + { + Definition def; + def.halfBlockId = halfBlockId; + def.fullBlockId = fullBlockId; + def.descriptionId = descriptionId; + def.woodFamily = woodFamily; + if (iconName) + def.iconName = iconName; + g_definitions[halfBlockId] = def; + g_definitions[fullBlockId] = def; + } + + const Definition* Find(int blockId) + { + const auto it = g_definitions.find(blockId); + if (it == g_definitions.end()) + return nullptr; + return &it->second; + } + + void ForEachUnique(void (*fn)(const Definition&, void*), void* userData) + { + if (!fn) + return; + + std::unordered_set seenHalfIds; + for (const auto& entry : g_definitions) + { + const Definition& def = entry.second; + if (def.halfBlockId < 0) + continue; + if (!seenHalfIds.insert(def.halfBlockId).second) + continue; + fn(def, userData); + } + } +} diff --git a/WeaveLoaderRuntime/src/CustomSlabRegistry.h b/WeaveLoaderRuntime/src/CustomSlabRegistry.h new file mode 100644 index 0000000..6da4583 --- /dev/null +++ b/WeaveLoaderRuntime/src/CustomSlabRegistry.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace CustomSlabRegistry +{ + struct Definition + { + int halfBlockId = -1; + int fullBlockId = -1; + int descriptionId = -1; + bool woodFamily = false; + std::wstring iconName; + }; + + void Register(int halfBlockId, int fullBlockId, int descriptionId, bool woodFamily, const wchar_t* iconName); + const Definition* Find(int blockId); + void ForEachUnique(void (*fn)(const Definition&, void*), void* userData); +} diff --git a/WeaveLoaderRuntime/src/DotNetHost.cpp b/WeaveLoaderRuntime/src/DotNetHost.cpp index dc3b765..b87ff5c 100644 --- a/WeaveLoaderRuntime/src/DotNetHost.cpp +++ b/WeaveLoaderRuntime/src/DotNetHost.cpp @@ -24,6 +24,9 @@ static managed_entry_fn fn_Tick = nullptr; static managed_entry_fn fn_Shutdown = nullptr; static managed_entry_fn fn_ItemMineBlock = nullptr; static managed_entry_fn fn_ItemUse = nullptr; +static managed_entry_fn fn_BlockOnPlace = nullptr; +static managed_entry_fn fn_BlockNeighborChanged = nullptr; +static managed_entry_fn fn_BlockTick = nullptr; static managed_entry_fn fn_EntitySummoned = nullptr; static bool LoadHostfxr() @@ -182,6 +185,9 @@ bool DotNetHost::Initialize() ok &= resolve(L"Shutdown", &fn_Shutdown); ok &= resolve(L"OnItemMineBlock", &fn_ItemMineBlock); ok &= resolve(L"OnItemUse", &fn_ItemUse); + ok &= resolve(L"OnBlockPlace", &fn_BlockOnPlace); + ok &= resolve(L"OnBlockNeighborChanged", &fn_BlockNeighborChanged); + ok &= resolve(L"OnBlockTick", &fn_BlockTick); ok &= resolve(L"OnEntitySummoned", &fn_EntitySummoned); if (!ok) @@ -247,6 +253,27 @@ int DotNetHost::CallItemUse(const void* args, int sizeBytes) return fn_ItemUse(const_cast(args), sizeBytes); } +int DotNetHost::CallBlockOnPlace(const void* args, int sizeBytes) +{ + if (!fn_BlockOnPlace || !args || sizeBytes <= 0) + return 0; + return fn_BlockOnPlace(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockNeighborChanged(const void* args, int sizeBytes) +{ + if (!fn_BlockNeighborChanged || !args || sizeBytes <= 0) + return 0; + return fn_BlockNeighborChanged(const_cast(args), sizeBytes); +} + +int DotNetHost::CallBlockTick(const void* args, int sizeBytes) +{ + if (!fn_BlockTick || !args || sizeBytes <= 0) + return 0; + return fn_BlockTick(const_cast(args), sizeBytes); +} + void DotNetHost::CallEntitySummoned(int entityNumericId, float x, float y, float z) { if (!fn_EntitySummoned) diff --git a/WeaveLoaderRuntime/src/DotNetHost.h b/WeaveLoaderRuntime/src/DotNetHost.h index e756de5..7afca6c 100644 --- a/WeaveLoaderRuntime/src/DotNetHost.h +++ b/WeaveLoaderRuntime/src/DotNetHost.h @@ -17,5 +17,8 @@ namespace DotNetHost void CallShutdown(); int CallItemMineBlock(const void* args, int sizeBytes); int CallItemUse(const void* args, int sizeBytes); + int CallBlockOnPlace(const void* args, int sizeBytes); + int CallBlockNeighborChanged(const void* args, int sizeBytes); + int CallBlockTick(const void* args, int sizeBytes); void CallEntitySummoned(int entityNumericId, float x, float y, float z); } diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 268dc8b..73f251d 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -7,6 +7,8 @@ #include "CustomPickaxeRegistry.h" #include "CustomToolMaterialRegistry.h" #include "CustomBlockRegistry.h" +#include "ManagedBlockRegistry.h" +#include "CustomSlabRegistry.h" #include "LogUtil.h" #include #include @@ -47,6 +49,28 @@ namespace GameHooks PickaxeCanDestroySpecial_fn Original_PickaxeItemCanDestroySpecial = nullptr; PickaxeGetDestroySpeed_fn Original_ShovelItemGetDestroySpeed = nullptr; PickaxeCanDestroySpecial_fn Original_ShovelItemCanDestroySpecial = nullptr; + TileOnPlace_fn Original_TileOnPlace = nullptr; + TileNeighborChanged_fn Original_TileNeighborChanged = nullptr; + TileTick_fn Original_TileTick = nullptr; + LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData = nullptr; + LevelSetDataDispatch_fn Original_LevelSetData = nullptr; + LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt = nullptr; + ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks = nullptr; + TileGetResource_fn Original_TileGetResource = nullptr; + TileCloneTileId_fn Original_TileCloneTileId = nullptr; + TileGetTextureFaceData_fn Original_StoneSlabGetTexture = nullptr; + TileGetTextureFaceData_fn Original_WoodSlabGetTexture = nullptr; + TileGetResource_fn Original_StoneSlabGetResource = nullptr; + TileGetResource_fn Original_WoodSlabGetResource = nullptr; + TileGetDescriptionId_fn Original_StoneSlabGetDescriptionId = nullptr; + TileGetDescriptionId_fn Original_WoodSlabGetDescriptionId = nullptr; + TileGetAuxName_fn Original_StoneSlabGetAuxName = nullptr; + TileGetAuxName_fn Original_WoodSlabGetAuxName = nullptr; + TileRegisterIcons_fn Original_StoneSlabRegisterIcons = nullptr; + TileRegisterIcons_fn Original_WoodSlabRegisterIcons = nullptr; + StoneSlabItemGetIcon_fn Original_StoneSlabItemGetIcon = nullptr; + StoneSlabItemGetDescriptionId_fn Original_StoneSlabItemGetDescriptionId = nullptr; + TileCloneTileId_fn Original_HalfSlabCloneTileId = nullptr; PlayerCanDestroy_fn Original_PlayerCanDestroy = nullptr; GameModeUseItem_fn Original_ServerPlayerGameModeUseItem = nullptr; GameModeUseItem_fn Original_MultiPlayerGameModeUseItem = nullptr; @@ -76,6 +100,7 @@ namespace GameHooks static constexpr ptrdiff_t kLevelIsClientSideOffset = 0x268; static constexpr ptrdiff_t kItemIdOffset = 0x20; static constexpr ptrdiff_t kTileIdOffset = 0x28; + static constexpr ptrdiff_t kTileIconOffset = 0x78; static constexpr ptrdiff_t kEntityXOffset = 0x78; static constexpr ptrdiff_t kEntityYOffset = 0x80; static constexpr ptrdiff_t kEntityZOffset = 0x88; @@ -117,7 +142,24 @@ namespace GameHooks static std::vector s_spawnedEntities; static int s_outOfWorldGuardLogCount = 0; static int s_pendingServerUseItemId = -1; + static LevelGetTile_fn s_levelGetTile = nullptr; + + struct ManagedScheduledTick + { + void* levelPtr; + int x; + int y; + int z; + int blockId; + int remainingTicks; + }; + + static std::vector s_managedScheduledTicks; static ULONGLONG s_pendingServerUseExpiryMs = 0; + static TileGetTextureFaceData_fn s_tileGetTextureFaceData = nullptr; + static bool s_preInitCalled = false; + static bool s_initCalled = false; + static bool s_postInitCalled = false; static void EnsurePageResourcesInitialized() { @@ -196,6 +238,53 @@ namespace GameHooks return (p + bytes) <= end; } + static void* TryReadTileIcon(void* tilePtr) + { + if (!tilePtr) + return nullptr; + + const void* iconSlot = static_cast(tilePtr) + kTileIconOffset; + if (!IsReadableRange(iconSlot, sizeof(void*))) + return nullptr; + + void* iconPtr = *reinterpret_cast(iconSlot); + if (!IsCanonicalUserPtr(iconPtr)) + return nullptr; + + return iconPtr; + } + + static void PatchSingleSlabIcon(const CustomSlabRegistry::Definition& def, void*) + { + if (def.iconName.empty() || !s_tileTilesArray || !IsReadableRange(s_tileTilesArray, sizeof(void*))) + return; + + void* modIcon = ModAtlas::LookupModIcon(def.iconName); + if (!modIcon) + return; + + const void* arrayPtr = *reinterpret_cast(s_tileTilesArray); + if (!arrayPtr || !IsReadableRange(arrayPtr, sizeof(void*) * 4096)) + return; + + auto* tiles = reinterpret_cast(const_cast(arrayPtr)); + const int ids[] = { def.halfBlockId, def.fullBlockId }; + for (int blockId : ids) + { + if (blockId < 0 || blockId >= 4096) + continue; + void* tilePtr = const_cast(tiles[blockId]); + if (!tilePtr || !IsReadableRange(static_cast(tilePtr) + kTileIconOffset, sizeof(void*))) + continue; + *reinterpret_cast(static_cast(tilePtr) + kTileIconOffset) = modIcon; + } + } + + static void PatchCustomSlabIcons() + { + CustomSlabRegistry::ForEachUnique(&PatchSingleSlabIcon, nullptr); + } + static std::wstring BuildVirtualAtlasPath(int atlasType, int page) { std::wstring base = L"/modloader/"; @@ -423,6 +512,39 @@ namespace GameHooks s_entitySetPos = reinterpret_cast(entitySetPos); } + void SetBlockHelperSymbols(void* tileGetTextureFaceData) + { + s_tileGetTextureFaceData = reinterpret_cast(tileGetTextureFaceData); + } + + void SetManagedBlockDispatchSymbols(void* levelGetTile) + { + s_levelGetTile = reinterpret_cast(levelGetTile); + } + + void EnqueueManagedBlockTick(void* levelPtr, int x, int y, int z, int blockId, int delay) + { + if (!levelPtr || blockId < 0 || !ManagedBlockRegistry::IsManaged(blockId)) + return; + + const int normalizedDelay = delay > 0 ? delay : 1; + for (ManagedScheduledTick& tick : s_managedScheduledTicks) + { + if (tick.levelPtr == levelPtr && + tick.x == x && + tick.y == y && + tick.z == z && + tick.blockId == blockId) + { + if (normalizedDelay < tick.remainingTicks) + tick.remainingTicks = normalizedDelay; + return; + } + } + + s_managedScheduledTicks.push_back({ levelPtr, x, y, z, blockId, normalizedDelay }); + } + static bool IsInventoryObjectPtr(void* objectPtr) { if (!objectPtr || !s_inventoryVtable || !IsReadableRange(objectPtr, sizeof(void*))) @@ -851,6 +973,344 @@ namespace GameHooks return nullptr; } + static int TryReadTileId(void* tilePtr) + { + if (!tilePtr || !IsReadableRange(static_cast(tilePtr) + kTileIdOffset, sizeof(int))) + return -1; + return *reinterpret_cast(static_cast(tilePtr) + kTileIdOffset); + } + + static void DispatchManagedBlockUpdate(void* tilePtr, void* level, int x, int y, int z, int eventKind, int neighborBlockId) + { + const int blockId = TryReadTileId(tilePtr); + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + struct BlockUpdateNativeArgs + { + int blockId; + int isClientSide; + void* levelPtr; + int x; + int y; + int z; + }; + struct BlockNeighborChangedNativeArgs + { + BlockUpdateNativeArgs block; + int neighborBlockId; + }; + + if (eventKind == 0) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockOnPlace(&args, sizeof(args)); + } + else if (eventKind == 1) + { + BlockNeighborChangedNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.neighborBlockId = neighborBlockId; + DotNetHost::CallBlockNeighborChanged(&args, sizeof(args)); + } + else if (eventKind == 2) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockTick(&args, sizeof(args)); + } + } + + static void DispatchManagedBlockById(int blockId, void* level, int x, int y, int z, int eventKind, int neighborBlockId) + { + if (!ManagedBlockRegistry::IsManaged(blockId)) + return; + + int isClientSide = 0; + if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool))) + isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0; + + struct BlockUpdateNativeArgs + { + int blockId; + int isClientSide; + void* levelPtr; + int x; + int y; + int z; + }; + struct BlockNeighborChangedNativeArgs + { + BlockUpdateNativeArgs block; + int neighborBlockId; + }; + + if (eventKind == 0) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockOnPlace(&args, sizeof(args)); + } + else if (eventKind == 1) + { + BlockNeighborChangedNativeArgs args{}; + args.block = { blockId, isClientSide, level, x, y, z }; + args.neighborBlockId = neighborBlockId; + DotNetHost::CallBlockNeighborChanged(&args, sizeof(args)); + } + else if (eventKind == 2) + { + BlockUpdateNativeArgs args{ blockId, isClientSide, level, x, y, z }; + DotNetHost::CallBlockTick(&args, sizeof(args)); + } + } + + void __fastcall Hooked_TileOnPlace(void* thisPtr, void* level, int x, int y, int z) + { + if (Original_TileOnPlace) + Original_TileOnPlace(thisPtr, level, x, y, z); + DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 0, 0); + } + + void __fastcall Hooked_TileNeighborChanged(void* thisPtr, void* level, int x, int y, int z, int type) + { + if (Original_TileNeighborChanged) + Original_TileNeighborChanged(thisPtr, level, x, y, z, type); + DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 1, type); + } + + void __fastcall Hooked_TileTick(void* thisPtr, void* level, int x, int y, int z, void* random) + { + if (Original_TileTick) + Original_TileTick(thisPtr, level, x, y, z, random); + DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 2, 0); + } + + bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags) + { + const bool result = Original_LevelSetTileAndData + ? Original_LevelSetTileAndData(thisPtr, x, y, z, tile, data, updateFlags) + : false; + + if (result && tile > 0) + DispatchManagedBlockById(tile, thisPtr, x, y, z, 0, 0); + + return result; + } + + bool __fastcall Hooked_LevelSetData(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate) + { + const bool result = Original_LevelSetData + ? Original_LevelSetData(thisPtr, x, y, z, data, updateFlags, forceUpdate) + : false; + + return result; + } + + void __fastcall Hooked_LevelUpdateNeighborsAt(void* thisPtr, int x, int y, int z, int type) + { + if (Original_LevelUpdateNeighborsAt) + Original_LevelUpdateNeighborsAt(thisPtr, x, y, z, type); + + if (!s_levelGetTile) + return; + + static const int kNeighborOffsets[6][3] = { + {-1, 0, 0}, {1, 0, 0}, + {0, -1, 0}, {0, 1, 0}, + {0, 0, -1}, {0, 0, 1} + }; + + for (const auto& offset : kNeighborOffsets) + { + const int nx = x + offset[0]; + const int ny = y + offset[1]; + const int nz = z + offset[2]; + const int neighborBlockId = s_levelGetTile(thisPtr, nx, ny, nz); + DispatchManagedBlockById(neighborBlockId, thisPtr, nx, ny, nz, 1, type); + } + } + + bool __fastcall Hooked_ServerLevelTickPendingTicks(void* thisPtr, bool force) + { + const bool originalResult = Original_ServerLevelTickPendingTicks + ? Original_ServerLevelTickPendingTicks(thisPtr, force) + : false; + + bool anyPending = false; + for (auto it = s_managedScheduledTicks.begin(); it != s_managedScheduledTicks.end();) + { + if (it->levelPtr != thisPtr) + { + ++it; + continue; + } + + --it->remainingTicks; + if (it->remainingTicks <= 0) + { + DispatchManagedBlockById(it->blockId, it->levelPtr, it->x, it->y, it->z, 2, 0); + it = s_managedScheduledTicks.erase(it); + } + else + { + anyPending = true; + ++it; + } + } + + return originalResult || anyPending; + } + + int __fastcall Hooked_TileGetResource(void* thisPtr, int data, void* random, int playerBonusLevel) + { + const ManagedBlockRegistry::Definition* def = ManagedBlockRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->dropBlockId >= 0) + return def->dropBlockId; + return Original_TileGetResource ? Original_TileGetResource(thisPtr, data, random, playerBonusLevel) : 0; + } + + int __fastcall Hooked_TileCloneTileId(void* thisPtr, void* level, int x, int y, int z) + { + const ManagedBlockRegistry::Definition* def = ManagedBlockRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->cloneBlockId >= 0) + return def->cloneBlockId; + return Original_TileCloneTileId ? Original_TileCloneTileId(thisPtr, level, x, y, z) : TryReadTileId(thisPtr); + } + + void* __fastcall Hooked_StoneSlabGetTexture(void* thisPtr, int face, int data) + { + if (CustomSlabRegistry::Find(TryReadTileId(thisPtr))) + { + if (void* iconPtr = TryReadTileIcon(thisPtr)) + return iconPtr; + } + return Original_StoneSlabGetTexture ? Original_StoneSlabGetTexture(thisPtr, face, data) : nullptr; + } + + void* __fastcall Hooked_WoodSlabGetTexture(void* thisPtr, int face, int data) + { + if (CustomSlabRegistry::Find(TryReadTileId(thisPtr))) + { + if (void* iconPtr = TryReadTileIcon(thisPtr)) + return iconPtr; + } + return Original_WoodSlabGetTexture ? Original_WoodSlabGetTexture(thisPtr, face, data) : nullptr; + } + + int __fastcall Hooked_StoneSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def) + return def->halfBlockId; + return Original_StoneSlabGetResource ? Original_StoneSlabGetResource(thisPtr, data, random, playerBonusLevel) : 0; + } + + int __fastcall Hooked_WoodSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def) + return def->halfBlockId; + return Original_WoodSlabGetResource ? Original_WoodSlabGetResource(thisPtr, data, random, playerBonusLevel) : 0; + } + + unsigned int __fastcall Hooked_StoneSlabGetDescriptionId(void* thisPtr, int data) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return static_cast(def->descriptionId); + return Original_StoneSlabGetDescriptionId ? Original_StoneSlabGetDescriptionId(thisPtr, data) : 0; + } + + unsigned int __fastcall Hooked_WoodSlabGetDescriptionId(void* thisPtr, int data) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return static_cast(def->descriptionId); + return Original_WoodSlabGetDescriptionId ? Original_WoodSlabGetDescriptionId(thisPtr, data) : 0; + } + + int __fastcall Hooked_StoneSlabGetAuxName(void* thisPtr, int auxValue) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return def->descriptionId; + return Original_StoneSlabGetAuxName ? Original_StoneSlabGetAuxName(thisPtr, auxValue) : 0; + } + + int __fastcall Hooked_WoodSlabGetAuxName(void* thisPtr, int auxValue) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def && def->descriptionId >= 0) + return def->descriptionId; + return Original_WoodSlabGetAuxName ? Original_WoodSlabGetAuxName(thisPtr, auxValue) : 0; + } + + void __fastcall Hooked_StoneSlabRegisterIcons(void* thisPtr, void* iconRegister) + { + if (Original_StoneSlabRegisterIcons) + Original_StoneSlabRegisterIcons(thisPtr, iconRegister); + } + + void __fastcall Hooked_WoodSlabRegisterIcons(void* thisPtr, void* iconRegister) + { + if (Original_WoodSlabRegisterIcons) + Original_WoodSlabRegisterIcons(thisPtr, iconRegister); + } + + void* __fastcall Hooked_StoneSlabItemGetIcon(void* thisPtr, int auxValue) + { + if (thisPtr && IsReadableRange(static_cast(thisPtr) + kItemIdOffset, sizeof(int))) + { + const int itemId = *reinterpret_cast(static_cast(thisPtr) + kItemIdOffset); + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(itemId); + if (def && s_tileTilesArray && IsReadableRange(s_tileTilesArray, sizeof(void*))) + { + const void* arrayPtr = *reinterpret_cast(s_tileTilesArray); + if (arrayPtr && IsReadableRange(arrayPtr, sizeof(void*) * 4096)) + { + auto* tiles = reinterpret_cast(const_cast(arrayPtr)); + if (def->halfBlockId >= 0 && def->halfBlockId < 4096) + { + void* halfTile = const_cast(tiles[def->halfBlockId]); + void* iconPtr = TryReadTileIcon(halfTile); + if (iconPtr) + return iconPtr; + } + } + } + } + + return Original_StoneSlabItemGetIcon + ? Original_StoneSlabItemGetIcon(thisPtr, auxValue) + : nullptr; + } + + unsigned int __fastcall Hooked_StoneSlabItemGetDescriptionId(void* thisPtr, void* itemInstanceSharedPtr) + { + if (thisPtr && IsReadableRange(static_cast(thisPtr) + kItemIdOffset, sizeof(int))) + { + const int itemId = *reinterpret_cast(static_cast(thisPtr) + kItemIdOffset); + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(itemId); + if (def && def->descriptionId >= 0) + return static_cast(def->descriptionId); + } + + return Original_StoneSlabItemGetDescriptionId + ? Original_StoneSlabItemGetDescriptionId(thisPtr, itemInstanceSharedPtr) + : 0; + } + + int __fastcall Hooked_HalfSlabCloneTileId(void* thisPtr, void* level, int x, int y, int z) + { + const CustomSlabRegistry::Definition* def = CustomSlabRegistry::Find(TryReadTileId(thisPtr)); + if (def) + return def->halfBlockId; + return Original_HalfSlabCloneTileId ? Original_HalfSlabCloneTileId(thisPtr, level, x, y, z) : TryReadTileId(thisPtr); + } + void __fastcall Hooked_LoadUVs(void* thisPtr) { LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: ENTER (textureMap=%p)", thisPtr); @@ -858,6 +1318,7 @@ namespace GameHooks Original_LoadUVs(thisPtr); LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: original returned, creating mod icons"); ModAtlas::CreateModIcons(thisPtr); + PatchCustomSlabIcons(); LogUtil::Log("[WeaveLoader] Hooked_LoadUVs: DONE"); } @@ -1585,11 +2046,13 @@ namespace GameHooks { LogUtil::Log("[WeaveLoader] Hook: RunStaticCtors -- calling PreInit"); DotNetHost::CallPreInit(); + s_preInitCalled = true; Original_RunStaticCtors(); LogUtil::Log("[WeaveLoader] Hook: RunStaticCtors complete -- calling Init"); DotNetHost::CallInit(); + s_initCalled = true; // Inject mod strings directly into the game's StringTable vector. // This is necessary because the compiler inlines GetString at call @@ -1653,8 +2116,26 @@ namespace GameHooks // fully populated. Copy it to our mod icons so getSourceHeight() works. ModAtlas::FixupModIcons(); - LogUtil::Log("[WeaveLoader] Hook: Minecraft::init complete -- calling PostInit"); - DotNetHost::CallPostInit(); + if (!s_preInitCalled) + { + LogUtil::Log("[WeaveLoader] Hook: Minecraft::init -- late PreInit fallback"); + DotNetHost::CallPreInit(); + s_preInitCalled = true; + } + if (!s_initCalled) + { + LogUtil::Log("[WeaveLoader] Hook: Minecraft::init -- late Init fallback"); + DotNetHost::CallInit(); + s_initCalled = true; + ModStrings::InjectAllIntoGameTable(); + } + + if (!s_postInitCalled) + { + LogUtil::Log("[WeaveLoader] Hook: Minecraft::init complete -- calling PostInit"); + DotNetHost::CallPostInit(); + s_postInitCalled = true; + } } void __fastcall Hooked_ExitGame(void* thisPtr) diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index b8d02ec..b023a67 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -27,6 +27,22 @@ typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level, typedef bool (__fastcall *ItemMineBlock_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr); typedef float (__fastcall *PickaxeGetDestroySpeed_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr); typedef bool (__fastcall *PickaxeCanDestroySpecial_fn)(void* thisPtr, void* tilePtr); +typedef void (__fastcall *TileOnPlace_fn)(void* thisPtr, void* level, int x, int y, int z); +typedef void (__fastcall *TileNeighborChanged_fn)(void* thisPtr, void* level, int x, int y, int z, int type); +typedef void (__fastcall *TileTick_fn)(void* thisPtr, void* level, int x, int y, int z, void* random); +typedef bool (__fastcall *LevelSetTileAndDataDispatch_fn)(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags); +typedef bool (__fastcall *LevelSetDataDispatch_fn)(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate); +typedef void (__fastcall *LevelUpdateNeighborsAtDispatch_fn)(void* thisPtr, int x, int y, int z, int type); +typedef bool (__fastcall *ServerLevelTickPendingTicks_fn)(void* thisPtr, bool force); +typedef int (__fastcall *LevelGetTile_fn)(void* thisPtr, int x, int y, int z); +typedef int (__fastcall *TileGetResource_fn)(void* thisPtr, int data, void* random, int playerBonusLevel); +typedef int (__fastcall *TileCloneTileId_fn)(void* thisPtr, void* level, int x, int y, int z); +typedef void* (__fastcall *TileGetTextureFaceData_fn)(void* thisPtr, int face, int data); +typedef unsigned int (__fastcall *TileGetDescriptionId_fn)(void* thisPtr, int data); +typedef int (__fastcall *TileGetAuxName_fn)(void* thisPtr, int auxValue); +typedef void (__fastcall *TileRegisterIcons_fn)(void* thisPtr, void* iconRegister); +typedef void* (__fastcall *StoneSlabItemGetIcon_fn)(void* thisPtr, int auxValue); +typedef unsigned int (__fastcall *StoneSlabItemGetDescriptionId_fn)(void* thisPtr, void* itemInstanceSharedPtr); typedef bool (__fastcall *PlayerCanDestroy_fn)(void* thisPtr, void* tilePtr); typedef bool (__fastcall *GameModeUseItem_fn)(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly); typedef void (__fastcall *MinecraftSetLevel_fn)(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut); @@ -75,6 +91,28 @@ namespace GameHooks extern PickaxeCanDestroySpecial_fn Original_PickaxeItemCanDestroySpecial; extern PickaxeGetDestroySpeed_fn Original_ShovelItemGetDestroySpeed; extern PickaxeCanDestroySpecial_fn Original_ShovelItemCanDestroySpecial; + extern TileOnPlace_fn Original_TileOnPlace; + extern TileNeighborChanged_fn Original_TileNeighborChanged; + extern TileTick_fn Original_TileTick; + extern LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData; + extern LevelSetDataDispatch_fn Original_LevelSetData; + extern LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt; + extern ServerLevelTickPendingTicks_fn Original_ServerLevelTickPendingTicks; + extern TileGetResource_fn Original_TileGetResource; + extern TileCloneTileId_fn Original_TileCloneTileId; + extern TileGetTextureFaceData_fn Original_StoneSlabGetTexture; + extern TileGetTextureFaceData_fn Original_WoodSlabGetTexture; + extern TileGetResource_fn Original_StoneSlabGetResource; + extern TileGetResource_fn Original_WoodSlabGetResource; + extern TileGetDescriptionId_fn Original_StoneSlabGetDescriptionId; + extern TileGetDescriptionId_fn Original_WoodSlabGetDescriptionId; + extern TileGetAuxName_fn Original_StoneSlabGetAuxName; + extern TileGetAuxName_fn Original_WoodSlabGetAuxName; + extern TileRegisterIcons_fn Original_StoneSlabRegisterIcons; + extern TileRegisterIcons_fn Original_WoodSlabRegisterIcons; + extern StoneSlabItemGetIcon_fn Original_StoneSlabItemGetIcon; + extern StoneSlabItemGetDescriptionId_fn Original_StoneSlabItemGetDescriptionId; + extern TileCloneTileId_fn Original_HalfSlabCloneTileId; extern PlayerCanDestroy_fn Original_PlayerCanDestroy; extern GameModeUseItem_fn Original_ServerPlayerGameModeUseItem; extern GameModeUseItem_fn Original_MultiPlayerGameModeUseItem; @@ -115,6 +153,28 @@ namespace GameHooks bool __fastcall Hooked_PickaxeItemCanDestroySpecial(void* thisPtr, void* tilePtr); float __fastcall Hooked_ShovelItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr); bool __fastcall Hooked_ShovelItemCanDestroySpecial(void* thisPtr, void* tilePtr); + void __fastcall Hooked_TileOnPlace(void* thisPtr, void* level, int x, int y, int z); + void __fastcall Hooked_TileNeighborChanged(void* thisPtr, void* level, int x, int y, int z, int type); + void __fastcall Hooked_TileTick(void* thisPtr, void* level, int x, int y, int z, void* random); + bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags); + bool __fastcall Hooked_LevelSetData(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate); + void __fastcall Hooked_LevelUpdateNeighborsAt(void* thisPtr, int x, int y, int z, int type); + bool __fastcall Hooked_ServerLevelTickPendingTicks(void* thisPtr, bool force); + int __fastcall Hooked_TileGetResource(void* thisPtr, int data, void* random, int playerBonusLevel); + int __fastcall Hooked_TileCloneTileId(void* thisPtr, void* level, int x, int y, int z); + void* __fastcall Hooked_StoneSlabGetTexture(void* thisPtr, int face, int data); + void* __fastcall Hooked_WoodSlabGetTexture(void* thisPtr, int face, int data); + int __fastcall Hooked_StoneSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel); + int __fastcall Hooked_WoodSlabGetResource(void* thisPtr, int data, void* random, int playerBonusLevel); + unsigned int __fastcall Hooked_StoneSlabGetDescriptionId(void* thisPtr, int data); + unsigned int __fastcall Hooked_WoodSlabGetDescriptionId(void* thisPtr, int data); + int __fastcall Hooked_StoneSlabGetAuxName(void* thisPtr, int auxValue); + int __fastcall Hooked_WoodSlabGetAuxName(void* thisPtr, int auxValue); + void __fastcall Hooked_StoneSlabRegisterIcons(void* thisPtr, void* iconRegister); + void __fastcall Hooked_WoodSlabRegisterIcons(void* thisPtr, void* iconRegister); + void* __fastcall Hooked_StoneSlabItemGetIcon(void* thisPtr, int auxValue); + unsigned int __fastcall Hooked_StoneSlabItemGetDescriptionId(void* thisPtr, void* itemInstanceSharedPtr); + int __fastcall Hooked_HalfSlabCloneTileId(void* thisPtr, void* level, int x, int y, int z); bool __fastcall Hooked_PlayerCanDestroy(void* thisPtr, void* tilePtr); bool __fastcall Hooked_ServerPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly); bool __fastcall Hooked_MultiPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly); @@ -140,6 +200,9 @@ namespace GameHooks void* livingEntityGetViewVector, void* entityLerpMotion, void* entitySetPos); + void SetBlockHelperSymbols(void* tileGetTextureFaceData); + void SetManagedBlockDispatchSymbols(void* levelGetTile); + void EnqueueManagedBlockTick(void* levelPtr, int x, int y, int z, int blockId, int delay); bool SummonEntityByNumericId(int entityNumericId, double x, double y, double z); bool ConsumePlayerResource(void* playerPtr, int itemId, int count); bool DamageItemInstance(void* itemInstancePtr, int amount, void* ownerSharedPtr); diff --git a/WeaveLoaderRuntime/src/GameObjectFactory.cpp b/WeaveLoaderRuntime/src/GameObjectFactory.cpp index 7e3337a..a391237 100644 --- a/WeaveLoaderRuntime/src/GameObjectFactory.cpp +++ b/WeaveLoaderRuntime/src/GameObjectFactory.cpp @@ -1,4 +1,5 @@ #include "GameObjectFactory.h" +#include "CustomSlabRegistry.h" #include "SymbolResolver.h" #include "PdbParser.h" #include "LogUtil.h" @@ -17,9 +18,12 @@ typedef void* (__fastcall *TileSetSoundType_fn)(void* thisPtr, const void* sound typedef void* (__fastcall *TileSetIconName_fn)(void* thisPtr, const std::wstring& name); // Tile* Tile::setDescriptionId(unsigned int) — public virtual typedef void* (__fastcall *TileSetDescriptionId_fn)(void* thisPtr, unsigned int id); +typedef void* (__fastcall *TileSetLightBlock_fn)(void* thisPtr, int value); +typedef void* (__fastcall *TileSetLightEmission_fn)(void* thisPtr, float value); // TileItem::TileItem(int id) typedef void (__fastcall *TileItemCtor_fn)(void* thisPtr, int id); +typedef void (__fastcall *HeavyTileCtor_fn)(void* thisPtr, int id, bool isSolidRender); // Item::Item(int id) — protected ctor typedef void (__fastcall *ItemCtor_fn)(void* thisPtr, int id); @@ -29,8 +33,12 @@ typedef void (__fastcall *ShovelCtor_fn)(void* thisPtr, int id, const void* tier typedef void (__fastcall *HoeCtor_fn)(void* thisPtr, int id, const void* tier); typedef void (__fastcall *HatchetCtor_fn)(void* thisPtr, int id, const void* tier); typedef void (__fastcall *WeaponCtor_fn)(void* thisPtr, int id, const void* tier); +typedef void (__fastcall *StoneSlabCtor_fn)(void* thisPtr, int id, bool fullSize); +typedef void (__fastcall *WoodSlabCtor_fn)(void* thisPtr, int id, bool fullSize); +typedef void (__fastcall *StoneSlabItemCtor_fn)(void* thisPtr, int id, void* halfTile, void* fullTile, bool full); // Item* Item::setIconName(const std::wstring&) typedef void* (__fastcall *ItemSetIconName_fn)(void* thisPtr, const std::wstring& name); +typedef void* (__fastcall *ItemSetUseDescriptionId_fn)(void* thisPtr, unsigned int id); // Item::getDescriptionId(int) — used to extract the descriptionId field offset typedef unsigned int (__fastcall *ItemGetDescriptionId_fn)(void* thisPtr, int auxData); @@ -40,8 +48,11 @@ static TileSetFloat_fn fnSetExplodeable = nullptr; static TileSetSoundType_fn fnSetSoundType = nullptr; static TileSetIconName_fn fnTileSetIconName= nullptr; static TileSetDescriptionId_fn fnTileSetDescriptionId = nullptr; +static TileSetLightBlock_fn fnTileSetLightBlock = nullptr; +static TileSetLightEmission_fn fnTileSetLightEmission = nullptr; static TileItemCtor_fn fnTileItemCtor = nullptr; +static HeavyTileCtor_fn fnHeavyTileCtor = nullptr; static ItemCtor_fn fnItemCtor = nullptr; static PickaxeCtor_fn fnPickaxeCtor = nullptr; @@ -49,7 +60,11 @@ static ShovelCtor_fn fnShovelCtor = nullptr; static HoeCtor_fn fnHoeCtor = nullptr; static HatchetCtor_fn fnHatchetCtor = nullptr; static WeaponCtor_fn fnWeaponCtor = nullptr; +static StoneSlabCtor_fn fnStoneSlabCtor = nullptr; +static WoodSlabCtor_fn fnWoodSlabCtor = nullptr; +static StoneSlabItemCtor_fn fnStoneSlabItemCtor = nullptr; static ItemSetIconName_fn fnItemSetIconName= nullptr; +static ItemSetUseDescriptionId_fn fnItemSetUseDescriptionId = nullptr; static int s_itemDescIdOffset = -1; // offset of descriptionId field in Item, extracted from getDescriptionId // Store ADDRESSES of Material*/SoundType* statics so we can dereference lazily @@ -61,6 +76,9 @@ static void** s_tierAddrs[5] = {}; static const int TILE_ALLOC_SIZE = 1024; static const int ITEM_ALLOC_SIZE = 1024; static const int TILEITEM_ALLOC_SIZE = 1024; +static const int STONE_SLAB_TILE_ALLOC_SIZE = 128; +static const int WOOD_SLAB_TILE_ALLOC_SIZE = 120; +static const int STONE_SLAB_TILE_ITEM_ALLOC_SIZE = 192; static bool s_resolved = false; static std::unordered_map s_createdItems; @@ -117,9 +135,14 @@ bool ResolveSymbols(SymbolResolver& resolver) "?setIconName@Tile@@MEAAPEAV1@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"); fnTileSetDescriptionId = (TileSetDescriptionId_fn)resolver.Resolve( "?setDescriptionId@Tile@@UEAAPEAV1@I@Z"); + fnTileSetLightBlock = (TileSetLightBlock_fn)resolver.Resolve( + "?setLightBlock@Tile@@MEAAPEAV1@H@Z"); + fnTileSetLightEmission = (TileSetLightEmission_fn)resolver.Resolve( + "?setLightEmission@Tile@@MEAAPEAV1@M@Z"); // TileItem constructor fnTileItemCtor = (TileItemCtor_fn)resolver.Resolve("??0TileItem@@QEAA@H@Z"); + fnHeavyTileCtor = (HeavyTileCtor_fn)resolver.Resolve("??0HeavyTile@@QEAA@H_N@Z"); // Item constructor — protected (IEAA not QEAA) fnItemCtor = (ItemCtor_fn)resolver.Resolve("??0Item@@IEAA@H@Z"); @@ -128,10 +151,15 @@ bool ResolveSymbols(SymbolResolver& resolver) fnHoeCtor = (HoeCtor_fn)resolver.Resolve("??0HoeItem@@QEAA@HPEBVTier@Item@@@Z"); fnHatchetCtor = (HatchetCtor_fn)resolver.Resolve("??0HatchetItem@@QEAA@HPEBVTier@Item@@@Z"); fnWeaponCtor = (WeaponCtor_fn)resolver.Resolve("??0WeaponItem@@QEAA@HPEBVTier@Item@@@Z"); + fnStoneSlabCtor = (StoneSlabCtor_fn)resolver.Resolve("??0StoneSlabTile@@QEAA@H_N@Z"); + fnWoodSlabCtor = (WoodSlabCtor_fn)resolver.Resolve("??0WoodSlabTile@@QEAA@H_N@Z"); + fnStoneSlabItemCtor = (StoneSlabItemCtor_fn)resolver.Resolve("??0StoneSlabTileItem@@QEAA@HPEAVHalfSlabTile@@0_N@Z"); // Item::setIconName fnItemSetIconName = (ItemSetIconName_fn)resolver.Resolve( "?setIconName@Item@@QEAAPEAV1@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@@Z"); + fnItemSetUseDescriptionId = (ItemSetUseDescriptionId_fn)resolver.Resolve( + "?setUseDescriptionId@Item@@QEAAPEAV1@I@Z"); // Item::setDescriptionId is inlined — extract the field offset from getDescriptionId instead. // getDescriptionId(int) is "mov eax, [rcx+offset]; ret" so we parse the offset from its opcodes. void* fnItemGetDescId = resolver.Resolve("?getDescriptionId@Item@@UEAAIH@Z"); @@ -214,14 +242,21 @@ bool ResolveSymbols(SymbolResolver& resolver) logSym("setExplodeable", (void*)fnSetExplodeable); logSym("setSoundType", (void*)fnSetSoundType); logSym("Tile::setIconName", (void*)fnTileSetIconName); + logSym("Tile::setLightBlock", (void*)fnTileSetLightBlock); + logSym("Tile::setLightEmission", (void*)fnTileSetLightEmission); logSym("TileItem::TileItem", (void*)fnTileItemCtor); + logSym("HeavyTile::HeavyTile", (void*)fnHeavyTileCtor); logSym("Item::Item", (void*)fnItemCtor); logSym("PickaxeItem::PickaxeItem", (void*)fnPickaxeCtor); logSym("ShovelItem::ShovelItem", (void*)fnShovelCtor); logSym("HoeItem::HoeItem", (void*)fnHoeCtor); logSym("HatchetItem::HatchetItem", (void*)fnHatchetCtor); logSym("WeaponItem::WeaponItem", (void*)fnWeaponCtor); + logSym("StoneSlabTile::StoneSlabTile", (void*)fnStoneSlabCtor); + logSym("WoodSlabTile::WoodSlabTile", (void*)fnWoodSlabCtor); + logSym("StoneSlabTileItem::StoneSlabTileItem", (void*)fnStoneSlabItemCtor); logSym("Item::setIconName", (void*)fnItemSetIconName); + logSym("Item::setUseDescriptionId", (void*)fnItemSetUseDescriptionId); logSym("Material::stone addr", (void*)s_materialAddrs[1]); logSym("SOUND_STONE addr", (void*)s_soundAddrs[1]); @@ -247,8 +282,44 @@ bool ResolveSymbols(SymbolResolver& resolver) return s_resolved; } +static void ApplyTileCommon( + void* tile, + float hardness, + float resistance, + int soundType, + const wchar_t* iconName, + float lightEmission, + int lightBlock, + int descriptionId) +{ + if (fnSetDestroyTime) + fnSetDestroyTime(tile, hardness); + + if (fnSetExplodeable) + fnSetExplodeable(tile, resistance); + + void* sound = GetSound(soundType); + if (fnSetSoundType && sound) + fnSetSoundType(tile, sound); + + if (fnTileSetLightEmission) + fnTileSetLightEmission(tile, lightEmission); + + if (fnTileSetLightBlock) + fnTileSetLightBlock(tile, lightBlock); + + if (fnTileSetIconName && iconName) + { + std::wstring name(iconName); + fnTileSetIconName(tile, name); + } + + if (fnTileSetDescriptionId && descriptionId >= 0) + fnTileSetDescriptionId(tile, static_cast(descriptionId)); +} + bool CreateTile(int tileId, int materialType, float hardness, float resistance, - int soundType, const wchar_t* iconName, int descriptionId) + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) { if (!s_resolved || !fnTileCtor) { @@ -270,27 +341,7 @@ bool CreateTile(int tileId, int materialType, float hardness, float resistance, void* tile = ::operator new(TILE_ALLOC_SIZE); memset(tile, 0, TILE_ALLOC_SIZE); fnTileCtor(tile, tileId, mat, true); - - if (fnSetDestroyTime) - fnSetDestroyTime(tile, hardness); - - if (fnSetExplodeable) - fnSetExplodeable(tile, resistance); - - void* sound = GetSound(soundType); - if (fnSetSoundType && sound) - fnSetSoundType(tile, sound); - - if (fnTileSetIconName && iconName) - { - std::wstring name(iconName); - fnTileSetIconName(tile, name); - } - - if (fnTileSetDescriptionId && descriptionId >= 0) - { - fnTileSetDescriptionId(tile, static_cast(descriptionId)); - } + ApplyTileCommon(tile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); LogUtil::Log("[WeaveLoader] Created Tile id=%d (material=%d, icon=%ls, descId=%d)", tileId, materialType, iconName ? iconName : L"", descriptionId); @@ -308,6 +359,102 @@ bool CreateTile(int tileId, int materialType, float hardness, float resistance, return true; } +bool CreateManagedTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) +{ + return CreateTile(tileId, materialType, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); +} + +bool CreateFallingTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) +{ + if (!s_resolved || !fnHeavyTileCtor) + { + LogUtil::Log("[WeaveLoader] CreateFallingTile: symbols not resolved"); + return false; + } + + void* tile = ::operator new(TILE_ALLOC_SIZE); + memset(tile, 0, TILE_ALLOC_SIZE); + fnHeavyTileCtor(tile, tileId, true); + ApplyTileCommon(tile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); + + if (fnTileItemCtor) + { + void* tileItem = ::operator new(TILEITEM_ALLOC_SIZE); + memset(tileItem, 0, TILEITEM_ALLOC_SIZE); + fnTileItemCtor(tileItem, tileId - 256); + } + + LogUtil::Log("[WeaveLoader] Created FallingTile id=%d (icon=%ls, descId=%d)", + tileId, iconName ? iconName : L"", descriptionId); + return true; +} + +bool CreateSlabPair(int halfTileId, int fullTileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId) +{ + if (!s_resolved || !fnStoneSlabItemCtor || (!fnStoneSlabCtor && !fnWoodSlabCtor)) + { + LogUtil::Log("[WeaveLoader] CreateSlabPair: symbols not resolved"); + return false; + } + + const bool woodFamily = (materialType == 2); + if ((woodFamily && !fnWoodSlabCtor) || (!woodFamily && !fnStoneSlabCtor)) + { + LogUtil::Log("[WeaveLoader] CreateSlabPair: missing %s slab ctor", woodFamily ? "wood" : "stone"); + return false; + } + const int tileAllocSize = woodFamily ? WOOD_SLAB_TILE_ALLOC_SIZE : STONE_SLAB_TILE_ALLOC_SIZE; + + void* halfTile = ::operator new(tileAllocSize); + void* fullTile = ::operator new(tileAllocSize); + memset(halfTile, 0, tileAllocSize); + memset(fullTile, 0, tileAllocSize); + + if (woodFamily) + { + fnWoodSlabCtor(halfTile, halfTileId, false); + fnWoodSlabCtor(fullTile, fullTileId, true); + } + else + { + fnStoneSlabCtor(halfTile, halfTileId, false); + fnStoneSlabCtor(fullTile, fullTileId, true); + } + + ApplyTileCommon(halfTile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); + ApplyTileCommon(fullTile, hardness, resistance, soundType, iconName, lightEmission, lightBlock, descriptionId); + + void* slabItem = ::operator new(STONE_SLAB_TILE_ITEM_ALLOC_SIZE); + memset(slabItem, 0, STONE_SLAB_TILE_ITEM_ALLOC_SIZE); + fnStoneSlabItemCtor(slabItem, halfTileId - 256, halfTile, fullTile, false); + + if (fnItemSetIconName) + { + std::wstring name = (iconName && iconName[0]) ? iconName : L"MISSING_ICON_ITEM"; + fnItemSetIconName(slabItem, name); + } + + if (s_itemDescIdOffset > 0 && descriptionId >= 0) + { + *reinterpret_cast(static_cast(slabItem) + s_itemDescIdOffset) = + static_cast(descriptionId); + } + + if (fnItemSetUseDescriptionId && descriptionId >= 0) + { + fnItemSetUseDescriptionId(slabItem, static_cast(descriptionId)); + } + + CustomSlabRegistry::Register(halfTileId, fullTileId, descriptionId, woodFamily, iconName); + + LogUtil::Log("[WeaveLoader] Created SlabPair half=%d full=%d family=%s (icon=%ls, descId=%d)", + halfTileId, fullTileId, woodFamily ? "wood" : "stone", iconName ? iconName : L"", descriptionId); + return true; +} + bool CreateItem(int itemId, int maxStackSize, int maxDamage, const wchar_t* iconName, int descriptionId) { if (!s_resolved || !fnItemCtor) diff --git a/WeaveLoaderRuntime/src/GameObjectFactory.h b/WeaveLoaderRuntime/src/GameObjectFactory.h index 08c3553..cb44fa9 100644 --- a/WeaveLoaderRuntime/src/GameObjectFactory.h +++ b/WeaveLoaderRuntime/src/GameObjectFactory.h @@ -13,7 +13,13 @@ namespace GameObjectFactory // iconName is the texture atlas key (e.g. "ruby_ore"). // descriptionId: if >= 0, call setDescriptionId on the Tile for localization. bool CreateTile(int tileId, int materialType, float hardness, float resistance, - int soundType, const wchar_t* iconName, int descriptionId = -1); + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); + bool CreateManagedTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); + bool CreateFallingTile(int tileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); + bool CreateSlabPair(int halfTileId, int fullTileId, int materialType, float hardness, float resistance, + int soundType, const wchar_t* iconName, float lightEmission, int lightBlock, int descriptionId = -1); // Create an Item game object. itemId is the FINAL id (256 + constructor param). // The Item is registered in Item::items[itemId]. diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index fd9687f..1e3e6c4 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -7,6 +7,7 @@ #include "MainMenuOverlay.h" #include "GameObjectFactory.h" #include "FurnaceRecipeRegistry.h" +#include "NativeExports.h" #include "LogUtil.h" #include @@ -292,6 +293,274 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.pLevelSetTileAndData) + { + if (MH_CreateHook(symbols.pLevelSetTileAndData, + reinterpret_cast(&GameHooks::Hooked_LevelSetTileAndData), + reinterpret_cast(&GameHooks::Original_LevelSetTileAndData)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Level::setTileAndData"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Level::setTileAndData (managed block callbacks)"); + } + } + + if (symbols.pLevelSetData) + { + if (MH_CreateHook(symbols.pLevelSetData, + reinterpret_cast(&GameHooks::Hooked_LevelSetData), + reinterpret_cast(&GameHooks::Original_LevelSetData)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Level::setData"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Level::setData (managed block callbacks)"); + } + } + + if (symbols.pLevelUpdateNeighborsAt) + { + if (MH_CreateHook(symbols.pLevelUpdateNeighborsAt, + reinterpret_cast(&GameHooks::Hooked_LevelUpdateNeighborsAt), + reinterpret_cast(&GameHooks::Original_LevelUpdateNeighborsAt)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Level::updateNeighborsAt"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Level::updateNeighborsAt (managed block callbacks)"); + } + } + + if (symbols.pServerLevelTickPendingTicks) + { + if (MH_CreateHook(symbols.pServerLevelTickPendingTicks, + reinterpret_cast(&GameHooks::Hooked_ServerLevelTickPendingTicks), + reinterpret_cast(&GameHooks::Original_ServerLevelTickPendingTicks)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook ServerLevel::tickPendingTicks"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked ServerLevel::tickPendingTicks (managed block callbacks)"); + } + } + + if (symbols.pTileGetResource) + { + if (MH_CreateHook(symbols.pTileGetResource, + reinterpret_cast(&GameHooks::Hooked_TileGetResource), + reinterpret_cast(&GameHooks::Original_TileGetResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::getResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::getResource (managed block drops)"); + } + } + + if (symbols.pTileCloneTileId) + { + if (MH_CreateHook(symbols.pTileCloneTileId, + reinterpret_cast(&GameHooks::Hooked_TileCloneTileId), + reinterpret_cast(&GameHooks::Original_TileCloneTileId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::cloneTileId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::cloneTileId (managed block pick-block)"); + } + } + + if (symbols.pStoneSlabGetTexture) + { + if (MH_CreateHook(symbols.pStoneSlabGetTexture, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetTexture), + reinterpret_cast(&GameHooks::Original_StoneSlabGetTexture)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getTexture"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getTexture (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetTexture) + { + if (MH_CreateHook(symbols.pWoodSlabGetTexture, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetTexture), + reinterpret_cast(&GameHooks::Original_WoodSlabGetTexture)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getTexture"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getTexture (custom slabs)"); + } + } + + if (symbols.pStoneSlabGetResource) + { + if (MH_CreateHook(symbols.pStoneSlabGetResource, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetResource), + reinterpret_cast(&GameHooks::Original_StoneSlabGetResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getResource (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetResource) + { + if (MH_CreateHook(symbols.pWoodSlabGetResource, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetResource), + reinterpret_cast(&GameHooks::Original_WoodSlabGetResource)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getResource"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getResource (custom slabs)"); + } + } + + if (symbols.pStoneSlabGetDescriptionId) + { + if (MH_CreateHook(symbols.pStoneSlabGetDescriptionId, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetDescriptionId), + reinterpret_cast(&GameHooks::Original_StoneSlabGetDescriptionId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getDescriptionId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getDescriptionId (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetDescriptionId) + { + if (MH_CreateHook(symbols.pWoodSlabGetDescriptionId, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetDescriptionId), + reinterpret_cast(&GameHooks::Original_WoodSlabGetDescriptionId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getDescriptionId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getDescriptionId (custom slabs)"); + } + } + + if (symbols.pStoneSlabGetAuxName) + { + if (MH_CreateHook(symbols.pStoneSlabGetAuxName, + reinterpret_cast(&GameHooks::Hooked_StoneSlabGetAuxName), + reinterpret_cast(&GameHooks::Original_StoneSlabGetAuxName)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::getAuxName"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::getAuxName (custom slabs)"); + } + } + + if (symbols.pWoodSlabGetAuxName) + { + if (MH_CreateHook(symbols.pWoodSlabGetAuxName, + reinterpret_cast(&GameHooks::Hooked_WoodSlabGetAuxName), + reinterpret_cast(&GameHooks::Original_WoodSlabGetAuxName)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::getAuxName"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::getAuxName (custom slabs)"); + } + } + + if (symbols.pStoneSlabRegisterIcons) + { + if (MH_CreateHook(symbols.pStoneSlabRegisterIcons, + reinterpret_cast(&GameHooks::Hooked_StoneSlabRegisterIcons), + reinterpret_cast(&GameHooks::Original_StoneSlabRegisterIcons)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTile::registerIcons"); + } + else + { + MH_EnableHook(symbols.pStoneSlabRegisterIcons); + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTile::registerIcons (custom slabs)"); + } + } + + if (symbols.pWoodSlabRegisterIcons) + { + if (MH_CreateHook(symbols.pWoodSlabRegisterIcons, + reinterpret_cast(&GameHooks::Hooked_WoodSlabRegisterIcons), + reinterpret_cast(&GameHooks::Original_WoodSlabRegisterIcons)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook WoodSlabTile::registerIcons"); + } + else + { + MH_EnableHook(symbols.pWoodSlabRegisterIcons); + LogUtil::Log("[WeaveLoader] Hooked WoodSlabTile::registerIcons (custom slabs)"); + } + } + + if (symbols.pHalfSlabCloneTileId) + { + if (MH_CreateHook(symbols.pHalfSlabCloneTileId, + reinterpret_cast(&GameHooks::Hooked_HalfSlabCloneTileId), + reinterpret_cast(&GameHooks::Original_HalfSlabCloneTileId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook HalfSlabTile::cloneTileId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked HalfSlabTile::cloneTileId (custom slabs)"); + } + } + + if (symbols.pStoneSlabItemGetDescriptionId) + { + if (MH_CreateHook(symbols.pStoneSlabItemGetDescriptionId, + reinterpret_cast(&GameHooks::Hooked_StoneSlabItemGetDescriptionId), + reinterpret_cast(&GameHooks::Original_StoneSlabItemGetDescriptionId)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTileItem::getDescriptionId"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTileItem::getDescriptionId (custom slabs)"); + } + } + + if (symbols.pStoneSlabItemGetIcon) + { + if (MH_CreateHook(symbols.pStoneSlabItemGetIcon, + reinterpret_cast(&GameHooks::Hooked_StoneSlabItemGetIcon), + reinterpret_cast(&GameHooks::Original_StoneSlabItemGetIcon)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook StoneSlabTileItem::getIcon"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked StoneSlabTileItem::getIcon (custom slabs)"); + } + } + if (symbols.pPlayerCanDestroy) { if (MH_CreateHook(symbols.pPlayerCanDestroy, @@ -336,6 +605,14 @@ bool HookManager::Install(const SymbolResolver& symbols) GameHooks::SetAtlasLocationPointers(symbols.pTextureAtlasLocationBlocks, symbols.pTextureAtlasLocationItems); GameHooks::SetTileTilesArray(symbols.pTileTiles); + GameHooks::SetBlockHelperSymbols(symbols.pTileGetTextureFaceData); + GameHooks::SetManagedBlockDispatchSymbols(symbols.pLevelGetTile); + NativeExports::SetLevelInteropSymbols( + symbols.pLevelHasNeighborSignal, + symbols.pLevelSetTileAndData, + symbols.pServerLevelAddToTickNextTick ? symbols.pServerLevelAddToTickNextTick + : symbols.pLevelAddToTickNextTick, + symbols.pLevelGetTile); if (symbols.pTexturesBindTextureResource) { diff --git a/WeaveLoaderRuntime/src/ManagedBlockRegistry.cpp b/WeaveLoaderRuntime/src/ManagedBlockRegistry.cpp new file mode 100644 index 0000000..2d1fc5d --- /dev/null +++ b/WeaveLoaderRuntime/src/ManagedBlockRegistry.cpp @@ -0,0 +1,35 @@ +#include "ManagedBlockRegistry.h" +#include + +namespace +{ + std::unordered_map g_definitions; +} + +namespace ManagedBlockRegistry +{ + void Register(int blockId) + { + g_definitions.try_emplace(blockId); + } + + void Configure(int blockId, int dropBlockId, int cloneBlockId) + { + Definition& def = g_definitions[blockId]; + def.dropBlockId = dropBlockId; + def.cloneBlockId = cloneBlockId; + } + + const Definition* Find(int blockId) + { + const auto it = g_definitions.find(blockId); + if (it == g_definitions.end()) + return nullptr; + return &it->second; + } + + bool IsManaged(int blockId) + { + return g_definitions.find(blockId) != g_definitions.end(); + } +} diff --git a/WeaveLoaderRuntime/src/ManagedBlockRegistry.h b/WeaveLoaderRuntime/src/ManagedBlockRegistry.h new file mode 100644 index 0000000..cde91e6 --- /dev/null +++ b/WeaveLoaderRuntime/src/ManagedBlockRegistry.h @@ -0,0 +1,15 @@ +#pragma once + +namespace ManagedBlockRegistry +{ + struct Definition + { + int dropBlockId = -1; + int cloneBlockId = -1; + }; + + void Register(int blockId); + void Configure(int blockId, int dropBlockId, int cloneBlockId); + const Definition* Find(int blockId); + bool IsManaged(int blockId); +} diff --git a/WeaveLoaderRuntime/src/NativeExports.cpp b/WeaveLoaderRuntime/src/NativeExports.cpp index a50dfac..1e8fffb 100644 --- a/WeaveLoaderRuntime/src/NativeExports.cpp +++ b/WeaveLoaderRuntime/src/NativeExports.cpp @@ -7,12 +7,35 @@ #include "CustomPickaxeRegistry.h" #include "CustomToolMaterialRegistry.h" #include "CustomBlockRegistry.h" +#include "CustomSlabRegistry.h" +#include "ManagedBlockRegistry.h" #include "ModStrings.h" #include "LogUtil.h" #include #include #include +namespace +{ + using LevelHasNeighborSignal_fn = bool (__fastcall *)(void* thisPtr, int x, int y, int z); + using LevelSetTileAndData_fn = bool (__fastcall *)(void* thisPtr, int x, int y, int z, int blockId, int data, int flags); + using LevelAddToTickNextTick_fn = void (__fastcall *)(void* thisPtr, int x, int y, int z, int blockId, int delay); + using LevelGetTile_fn = int (__fastcall *)(void* thisPtr, int x, int y, int z); + + LevelHasNeighborSignal_fn s_levelHasNeighborSignal = nullptr; + LevelSetTileAndData_fn s_levelSetTileAndData = nullptr; + LevelAddToTickNextTick_fn s_levelAddToTickNextTick = nullptr; + LevelGetTile_fn s_levelGetTile = nullptr; +} + +void NativeExports::SetLevelInteropSymbols(void* hasNeighborSignal, void* setTileAndData, void* addToTickNextTick, void* getTile) +{ + s_levelHasNeighborSignal = reinterpret_cast(hasNeighborSignal); + s_levelSetTileAndData = reinterpret_cast(setTileAndData); + s_levelAddToTickNextTick = reinterpret_cast(addToTickNextTick); + s_levelGetTile = reinterpret_cast(getTile); +} + static std::wstring Utf8ToWide(const char* utf8) { if (!utf8 || !utf8[0]) return std::wstring(); @@ -36,6 +59,36 @@ static int ResolveRecipeId(const char* namespacedId, bool preferItem) return (blockId >= 0) ? blockId : itemId; } +static bool PrepareBlockRegistration( + const char* namespacedId, + const char* iconName, + const char* displayName, + int* outId, + int* outDescId, + std::wstring* outIcon) +{ + if (!namespacedId || !outId || !outDescId || !outIcon) + return false; + + *outId = IdRegistry::Instance().Register(IdRegistry::Type::Block, namespacedId); + if (*outId < 0) + { + LogUtil::Log("[WeaveLoader] Failed to allocate block ID for '%s'", namespacedId); + return false; + } + + *outIcon = Utf8ToWide(iconName); + *outDescId = -1; + if (displayName && displayName[0]) + { + *outDescId = ModStrings::AllocateId(); + std::wstring wName = Utf8ToWide(displayName); + ModStrings::Register(*outDescId, wName.c_str()); + } + + return true; +} + extern "C" { @@ -50,44 +103,162 @@ int native_register_block( int lightBlock, const char* displayName, int requiredHarvestLevel, - int requiredTool) + int requiredTool, + int acceptsRedstonePower) { if (!namespacedId) return -1; - int id = IdRegistry::Instance().Register(IdRegistry::Type::Block, namespacedId); - if (id < 0) - { - LogUtil::Log("[WeaveLoader] Failed to allocate block ID for '%s'", namespacedId); + int id = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &id, &descId, &wIcon)) return -1; - } LogUtil::Log("[WeaveLoader] Registered block '%s' -> ID %d (hardness=%.1f, resistance=%.1f)", namespacedId, id, hardness, resistance); - std::wstring wIcon = Utf8ToWide(iconName); - - int descId = -1; - if (displayName && displayName[0]) - { - descId = ModStrings::AllocateId(); - std::wstring wName = Utf8ToWide(displayName); - ModStrings::Register(descId, wName.c_str()); - } - if (!GameObjectFactory::CreateTile(id, materialId, hardness, resistance, - soundType, wIcon.empty() ? nullptr : wIcon.c_str(), descId)) + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) { LogUtil::Log("[WeaveLoader] Warning: failed to create game Tile for block '%s' id=%d", namespacedId, id); } - if (requiredHarvestLevel >= 0 || requiredTool != 0) + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) { - CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool); + CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); } return id; } +int native_register_managed_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower) +{ + if (!namespacedId) return -1; + + int id = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &id, &descId, &wIcon)) + return -1; + + if (!GameObjectFactory::CreateManagedTile(id, materialId, hardness, resistance, + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) + { + LogUtil::Log("[WeaveLoader] Warning: failed to create managed Tile for '%s' id=%d", namespacedId, id); + } + + ManagedBlockRegistry::Register(id); + + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) + { + CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + } + + return id; +} + +int native_register_falling_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower) +{ + int id = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &id, &descId, &wIcon)) + return -1; + + if (!GameObjectFactory::CreateFallingTile(id, materialId, hardness, resistance, + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) + { + LogUtil::Log("[WeaveLoader] Warning: failed to create falling Tile for '%s' id=%d", namespacedId, id); + } + + ManagedBlockRegistry::Register(id); + + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) + CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + + return id; +} + +int native_register_slab_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower, + int* outDoubleBlockNumericId) +{ + if (!namespacedId) return -1; + + int halfId = -1; + int descId = -1; + std::wstring wIcon; + if (!PrepareBlockRegistration(namespacedId, iconName, displayName, &halfId, &descId, &wIcon)) + return -1; + + std::string fullName = std::string(namespacedId) + "_double"; + int fullId = IdRegistry::Instance().Register(IdRegistry::Type::Block, fullName.c_str()); + if (fullId < 0) + { + LogUtil::Log("[WeaveLoader] Failed to allocate double slab block ID for '%s'", namespacedId); + return -1; + } + + if (outDoubleBlockNumericId) + *outDoubleBlockNumericId = fullId; + + if (!GameObjectFactory::CreateSlabPair(halfId, fullId, materialId, hardness, resistance, + soundType, wIcon.empty() ? nullptr : wIcon.c_str(), lightEmission, lightBlock, descId)) + { + LogUtil::Log("[WeaveLoader] Warning: failed to create slab pair for '%s' half=%d full=%d", namespacedId, halfId, fullId); + } + + if (requiredHarvestLevel >= 0 || requiredTool != 0 || acceptsRedstonePower != 0) + { + CustomBlockRegistry::Register(halfId, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + CustomBlockRegistry::Register(fullId, requiredHarvestLevel, requiredTool, acceptsRedstonePower != 0); + } + + return halfId; +} + +void native_configure_managed_block(int numericBlockId, int dropNumericBlockId, int cloneNumericBlockId) +{ + if (numericBlockId < 0) + return; + ManagedBlockRegistry::Configure(numericBlockId, dropNumericBlockId, cloneNumericBlockId); +} + int native_register_item( const char* namespacedId, int maxStackSize, @@ -460,6 +631,38 @@ int native_summon_entity_by_id(int numericEntityId, double x, double y, double z return 1; } +int native_level_has_neighbor_signal(void* levelPtr, int x, int y, int z) +{ + return (levelPtr && s_levelHasNeighborSignal && s_levelHasNeighborSignal(levelPtr, x, y, z)) ? 1 : 0; +} + +int native_level_set_tile(void* levelPtr, int x, int y, int z, int blockId, int data, int flags) +{ + return (levelPtr && s_levelSetTileAndData && s_levelSetTileAndData(levelPtr, x, y, z, blockId, data, flags)) ? 1 : 0; +} + +int native_level_schedule_tick(void* levelPtr, int x, int y, int z, int blockId, int delay) +{ + if (!levelPtr) + return 0; + if (ManagedBlockRegistry::IsManaged(blockId)) + { + GameHooks::EnqueueManagedBlockTick(levelPtr, x, y, z, blockId, delay); + return 1; + } + if (!s_levelAddToTickNextTick) + return 0; + s_levelAddToTickNextTick(levelPtr, x, y, z, blockId, delay); + return 1; +} + +int native_level_get_tile(void* levelPtr, int x, int y, int z) +{ + if (!levelPtr || !s_levelGetTile) + return -1; + return s_levelGetTile(levelPtr, x, y, z); +} + int native_summon_entity(const char* namespacedId, double x, double y, double z) { if (!namespacedId || !namespacedId[0]) diff --git a/WeaveLoaderRuntime/src/NativeExports.h b/WeaveLoaderRuntime/src/NativeExports.h index 3d8cf8a..c1a3379 100644 --- a/WeaveLoaderRuntime/src/NativeExports.h +++ b/WeaveLoaderRuntime/src/NativeExports.h @@ -1,5 +1,10 @@ #pragma once +namespace NativeExports +{ + void SetLevelInteropSymbols(void* hasNeighborSignal, void* setTileAndData, void* addToTickNextTick, void* getTile); +} + /// Exported C functions callable from C# via P/Invoke. /// All registration functions accept namespaced string IDs and delegate /// to IdRegistry for numeric ID allocation. @@ -16,7 +21,48 @@ extern "C" int lightBlock, const char* displayName, int requiredHarvestLevel, - int requiredTool); + int requiredTool, + int acceptsRedstonePower); + __declspec(dllexport) int native_register_managed_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + __declspec(dllexport) int native_register_falling_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower); + __declspec(dllexport) int native_register_slab_block( + const char* namespacedId, + int materialId, + float hardness, + float resistance, + int soundType, + const char* iconName, + float lightEmission, + int lightBlock, + const char* displayName, + int requiredHarvestLevel, + int requiredTool, + int acceptsRedstonePower, + int* outDoubleBlockNumericId); __declspec(dllexport) int native_register_item( const char* namespacedId, @@ -60,6 +106,10 @@ extern "C" int numericItemId, int harvestLevel, float destroySpeed); + __declspec(dllexport) void native_configure_managed_block( + int numericBlockId, + int dropNumericBlockId, + int cloneNumericBlockId); __declspec(dllexport) int native_configure_custom_tool_item( int numericItemId, int toolKind, @@ -99,6 +149,10 @@ extern "C" __declspec(dllexport) int native_spawn_entity_from_player_look(void* playerPtr, void* playerSharedPtr, int numericEntityId, double speed, double spawnForward, double spawnUp); __declspec(dllexport) int native_summon_entity(const char* namespacedId, double x, double y, double z); __declspec(dllexport) int native_summon_entity_by_id(int numericEntityId, double x, double y, double z); + __declspec(dllexport) int native_level_has_neighbor_signal(void* levelPtr, int x, int y, int z); + __declspec(dllexport) int native_level_set_tile(void* levelPtr, int x, int y, int z, int blockId, int data, int flags); + __declspec(dllexport) int native_level_schedule_tick(void* levelPtr, int x, int y, int z, int blockId, int delay); + __declspec(dllexport) int native_level_get_tile(void* levelPtr, int x, int y, int z); __declspec(dllexport) void native_subscribe_event( const char* eventName, diff --git a/WeaveLoaderRuntime/src/PdbParser.cpp b/WeaveLoaderRuntime/src/PdbParser.cpp index 0e5d224..311f597 100644 --- a/WeaveLoaderRuntime/src/PdbParser.cpp +++ b/WeaveLoaderRuntime/src/PdbParser.cpp @@ -282,6 +282,98 @@ uint32_t FindSymbolRVA(const char* decoratedName) return 0; } +uint32_t FindSymbolRVAByName(const char* exactName) +{ + if (!s_open || !exactName || !exactName[0]) return 0; + + // 1) Search global symbol stream for exact name matches on data/thread symbols. + { + const PDB::ArrayView records = s_globalStream->GetRecords(); + for (const PDB::HashRecord& hashRecord : records) + { + const PDB::CodeView::DBI::Record* record = s_globalStream->GetRecord(*s_symbolRecords, hashRecord); + uint16_t section = 0; + uint32_t offset = 0; + const char* name = GetGlobalSymName(record, section, offset); + + if (!name || strcmp(name, exactName) != 0) + continue; + + uint32_t rva = s_sectionStream->ConvertSectionOffsetToRVA(section, offset); + if (rva != 0) return rva; + } + } + + // 2) Search per-module symbol streams for exact name matches. + { + const PDB::ArrayView modules = s_moduleStream->GetModules(); + for (const PDB::ModuleInfoStream::Module& mod : modules) + { + if (!mod.HasSymbolStream()) + continue; + + const PDB::ModuleSymbolStream modSymStream = mod.CreateSymbolStream(*s_rawFile); + uint32_t foundRVA = 0; + + modSymStream.ForEachSymbol([&](const PDB::CodeView::DBI::Record* record) + { + if (foundRVA != 0) return; + + const char* name = nullptr; + uint16_t section = 0; + uint32_t offset = 0; + + switch (record->header.kind) + { + case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32: + name = record->data.S_LPROC32.name; + section = record->data.S_LPROC32.section; + offset = record->data.S_LPROC32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32: + name = record->data.S_GPROC32.name; + section = record->data.S_GPROC32.section; + offset = record->data.S_GPROC32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32_ID: + name = record->data.S_LPROC32_ID.name; + section = record->data.S_LPROC32_ID.section; + offset = record->data.S_LPROC32_ID.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32_ID: + name = record->data.S_GPROC32_ID.name; + section = record->data.S_GPROC32_ID.section; + offset = record->data.S_GPROC32_ID.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_LDATA32: + name = record->data.S_LDATA32.name; + section = record->data.S_LDATA32.section; + offset = record->data.S_LDATA32.offset; + break; + case PDB::CodeView::DBI::SymbolRecordKind::S_GDATA32: + name = record->data.S_GDATA32.name; + section = record->data.S_GDATA32.section; + offset = record->data.S_GDATA32.offset; + break; + default: + return; + } + + if (name && strcmp(name, exactName) == 0) + { + uint32_t rva = s_sectionStream->ConvertSectionOffsetToRVA(section, offset); + if (rva != 0) foundRVA = rva; + } + }); + + if (foundRVA != 0) + return foundRVA; + } + } + + return 0; +} + void DumpMatching(const char* substring) { if (!s_open) return; diff --git a/WeaveLoaderRuntime/src/PdbParser.h b/WeaveLoaderRuntime/src/PdbParser.h index a3901ef..1c9e560 100644 --- a/WeaveLoaderRuntime/src/PdbParser.h +++ b/WeaveLoaderRuntime/src/PdbParser.h @@ -8,6 +8,10 @@ namespace PdbParser // Returns the RVA for a decorated symbol name, or 0 on failure. uint32_t FindSymbolRVA(const char* decoratedName); + // Returns the RVA for an exact procedure/data name as stored in module/global + // symbol streams (for example "Tile::onPlace"), or 0 on failure. + uint32_t FindSymbolRVAByName(const char* exactName); + // Logs all symbols whose name contains the given substring (for debugging). void DumpMatching(const char* substring); @@ -19,6 +23,5 @@ namespace PdbParser // Returns true if found. outName receives the symbol name, outOffset // the byte distance from the symbol's start address. bool FindNameByRVA(uint32_t rva, char* outName, size_t nameSize, uint32_t* outOffset); - void Close(); } diff --git a/WeaveLoaderRuntime/src/SymbolResolver.cpp b/WeaveLoaderRuntime/src/SymbolResolver.cpp index 2db242d..380eca2 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.cpp +++ b/WeaveLoaderRuntime/src/SymbolResolver.cpp @@ -34,6 +34,29 @@ static const char* SYM_PICKAXEITEM_GETDESTROYSPEED = "?getDestroySpeed@PickaxeIt static const char* SYM_PICKAXEITEM_CANDESTROYSPECIAL = "?canDestroySpecial@PickaxeItem@@UEAA_NPEAVTile@@@Z"; static const char* SYM_SHOVELITEM_GETDESTROYSPEED = "?getDestroySpeed@ShovelItem@@UEAAMV?$shared_ptr@VItemInstance@@@std@@PEAVTile@@@Z"; static const char* SYM_SHOVELITEM_CANDESTROYSPECIAL = "?canDestroySpecial@ShovelItem@@UEAA_NPEAVTile@@@Z"; +static const char* SYM_TILE_ONPLACE = "?onPlace@Tile@@UEAAXPEAVLevel@@HHH@Z"; +static const char* SYM_TILE_NEIGHBORCHANGED = "?neighborChanged@Tile@@UEAAXPEAVLevel@@HHHH@Z"; +static const char* SYM_TILE_TICK = "?tick@Tile@@UEAAXPEAVLevel@@HHHPEAVRandom@@@Z"; +static const char* SYM_LEVEL_UPDATE_NEIGHBORS_AT = "?updateNeighborsAt@Level@@QEAAXHHHH@Z"; +static const char* SYM_SERVERLEVEL_TICKPENDINGTICKS = "?tickPendingTicks@ServerLevel@@UEAA_N_N@Z"; +static const char* SYM_LEVEL_GETTILE = "?getTile@Level@@UEAAHHHH@Z"; +static const char* SYM_LEVEL_SETDATA = "?setData@Level@@UEAA_NHHHHH_N@Z"; +static const char* SYM_TILE_GETRESOURCE = "?getResource@Tile@@UEAAHHPEAVRandom@@H@Z"; +static const char* SYM_TILE_CLONETILEID = "?cloneTileId@Tile@@UEAAHPEAVLevel@@HHH@Z"; +static const char* SYM_TILE_GETTEXTURE_FACEDATA = "?getTexture@Tile@@UEAAPEAVIcon@@HH@Z"; +static const char* SYM_STONESLAB_GETTEXTURE = "?getTexture@StoneSlabTile@@UEAAPEAVIcon@@HH@Z"; +static const char* SYM_WOODSLAB_GETTEXTURE = "?getTexture@WoodSlabTile@@UEAAPEAVIcon@@HH@Z"; +static const char* SYM_STONESLAB_GETRESOURCE = "?getResource@StoneSlabTile@@UEAAHHPEAVRandom@@H@Z"; +static const char* SYM_WOODSLAB_GETRESOURCE = "?getResource@WoodSlabTile@@UEAAHHPEAVRandom@@H@Z"; +static const char* SYM_STONESLAB_GETDESCRIPTIONID = "?getDescriptionId@StoneSlabTile@@UEAAIH@Z"; +static const char* SYM_WOODSLAB_GETDESCRIPTIONID = "?getDescriptionId@WoodSlabTile@@UEAAIH@Z"; +static const char* SYM_STONESLAB_GETAUXNAME = "?getAuxName@StoneSlabTile@@UEAAHH@Z"; +static const char* SYM_WOODSLAB_GETAUXNAME = "?getAuxName@WoodSlabTile@@UEAAHH@Z"; +static const char* SYM_STONESLAB_REGISTERICONS = "?registerIcons@StoneSlabTile@@UEAAXPEAVIconRegister@@@Z"; +static const char* SYM_WOODSLAB_REGISTERICONS = "?registerIcons@WoodSlabTile@@UEAAXPEAVIconRegister@@@Z"; +static const char* SYM_STONESLABITEM_GETICON = "?getIcon@StoneSlabTileItem@@UEAAPEAVIcon@@H@Z"; +static const char* SYM_STONESLABITEM_GETDESCRIPTIONID = "?getDescriptionId@StoneSlabTileItem@@UEAAIV?$shared_ptr@VItemInstance@@@std@@@Z"; +static const char* SYM_HALFSLAB_CLONETILEID = "?cloneTileId@HalfSlabTile@@UEAAHPEAVLevel@@HHH@Z"; static const char* SYM_PLAYER_CANDESTROY = "?canDestroy@Player@@QEAA_NPEAVTile@@@Z"; static const char* SYM_SERVER_PLAYER_GAMEMODE_USEITEM = "?useItem@ServerPlayerGameMode@@QEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z"; static const char* SYM_MULTI_PLAYER_GAMEMODE_USEITEM = "?useItem@MultiPlayerGameMode@@UEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z"; @@ -61,6 +84,23 @@ static const char* SYM_ABSTRACTCONTAINERMENU_BROADCASTCHANGES = "?broadcastChang static const char* SYM_TEXATLAS_BLOCKS = "?LOCATION_BLOCKS@TextureAtlas@@2VResourceLocation@@A"; static const char* SYM_TEXATLAS_ITEMS = "?LOCATION_ITEMS@TextureAtlas@@2VResourceLocation@@A"; static const char* SYM_TILE_TILES = "?tiles@Tile@@2PEAPEAV1@EA"; +static const char* SYM_LEVEL_HASNEIGHBORSIGNAL = "?hasNeighborSignal@Level@@QEAA_NHHH@Z"; +static const char* SYM_LEVEL_SETTILEANDDATA = "?setTileAndData@Level@@UEAA_NHHHHHH@Z"; +static const char* SYM_LEVEL_ADDTOTICKNEXTTICK = "?addToTickNextTick@Level@@UEAAXHHHHH@Z"; +static const char* SYM_SERVERLEVEL_ADDTOTICKNEXTTICK = "?addToTickNextTick@ServerLevel@@UEAAXHHHHH@Z"; + +static void* ResolveExactProcName(uintptr_t moduleBase, const char* exactName) +{ + uint32_t rva = PdbParser::FindSymbolRVAByName(exactName); + if (rva == 0) + return nullptr; + return reinterpret_cast(moduleBase + rva); +} + +static bool IsStub31000(uintptr_t moduleBase, void* ptr) +{ + return ptr == reinterpret_cast(moduleBase + 0x31000u); +} bool SymbolResolver::Initialize() { @@ -147,6 +187,29 @@ bool SymbolResolver::ResolveGameFunctions() pPickaxeItemCanDestroySpecial = Resolve(SYM_PICKAXEITEM_CANDESTROYSPECIAL); pShovelItemGetDestroySpeed = Resolve(SYM_SHOVELITEM_GETDESTROYSPEED); pShovelItemCanDestroySpecial = Resolve(SYM_SHOVELITEM_CANDESTROYSPECIAL); + pTileOnPlace = Resolve(SYM_TILE_ONPLACE); + pTileNeighborChanged = Resolve(SYM_TILE_NEIGHBORCHANGED); + pTileTick = Resolve(SYM_TILE_TICK); + pLevelUpdateNeighborsAt = Resolve(SYM_LEVEL_UPDATE_NEIGHBORS_AT); + pServerLevelTickPendingTicks = Resolve(SYM_SERVERLEVEL_TICKPENDINGTICKS); + pLevelGetTile = Resolve(SYM_LEVEL_GETTILE); + pLevelSetData = Resolve(SYM_LEVEL_SETDATA); + pTileGetResource = Resolve(SYM_TILE_GETRESOURCE); + pTileCloneTileId = Resolve(SYM_TILE_CLONETILEID); + pTileGetTextureFaceData = Resolve(SYM_TILE_GETTEXTURE_FACEDATA); + pStoneSlabGetTexture = Resolve(SYM_STONESLAB_GETTEXTURE); + pWoodSlabGetTexture = Resolve(SYM_WOODSLAB_GETTEXTURE); + pStoneSlabGetResource = Resolve(SYM_STONESLAB_GETRESOURCE); + pWoodSlabGetResource = Resolve(SYM_WOODSLAB_GETRESOURCE); + pStoneSlabGetDescriptionId = Resolve(SYM_STONESLAB_GETDESCRIPTIONID); + pWoodSlabGetDescriptionId = Resolve(SYM_WOODSLAB_GETDESCRIPTIONID); + pStoneSlabGetAuxName = Resolve(SYM_STONESLAB_GETAUXNAME); + pWoodSlabGetAuxName = Resolve(SYM_WOODSLAB_GETAUXNAME); + pStoneSlabRegisterIcons = Resolve(SYM_STONESLAB_REGISTERICONS); + pWoodSlabRegisterIcons = Resolve(SYM_WOODSLAB_REGISTERICONS); + pStoneSlabItemGetIcon = Resolve(SYM_STONESLABITEM_GETICON); + pStoneSlabItemGetDescriptionId = Resolve(SYM_STONESLABITEM_GETDESCRIPTIONID); + pHalfSlabCloneTileId = Resolve(SYM_HALFSLAB_CLONETILEID); pPlayerCanDestroy = Resolve(SYM_PLAYER_CANDESTROY); pServerPlayerGameModeUseItem = Resolve(SYM_SERVER_PLAYER_GAMEMODE_USEITEM); pMultiPlayerGameModeUseItem = Resolve(SYM_MULTI_PLAYER_GAMEMODE_USEITEM); @@ -176,6 +239,24 @@ bool SymbolResolver::ResolveGameFunctions() pTextureAtlasLocationBlocks = Resolve(SYM_TEXATLAS_BLOCKS); pTextureAtlasLocationItems = Resolve(SYM_TEXATLAS_ITEMS); pTileTiles = Resolve(SYM_TILE_TILES); + pLevelHasNeighborSignal = Resolve(SYM_LEVEL_HASNEIGHBORSIGNAL); + pLevelSetTileAndData = Resolve(SYM_LEVEL_SETTILEANDDATA); + pLevelAddToTickNextTick = Resolve(SYM_LEVEL_ADDTOTICKNEXTTICK); + pServerLevelAddToTickNextTick = Resolve(SYM_SERVERLEVEL_ADDTOTICKNEXTTICK); + + // Some public symbols in this build resolve to stub bodies. Prefer exact + // module procedure names from the PDB where those exist. + if (pShovelItemGetDestroySpeed == nullptr) + pShovelItemGetDestroySpeed = ResolveExactProcName(m_moduleBase, "DiggerItem::getDestroySpeed"); + if (IsStub31000(m_moduleBase, pTileOnPlace)) + pTileOnPlace = ResolveExactProcName(m_moduleBase, "Tile::onPlace"); + if (IsStub31000(m_moduleBase, pTileNeighborChanged)) + pTileNeighborChanged = ResolveExactProcName(m_moduleBase, "Tile::neighborChanged"); + if (IsStub31000(m_moduleBase, pTileTick)) + pTileTick = ResolveExactProcName(m_moduleBase, "Tile::tick"); + if (IsStub31000(m_moduleBase, pWoodSlabRegisterIcons)) + pWoodSlabRegisterIcons = ResolveExactProcName(m_moduleBase, "WoodSlabTile::registerIcons"); + if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140.dll"), SYM_OPERATOR_NEW); if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140d.dll"), SYM_OPERATOR_NEW); if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandle(nullptr), SYM_OPERATOR_NEW); @@ -218,6 +299,29 @@ bool SymbolResolver::ResolveGameFunctions() logSym("PickaxeItem::canDestroySpecial", pPickaxeItemCanDestroySpecial); logSym("ShovelItem::getDestroySpeed", pShovelItemGetDestroySpeed); logSym("ShovelItem::canDestroySpecial", pShovelItemCanDestroySpecial); + logSym("Tile::onPlace", pTileOnPlace); + logSym("Tile::neighborChanged", pTileNeighborChanged); + logSym("Tile::tick", pTileTick); + logSym("Level::updateNeighborsAt", pLevelUpdateNeighborsAt); + logSym("ServerLevel::tickPendingTicks", pServerLevelTickPendingTicks); + logSym("Level::getTile", pLevelGetTile); + logSym("Level::setData", pLevelSetData); + logSym("Tile::getResource", pTileGetResource); + logSym("Tile::cloneTileId", pTileCloneTileId); + logSym("Tile::getTexture(face,data)", pTileGetTextureFaceData); + logSym("StoneSlabTile::getTexture", pStoneSlabGetTexture); + logSym("WoodSlabTile::getTexture", pWoodSlabGetTexture); + logSym("StoneSlabTile::getResource", pStoneSlabGetResource); + logSym("WoodSlabTile::getResource", pWoodSlabGetResource); + logSym("StoneSlabTile::getDescriptionId", pStoneSlabGetDescriptionId); + logSym("WoodSlabTile::getDescriptionId", pWoodSlabGetDescriptionId); + logSym("StoneSlabTile::getAuxName", pStoneSlabGetAuxName); + logSym("WoodSlabTile::getAuxName", pWoodSlabGetAuxName); + logSym("StoneSlabTile::registerIcons", pStoneSlabRegisterIcons); + logSym("WoodSlabTile::registerIcons", pWoodSlabRegisterIcons); + logSym("StoneSlabTileItem::getIcon", pStoneSlabItemGetIcon); + logSym("StoneSlabTileItem::getDescriptionId", pStoneSlabItemGetDescriptionId); + logSym("HalfSlabTile::cloneTileId", pHalfSlabCloneTileId); logSym("Player::canDestroy", pPlayerCanDestroy); logSym("ServerPlayerGameMode::useItem", pServerPlayerGameModeUseItem); logSym("MultiPlayerGameMode::useItem", pMultiPlayerGameModeUseItem); @@ -243,6 +347,10 @@ bool SymbolResolver::ResolveGameFunctions() logSym("TextureAtlas::LOCATION_BLOCKS", pTextureAtlasLocationBlocks); logSym("TextureAtlas::LOCATION_ITEMS", pTextureAtlasLocationItems); logSym("Tile::tiles", pTileTiles); + logSym("Level::hasNeighborSignal", pLevelHasNeighborSignal); + logSym("Level::setTileAndData", pLevelSetTileAndData); + logSym("Level::addToTickNextTick", pLevelAddToTickNextTick); + logSym("ServerLevel::addToTickNextTick", pServerLevelAddToTickNextTick); bool ok = pRunStaticCtors && pMinecraftTick && pMinecraftInit; if (ok) diff --git a/WeaveLoaderRuntime/src/SymbolResolver.h b/WeaveLoaderRuntime/src/SymbolResolver.h index ddfe491..e988eaa 100644 --- a/WeaveLoaderRuntime/src/SymbolResolver.h +++ b/WeaveLoaderRuntime/src/SymbolResolver.h @@ -39,6 +39,29 @@ public: void* pPickaxeItemCanDestroySpecial = nullptr; // PickaxeItem::canDestroySpecial(Tile*) void* pShovelItemGetDestroySpeed = nullptr; // ShovelItem::getDestroySpeed(shared_ptr,Tile*) void* pShovelItemCanDestroySpecial = nullptr; // ShovelItem::canDestroySpecial(Tile*) + void* pTileOnPlace = nullptr; // Tile::onPlace(Level*,int,int,int) + void* pTileNeighborChanged = nullptr; // Tile::neighborChanged(Level*,int,int,int,int) + void* pTileTick = nullptr; // Tile::tick(Level*,int,int,int,Random*) + void* pLevelUpdateNeighborsAt = nullptr; // Level::updateNeighborsAt(int,int,int,int) + void* pServerLevelTickPendingTicks = nullptr; // ServerLevel::tickPendingTicks(bool) + void* pLevelGetTile = nullptr; // Level::getTile(int,int,int) + void* pLevelSetData = nullptr; // Level::setData(int,int,int,int,int,bool) + void* pTileGetResource = nullptr; // Tile::getResource(int,Random*,int) + void* pTileCloneTileId = nullptr; // Tile::cloneTileId(Level*,int,int,int) + void* pTileGetTextureFaceData = nullptr; // Tile::getTexture(int,int) + void* pStoneSlabGetTexture = nullptr; // StoneSlabTile::getTexture(int,int) + void* pWoodSlabGetTexture = nullptr; // WoodSlabTile::getTexture(int,int) + void* pStoneSlabGetResource = nullptr; // StoneSlabTile::getResource(int,Random*,int) + void* pWoodSlabGetResource = nullptr; // WoodSlabTile::getResource(int,Random*,int) + void* pStoneSlabGetDescriptionId = nullptr; // StoneSlabTile::getDescriptionId(int) + void* pWoodSlabGetDescriptionId = nullptr; // WoodSlabTile::getDescriptionId(int) + void* pStoneSlabGetAuxName = nullptr; // StoneSlabTile::getAuxName(int) + void* pWoodSlabGetAuxName = nullptr; // WoodSlabTile::getAuxName(int) + void* pStoneSlabRegisterIcons = nullptr; // StoneSlabTile::registerIcons(IconRegister*) + void* pWoodSlabRegisterIcons = nullptr; // WoodSlabTile::registerIcons(IconRegister*) + void* pStoneSlabItemGetIcon = nullptr; // StoneSlabTileItem::getIcon(int) + void* pStoneSlabItemGetDescriptionId = nullptr; // StoneSlabTileItem::getDescriptionId(shared_ptr) + void* pHalfSlabCloneTileId = nullptr; // HalfSlabTile::cloneTileId(Level*,int,int,int) void* pPlayerCanDestroy = nullptr; // Player::canDestroy(Tile*) void* pServerPlayerGameModeUseItem = nullptr; // ServerPlayerGameMode::useItem(shared_ptr,Level*,shared_ptr,bool) void* pMultiPlayerGameModeUseItem = nullptr; // MultiPlayerGameMode::useItem(shared_ptr,Level*,shared_ptr,bool) @@ -64,6 +87,10 @@ public: void* pTextureAtlasLocationBlocks = nullptr; // TextureAtlas::LOCATION_BLOCKS void* pTextureAtlasLocationItems = nullptr; // TextureAtlas::LOCATION_ITEMS void* pTileTiles = nullptr; // Tile::tiles (Tile*[]) for tile id lookup + void* pLevelHasNeighborSignal = nullptr; // Level::hasNeighborSignal(int,int,int) + void* pLevelSetTileAndData = nullptr; // Level::setTileAndData(int,int,int,int,int,int) + void* pLevelAddToTickNextTick = nullptr; // Level::addToTickNextTick(int,int,int,int,int) + void* pServerLevelAddToTickNextTick = nullptr; // ServerLevel::addToTickNextTick(int,int,int,int,int) private: uintptr_t m_moduleBase = 0; diff --git a/WeaveLoaderRuntime/src/dllmain.cpp b/WeaveLoaderRuntime/src/dllmain.cpp index 6dceac3..9d75989 100644 --- a/WeaveLoaderRuntime/src/dllmain.cpp +++ b/WeaveLoaderRuntime/src/dllmain.cpp @@ -25,6 +25,9 @@ static std::string GetDllDirectory(HMODULE hModule) DWORD WINAPI InitThread(LPVOID lpParam) { LogUtil::Log("[WeaveLoader] InitThread started (module=%p)", g_hModule); +#ifdef WEAVELOADER_DEBUG_BUILD + LogUtil::Log("[WeaveLoader] Loader is running in debug mode"); +#endif std::string baseDir = GetDllDirectory(g_hModule); LogUtil::Log("[WeaveLoader] Runtime DLL directory: %s", baseDir.c_str());