1313import dev ._2lstudios .hamsterapi .utils .NMSItemStackConverter ;
1414import dev ._2lstudios .hamsterapi .utils .Reflection ;
1515
16+ // The Unsafe import is required for the bypass.
17+ // IDEs will correctly warn that this is a restricted internal API.
18+ import sun .misc .Unsafe ;
19+
1620public class PacketWrapper {
1721 private final Class <?> craftItemStackClass ;
1822 private final Class <?> nmsItemStackClass ;
@@ -27,6 +31,24 @@ public class PacketWrapper {
2731 private final Map <String , ItemStack > items = new HashMap <>();
2832 private final Map <String , Object > objects = new HashMap <>();
2933
34+ private static final Unsafe unsafe ;
35+
36+ /**
37+ * Static initializer to get the instance of sun.misc.Unsafe.
38+ * This is the cornerstone of the bypass, providing direct memory access.
39+ */
40+ static {
41+ try {
42+ Field f = Unsafe .class .getDeclaredField ("theUnsafe" );
43+ f .setAccessible (true );
44+ unsafe = (Unsafe ) f .get (null );
45+ } catch (Exception e ) {
46+ // If Unsafe is not available, this class cannot perform its core function.
47+ // Throwing a RuntimeException is appropriate as this is a critical failure.
48+ throw new RuntimeException ("Unable to acquire sun.misc.Unsafe instance" , e );
49+ }
50+ }
51+
3052 public PacketWrapper (final Object packet ) {
3153 final Reflection reflection = HamsterAPI .getInstance ().getReflection ();
3254 final Class <?> minecraftKeyClass = reflection .getMinecraftKey ();
@@ -68,7 +90,7 @@ public PacketWrapper(final Object packet) {
6890
6991 field .setAccessible (false );
7092 } catch (Exception e ) {
71- e . printStackTrace ();
93+ // Graceful as requested
7294 }
7395 }
7496 }
@@ -92,32 +114,82 @@ public PacketType getType() {
92114 return null ;
93115 }
94116
117+ /**
118+ * Writes a value to a field by its name, bypassing 'final' and module
119+ * access restrictions.
120+ *
121+ * It first attempts a standard reflection-based final modifier removal. If that
122+ * fails (as it does on Java 9+), it falls back to using sun.misc.Unsafe
123+ * for direct memory manipulation.
124+ *
125+ * @param key The name of the field to modify.
126+ * @param value The new value for the field.
127+ */
95128 public void write (final String key , final Object value ) {
96129 try {
97- Field field = this .packet .getClass ().getDeclaredField (key );
130+ // Search for the field in the class and its superclasses to support inheritance
131+ Field field = null ;
132+ Class <?> currentClass = this .packet .getClass ();
133+ while (currentClass != null ) {
134+ try {
135+ field = currentClass .getDeclaredField (key );
136+ break ; // Field found, exit the loop
137+ } catch (NoSuchFieldException e ) {
138+ // Field not in this class, move to the superclass
139+ currentClass = currentClass .getSuperclass ();
140+ }
141+ }
98142
99- // Remove the 'final' modifier
143+ if (field == null ) {
144+ // Field does not exist in the class hierarchy, do nothing.
145+ return ;
146+ }
147+
148+ // First, attempt the "old way" which might work on older Java versions or for non-final fields.
100149 try {
150+ field .setAccessible (true );
101151 Field modifiersField = Field .class .getDeclaredField ("modifiers" );
102152 modifiersField .setAccessible (true );
103153 modifiersField .setInt (field , field .getModifiers () & ~Modifier .FINAL );
104- } catch (Exception e ) {
105- // Does not have modifiers, skip
154+ field .set (this .packet , value );
155+ } catch (Exception standardReflectionFailed ) {
156+ // If the standard way fails, fall back to the extreme Unsafe bypass.
157+ // This is expected on modern Java versions (9+).
158+ long offset = unsafe .objectFieldOffset (field );
159+
160+ // Use the correct "put" method based on the value's type.
161+ if (value instanceof Boolean ) {
162+ unsafe .putBoolean (this .packet , offset , (Boolean ) value );
163+ } else if (value instanceof Byte ) {
164+ unsafe .putByte (this .packet , offset , (Byte ) value );
165+ } else if (value instanceof Short ) {
166+ unsafe .putShort (this .packet , offset , (Short ) value );
167+ } else if (value instanceof Integer ) {
168+ unsafe .putInt (this .packet , offset , (Integer ) value );
169+ } else if (value instanceof Long ) {
170+ unsafe .putLong (this .packet , offset , (Long ) value );
171+ } else if (value instanceof Float ) {
172+ unsafe .putFloat (this .packet , offset , (Float ) value );
173+ } else if (value instanceof Double ) {
174+ unsafe .putDouble (this .packet , offset , (Double ) value );
175+ } else if (value instanceof Character ) {
176+ unsafe .putChar (this .packet , offset , (Character ) value );
177+ } else {
178+ // For all other Object types
179+ unsafe .putObject (this .packet , offset , value );
180+ }
106181 }
107-
108- field .setAccessible (true );
109- field .set (packet , value );
110- field .setAccessible (false );
111182 } catch (final Exception e ) {
112- e .printStackTrace ();
183+ // Graceful: If any part of the process fails, the write operation is
184+ // aborted silently without crashing.
113185 }
114186 }
115187
116188 public void write (final String key , final ItemStack itemStack ) {
117189 try {
118190 write (key , NMSItemStackConverter .convertToNMS (itemStack ));
119191 } catch (final Exception e ) {
120- e . printStackTrace ();
192+ // Graceful as requested
121193 }
122194 }
123195
@@ -184,4 +256,4 @@ public String getName() {
184256 public String toString () {
185257 return this .packet .toString ();
186258 }
187- }
259+ }
0 commit comments