Last active
August 28, 2023 19:12
-
-
Save CaptainBern/73fa15cb0359fda67691 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package test; | |
import com.captainbern.reflection.ClassTemplate; | |
import com.captainbern.reflection.Reflection; | |
import com.captainbern.reflection.accessor.FieldAccessor; | |
import com.captainbern.reflection.accessor.MethodAccessor; | |
import net.minecraft.server.v1_7_R3.ChunkCoordIntPair; | |
import net.minecraft.server.v1_7_R3.PacketPlayOutMultiBlockChange; | |
import org.bukkit.Chunk; | |
import org.bukkit.Location; | |
import org.bukkit.craftbukkit.v1_7_R3.CraftChunk; | |
import org.spigotmc.*; | |
import java.io.ByteArrayOutputStream; | |
import java.io.DataOutputStream; | |
import java.io.IOException; | |
/** | |
* @author CaptainBern | |
*/ | |
public class MultiBlockChanger { | |
public static short toIndex(int x, int y, int z) { | |
return (short) ((x << 12) | (z << 8) | (y)); | |
} | |
public static int[] toCoords(short index) { | |
int x = index >> 12 & 0xF; | |
int y = index >> 8 & 0xF; | |
int z = index & 0xFF; | |
return new int[] {x, y, z}; | |
} | |
private static FieldAccessor<ChunkCoordIntPair> CHUNK_COORD_ACCESSOR; | |
private static FieldAccessor<byte[]> BULK_ACCESSOR; | |
private static FieldAccessor<Integer> SIZE_ACCESSOR; | |
// Spigot 1.8 proto hack fields | |
private static boolean IS_SPIGOT_PROTO = false; | |
private static FieldAccessor<net.minecraft.server.v1_7_R3.Chunk> CHUNK_ACCESSOR; | |
private static FieldAccessor<short[]> BLOCK_LOCS_ACCESSOR; | |
private static FieldAccessor<int[]> BLOCK_IDS_ACCESSOR; | |
private static MethodAccessor<Integer> GET_CORRECT_DATA_ACCESSOR; | |
public static PacketPlayOutMultiBlockChange createBlockChange(Chunk chunk, int newId, int data, Location... locations) throws IOException { | |
initializeFields(); | |
PacketPlayOutMultiBlockChange packet = new PacketPlayOutMultiBlockChange(); | |
if (IS_SPIGOT_PROTO) | |
CHUNK_ACCESSOR.set(packet, ((CraftChunk) chunk).getHandle()); | |
ChunkCoordIntPair intPair = new ChunkCoordIntPair(chunk.getX(), chunk.getZ()); | |
int numberOfRecords = locations.length; | |
CHUNK_COORD_ACCESSOR.set(packet, intPair); | |
SIZE_ACCESSOR.set(packet, numberOfRecords); | |
createBulk(packet, newId, data, locations); | |
return packet; | |
} | |
private static void createBulk(PacketPlayOutMultiBlockChange packet, int newId, int data, Location... locations) throws IOException { | |
ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(locations.length); | |
DataOutputStream dataoutputstream = new DataOutputStream(bytearrayoutputstream); | |
// the spigot proto hack uses the block-loc field. (Block loc -> index) | |
short[] blockIndexes = new short[locations.length]; // Spigot proto hack needs this | |
int[] blocks = new int[locations.length]; // also this | |
for (int i = 0; i < locations.length; i++) { | |
Location location = locations[i]; | |
blockIndexes[i] = toIndex(location.getBlockX(), location.getBlockY(), location.getBlockZ()); | |
if (IS_SPIGOT_PROTO) { | |
data = GET_CORRECT_DATA_ACCESSOR.invokeStatic(newId, data); | |
} | |
int id = (((newId) & 0xFFF) << 4 | data & 0xF); | |
blocks[i] = id; | |
dataoutputstream.writeShort(blockIndexes[i]); | |
dataoutputstream.writeShort(id); | |
} | |
int size = locations.length * 4; | |
byte[] bulk = bytearrayoutputstream.toByteArray(); | |
if (bulk.length != size) { | |
throw new RuntimeException("Expected length: \'" + size + "\' doesn't match the generated length: \'" + bulk.length + "\'"); | |
} | |
BULK_ACCESSOR.set(packet, bulk); | |
if (IS_SPIGOT_PROTO) { | |
BLOCK_LOCS_ACCESSOR.set(packet, blockIndexes); | |
BLOCK_IDS_ACCESSOR.set(packet, blocks); | |
} | |
} | |
private static void initializeFields() { | |
if (CHUNK_COORD_ACCESSOR != null && BULK_ACCESSOR != null && SIZE_ACCESSOR != null) | |
return; | |
Class<PacketPlayOutMultiBlockChange> clazz = PacketPlayOutMultiBlockChange.class; | |
ClassTemplate<PacketPlayOutMultiBlockChange> template = new Reflection().reflect(clazz); | |
try { | |
CHUNK_COORD_ACCESSOR = template.getSafeFieldByType(ChunkCoordIntPair.class).getAccessor(); | |
BULK_ACCESSOR = template.getSafeFieldByType(byte[].class).getAccessor(); | |
SIZE_ACCESSOR = template.getSafeFieldByNameAndType("d", int.class).getAccessor(); | |
if (template.getSafeFieldByType(short[].class) != null && template.getSafeFieldByType(int[].class) != null | |
&& template.getSafeFieldByType(net.minecraft.server.v1_7_R3.Chunk.class) != null) { | |
// We're on a spigot proto hack | |
CHUNK_ACCESSOR = template.getSafeFieldByType(net.minecraft.server.v1_7_R3.Chunk.class).getAccessor(); | |
BLOCK_LOCS_ACCESSOR = template.getSafeFieldByType(short[].class).getAccessor(); | |
BLOCK_IDS_ACCESSOR = template.getSafeFieldByType(int[].class).getAccessor(); | |
GET_CORRECT_DATA_ACCESSOR = new Reflection().reflect(SpigotDebreakifier.class).getSafeMethod("getCorrectedData", int.class, int.class); | |
IS_SPIGOT_PROTO = true; | |
} | |
} catch (Exception e) { | |
throw new RuntimeException("Failed to initialize the fields!"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment