Skip to content

Instantly share code, notes, and snippets.

@CaptainBern
Last active August 28, 2023 19:12
Show Gist options
  • Save CaptainBern/73fa15cb0359fda67691 to your computer and use it in GitHub Desktop.
Save CaptainBern/73fa15cb0359fda67691 to your computer and use it in GitHub Desktop.
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