1+ package net .imprex .zip .nms .v1_21_R5 ;
2+
3+ import java .io .ByteArrayInputStream ;
4+ import java .io .ByteArrayOutputStream ;
5+ import java .io .IOException ;
6+ import java .io .InputStreamReader ;
7+ import java .io .OutputStreamWriter ;
8+ import java .lang .reflect .InvocationTargetException ;
9+ import java .lang .reflect .Method ;
10+ import java .nio .charset .StandardCharsets ;
11+ import java .util .ArrayList ;
12+ import java .util .Collections ;
13+ import java .util .List ;
14+ import java .util .UUID ;
15+ import java .util .function .BiConsumer ;
16+
17+ import org .bukkit .Material ;
18+ import org .bukkit .craftbukkit .v1_21_R5 .inventory .CraftItemStack ;
19+ import org .bukkit .inventory .ItemStack ;
20+ import org .bukkit .inventory .meta .SkullMeta ;
21+
22+ import com .google .gson .Gson ;
23+ import com .google .gson .JsonArray ;
24+ import com .google .gson .JsonElement ;
25+ import com .google .gson .JsonObject ;
26+ import com .mojang .authlib .GameProfile ;
27+ import com .mojang .authlib .properties .Property ;
28+ import com .mojang .serialization .DataResult ;
29+ import com .mojang .serialization .Dynamic ;
30+ import com .mojang .serialization .DynamicOps ;
31+ import com .mojang .serialization .JsonOps ;
32+
33+ import net .imprex .zip .common .ReflectionUtil ;
34+ import net .imprex .zip .nms .api .NmsManager ;
35+ import net .minecraft .SharedConstants ;
36+ import net .minecraft .core .RegistryAccess ;
37+ import net .minecraft .nbt .CompoundTag ;
38+ import net .minecraft .nbt .ListTag ;
39+ import net .minecraft .nbt .NbtAccounter ;
40+ import net .minecraft .nbt .NbtIo ;
41+ import net .minecraft .nbt .NbtOps ;
42+ import net .minecraft .nbt .Tag ;
43+ import net .minecraft .server .MinecraftServer ;
44+ import net .minecraft .util .datafix .DataFixers ;
45+ import net .minecraft .util .datafix .fixes .References ;
46+ import net .minecraft .world .item .component .ResolvableProfile ;
47+
48+ public class ZipNmsManager implements NmsManager {
49+
50+ private static final BiConsumer <SkullMeta , GameProfile > SET_PROFILE ;
51+
52+ @ SuppressWarnings ("deprecation" )
53+ private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer .getServer ().registryAccess ();
54+
55+ private static final CompoundTag NBT_EMPTY_ITEMSTACK = new CompoundTag ();
56+
57+ private static final int DATA_VERSION = SharedConstants .getCurrentVersion ().dataVersion ().version ();
58+
59+ private static final Gson GSON = new Gson ();
60+
61+ private static final DynamicOps <Tag > DYNAMIC_OPS_NBT = DEFAULT_REGISTRY .createSerializationContext (NbtOps .INSTANCE );
62+ private static final DynamicOps <JsonElement > DYNAMIC_OPS_JSON = DEFAULT_REGISTRY .createSerializationContext (JsonOps .INSTANCE );
63+
64+ static {
65+ NBT_EMPTY_ITEMSTACK .putString ("id" , "minecraft:air" );
66+
67+ BiConsumer <SkullMeta , GameProfile > setProfile = (meta , profile ) -> {
68+ throw new NullPointerException ("Unable to find 'setProfile' method!" );
69+ };
70+
71+ Class <?> craftMetaSkullClass = new ItemStack (Material .PLAYER_HEAD )
72+ .getItemMeta ()
73+ .getClass ();
74+
75+ Method setResolvableProfileMethod = ReflectionUtil .searchMethod (craftMetaSkullClass , void .class , ResolvableProfile .class );
76+ if (setResolvableProfileMethod != null ) {
77+ setProfile = (meta , profile ) -> {
78+ try {
79+ setResolvableProfileMethod .invoke (meta , new ResolvableProfile (profile ));
80+ } catch (IllegalAccessException | InvocationTargetException e ) {
81+ e .printStackTrace ();
82+ }
83+ };
84+ } else {
85+ Method setProfileMethod = ReflectionUtil .searchMethod (craftMetaSkullClass , void .class , GameProfile .class );
86+ if (setProfileMethod != null ) {
87+ setProfile = (meta , profile ) -> {
88+ try {
89+ setProfileMethod .invoke (meta , profile );
90+ } catch (IllegalAccessException | InvocationTargetException e ) {
91+ e .printStackTrace ();
92+ }
93+ };
94+ }
95+ }
96+
97+ SET_PROFILE = setProfile ;
98+ }
99+
100+ public byte [] nbtToBinary (CompoundTag compound ) {
101+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream ()) {
102+ NbtIo .writeCompressed (compound , outputStream );
103+ return outputStream .toByteArray ();
104+ } catch (Exception e ) {
105+ e .printStackTrace ();
106+ }
107+ return null ;
108+ }
109+
110+ public CompoundTag binaryToNBT (byte [] binary ) {
111+ try (ByteArrayInputStream inputStream = new ByteArrayInputStream (binary )) {
112+ return NbtIo .readCompressed (inputStream , NbtAccounter .unlimitedHeap ());
113+ } catch (Exception e ) {
114+ e .printStackTrace ();
115+ }
116+ return new CompoundTag ();
117+ }
118+
119+ @ Override
120+ public byte [] itemstackToBinary (ItemStack [] items ) {
121+ JsonArray jsonItems = new JsonArray ();
122+ for (int slot = 0 ; slot < items .length ; slot ++) {
123+ ItemStack item = items [slot ];
124+ if (item == null || item .getType () == Material .AIR ) {
125+ continue ;
126+ }
127+ net .minecraft .world .item .ItemStack minecraftItem = CraftItemStack .asNMSCopy (item );
128+
129+ DataResult <JsonElement > result = net .minecraft .world .item .ItemStack .CODEC .encodeStart (DYNAMIC_OPS_JSON , minecraftItem );
130+ JsonObject resultJson = result .getOrThrow ().getAsJsonObject ();
131+
132+ resultJson .addProperty ("Slot" , slot );
133+ jsonItems .add (resultJson );
134+ }
135+
136+ JsonObject outputJson = new JsonObject ();
137+ outputJson .addProperty ("ZIPVersion" , 2 );
138+ outputJson .addProperty ("DataVersion" , DATA_VERSION );
139+ outputJson .addProperty ("ContainerSize" , items .length );
140+ outputJson .add ("Items" , jsonItems );
141+
142+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
143+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter (outputStream , StandardCharsets .UTF_8 )) {
144+ GSON .toJson (outputJson , outputStreamWriter );
145+ return outputStream .toByteArray ();
146+ } catch (IOException e ) {
147+ throw new IllegalStateException ("Unable to convert ItemStack into json" , e );
148+ }
149+ }
150+
151+ @ Override
152+ public List <ItemStack > binaryToItemStack (byte [] binary ) {
153+ try {
154+ // parse new version (JSON)
155+ JsonObject inputJson ;
156+ try (ByteArrayInputStream inputStream = new ByteArrayInputStream (binary );
157+ InputStreamReader inputStreamReader = new InputStreamReader (inputStream , StandardCharsets .UTF_8 )) {
158+ inputJson = GSON .fromJson (inputStreamReader , JsonObject .class );
159+ }
160+
161+ // check if current version the same
162+ if (inputJson .get ("ZIPVersion" ).getAsInt () != 2 ) {
163+ throw new IllegalStateException ("Unable to convert binary to itemstack because zip version is missmatching" );
164+ }
165+
166+ int dataVersion = inputJson .get ("DataVersion" ).getAsInt ();
167+ int containerSize = inputJson .get ("ContainerSize" ).getAsInt ();
168+
169+ // convert json into bukkit item
170+ List <ItemStack > items = new ArrayList <>(containerSize );
171+ JsonArray jsonItems = inputJson .get ("Items" ).getAsJsonArray ();
172+ for (JsonElement item : jsonItems ) {
173+ Dynamic <JsonElement > dynamicItem = new Dynamic <>(JsonOps .INSTANCE , item );
174+ Dynamic <JsonElement > dynamicItemFixed = DataFixers .getDataFixer ().update (References .ITEM_STACK , dynamicItem , dataVersion , DATA_VERSION );
175+ net .minecraft .world .item .ItemStack minecraftItem = net .minecraft .world .item .ItemStack .CODEC
176+ .parse (DYNAMIC_OPS_JSON , dynamicItemFixed .getValue ())
177+ .getOrThrow ();
178+
179+ ItemStack bukkitItem = CraftItemStack .asCraftMirror (minecraftItem );
180+ int slot = item .getAsJsonObject ().get ("Slot" ).getAsInt ();
181+ items .set (slot , bukkitItem );
182+ }
183+
184+ return items ;
185+ } catch (Exception e ) {
186+ // parse outdated version (NBT)
187+ CompoundTag compound = binaryToNBT (binary );
188+ ListTag list = compound .getListOrEmpty ("i" );
189+ if (list .isEmpty ()) {
190+ return Collections .emptyList ();
191+ }
192+
193+ List <ItemStack > items = new ArrayList <>();
194+ for (Tag base : list ) {
195+ if (base instanceof CompoundTag itemTag ) {
196+ String itemType = itemTag .getString ("id" ).orElse ("" );
197+ if (itemType .equals ("minecraft:air" )) {
198+ items .add (new ItemStack (Material .AIR ));
199+ } else {
200+ Dynamic <Tag > dynamicItem = new Dynamic <>(NbtOps .INSTANCE , itemTag );
201+ net .minecraft .world .item .ItemStack minecraftItem = net .minecraft .world .item .ItemStack .CODEC
202+ .parse (DYNAMIC_OPS_NBT , dynamicItem .getValue ())
203+ .getOrThrow ();
204+
205+ ItemStack bukkitItem = CraftItemStack .asCraftMirror (minecraftItem );
206+ items .add (bukkitItem );
207+ }
208+ }
209+ }
210+ return items ;
211+ }
212+ }
213+
214+ @ Override
215+ public void setSkullProfile (SkullMeta meta , String texture ) {
216+ try {
217+ GameProfile gameProfile = new GameProfile (UUID .randomUUID (), "" );
218+ gameProfile .getProperties ().put ("textures" , new Property ("textures" , texture ));
219+
220+ SET_PROFILE .accept (meta , gameProfile );
221+ } catch (Exception e ) {
222+ e .printStackTrace ();
223+ }
224+ }
225+
226+ @ Override
227+ public boolean isAir (Material material ) {
228+ return material == null || material == Material .AIR ;
229+ }
230+ }
0 commit comments