package net.scapeemulator.game.net.game;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import net.scapeemulator.game.net.game.GameFrame.Type;
public final class GameFrameBuilder {
private static final int[] BITMASKS = { 0x0, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff, 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, 0x1fffff,
0x3fffff, 0x7fffff, 0xffffff, 0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, -1 };
private final int opcode;
private final Type type;
private final ByteBufAllocator alloc;
private final ByteBuf buffer;
private AccessMode mode = AccessMode.BYTE_ACCESS;
private int bitIndex;
public GameFrameBuilder(ByteBufAllocator alloc) {
this(alloc, -1, Type.RAW);
}
public GameFrameBuilder(ByteBufAllocator alloc, int opcode) {
this(alloc, opcode, Type.FIXED);
}
public GameFrameBuilder(ByteBufAllocator alloc, int opcode, Type type) {
this.alloc = alloc;
this.buffer = alloc.buffer();
this.opcode = opcode;
this.type = type;
}
public GameFrame toGameFrame() {
if (type == Type.RAW)
throw new IllegalStateException("Raw builders cannot be converted to frames");
if (mode != AccessMode.BYTE_ACCESS)
throw new IllegalStateException("Must be in byte access mode to convert to a packet");
return new GameFrame(opcode, type, buffer);
}
public ByteBufAllocator getAllocator() {
return alloc;
}
public int getLength() {
checkByteAccess();
return buffer.writerIndex();
}
public void switchToByteAccess() {
if (mode == AccessMode.BYTE_ACCESS) {
throw new IllegalStateException("Already in byte access mode");
}
mode = AccessMode.BYTE_ACCESS;
buffer.writerIndex((bitIndex + 7) / 8);
}
public void switchToBitAccess() {
if (mode == AccessMode.BIT_ACCESS) {
throw new IllegalStateException("Already in bit access mode");
}
mode = AccessMode.BIT_ACCESS;
bitIndex = buffer.writerIndex() * 8;
}
public void putRawBuilder(GameFrameBuilder builder) {
checkByteAccess();
if (builder.type != Type.RAW) {
throw new IllegalArgumentException("Builder must be raw!");
}
builder.checkByteAccess();
putBytes(builder.buffer);
}
/**
* Puts a raw builder in reverse. Both builders (this and parameter) must be in byte access
* mode.
*
* @param builder The builder.
*/
public void putRawBuilderReverse(GameFrameBuilder builder) {
checkByteAccess();
if (builder.type != Type.RAW) {
throw new IllegalArgumentException("Builder must be raw!");
}
builder.checkByteAccess();
putBytesReverse(builder.buffer);
}
/**
* Puts a standard data type with the specified value.
*
* @param type The data type.
* @param value The value.
* @throws IllegalStateException if this reader is not in byte access mode.
*/
public void put(DataType type, Number value) {
put(type, DataOrder.BIG, DataTransformation.NONE, value);
}
/**
* Puts a standard data type with the specified value and byte order.
*
* @param type The data type.
* @param order The byte order.
* @param value The value.
* @throws IllegalStateException if this reader is not in byte access mode.
* @throws IllegalArgumentException if the combination is invalid.
*/
public void put(DataType type, DataOrder order, Number value) {
put(type, order, DataTransformation.NONE, value);
}
/**
* Puts a standard data type with the specified value and transformation.
*
* @param type The type.
* @param transformation The transformation.
* @param value The value.
* @throws IllegalStateException if this reader is not in byte access mode.
* @throws IllegalArgumentException if the combination is invalid.
*/
public void put(DataType type, DataTransformation transformation, Number value) {
put(type, DataOrder.BIG, transformation, value);
}
/**
* Puts a standard data type with the specified value, byte order and transformation.
*
* @param type The data type.
* @param order The byte order.
* @param transformation The transformation.
* @param value The value.
* @throws IllegalStateException if this reader is not in byte access mode.
* @throws IllegalArgumentException if the combination is invalid.
*/
public void put(DataType type, DataOrder order, DataTransformation transformation, Number value) {
checkByteAccess();
long longValue = value.longValue();
int length = type.getBytes();
if (order == DataOrder.BIG) {
for (int i = length - 1; i >= 0; i--) {
if (i == 0 && transformation != DataTransformation.NONE) {
if (transformation == DataTransformation.ADD) {
buffer.writeByte((byte) (longValue + 128));
} else if (transformation == DataTransformation.NEGATE) {
buffer.writeByte((byte) (-longValue));
} else if (transformation == DataTransformation.SUBTRACT) {
buffer.writeByte((byte) (128 - longValue));
} else {
throw new IllegalArgumentException("unknown transformation");
}
} else {
buffer.writeByte((byte) (longValue >> (i * 8)));
}
}
} else if (order == DataOrder.LITTLE) {
for (int i = 0; i < length; i++) {
if (i == 0 && transformation != DataTransformation.NONE) {
if (transformation == DataTransformation.ADD) {
buffer.writeByte((byte) (longValue + 128));
} else if (transformation == DataTransformation.NEGATE) {
buffer.writeByte((byte) (-longValue));
} else if (transformation == DataTransformation.SUBTRACT) {
buffer.writeByte((byte) (128 - longValue));
} else {
throw new IllegalArgumentException("unknown transformation");
}
} else {
buffer.writeByte((byte) (longValue >> (i * 8)));
}
}
} else if (order == DataOrder.MIDDLE) {
if (transformation != DataTransformation.NONE) {
throw new IllegalArgumentException("middle endian cannot be transformed");
}
if (type != DataType.INT) {
throw new IllegalArgumentException("middle endian can only be used with an integer");
}
buffer.writeByte((byte) (longValue >> 8));
buffer.writeByte((byte) longValue);
buffer.writeByte((byte) (longValue >> 24));
buffer.writeByte((byte) (longValue >> 16));
} else if (order == DataOrder.INVERSED_MIDDLE) {
if (transformation != DataTransformation.NONE) {
throw new IllegalArgumentException("inversed middle endian cannot be transformed");
}
if (type != DataType.INT) {
throw new IllegalArgumentException("inversed middle endian can only be used with an integer");
}
buffer.writeByte((byte) (longValue >> 16));
buffer.writeByte((byte) (longValue >> 24));
buffer.writeByte((byte) longValue);
buffer.writeByte((byte) (longValue >> 8));
} else {
throw new IllegalArgumentException("unknown order");
}
}
/**
* Puts a string into the buffer.
*
* @param str The string.
*/
public void putString(String str) {
checkByteAccess();
char[] chars = str.toCharArray();
for (char c : chars) {
buffer.writeByte((byte) c);
}
buffer.writeByte(0);
}
/**
* Puts a smart into the buffer.
*
* @param value The value.
*/
public void putSmart(int value) {
checkByteAccess();
if (value < 128) {
buffer.writeByte(value);
} else {
buffer.writeShort(value);
}
}
/**
* Puts the bytes from the specified buffer into this packet's buffer.
*
* @param buffer The source {@link ByteBuf}.
* @throws IllegalStateException if the builder is not in byte access mode.
*/
public void putBytes(ByteBuf buffer) {
byte[] bytes = new byte[buffer.readableBytes()];
buffer.markReaderIndex();
try {
buffer.readBytes(bytes);
} finally {
buffer.resetReaderIndex();
}
putBytes(bytes);
}
/**
* Puts the bytes from the specified buffer into this packet's buffer, in reverse.
*
* @param buffer The source {@link ByteBuf}.
* @throws IllegalStateException if the builder is not in byte access mode.
*/
public void putBytesReverse(ByteBuf buffer) {
byte[] bytes = new byte[buffer.readableBytes()];
buffer.markReaderIndex();
try {
buffer.readBytes(bytes);
} finally {
buffer.resetReaderIndex();
}
putBytesReverse(bytes);
}
/**
* Puts the specified byte array into the buffer.
*
* @param bytes The byte array.
* @throws IllegalStateException if the builder is not in bit access mode.
*/
public void putBytes(byte[] bytes) {
buffer.writeBytes(bytes);
}
/**
* Puts the bytes into the buffer with the specified transformation.
*
* @param transformation The transformation.
* @param bytes The byte array.
* @throws IllegalStateException if the builder is not in byte access mode.
*/
public void putBytes(DataTransformation transformation, byte[] bytes) {
if (transformation == DataTransformation.NONE) {
putBytes(bytes);
} else {
for (byte b : bytes) {
put(DataType.BYTE, transformation, b);
}
}
}
/**
* Puts the specified byte array into the buffer in reverse.
*
* @param bytes The byte array.
* @throws IllegalStateException if the builder is not in byte access mode.
*/
public void putBytesReverse(byte[] bytes) {
checkByteAccess();
for (int i = bytes.length - 1; i >= 0; i--) {
buffer.writeByte(bytes[i]);
}
}
/**
* Puts the specified byte array into the buffer in reverse with the specified transformation.
*
* @param transformation The transformation.
* @param bytes The byte array.
* @throws IllegalStateException if the builder is not in byte access mode.
*/
public void putBytesReverse(DataTransformation transformation, byte[] bytes) {
if (transformation == DataTransformation.NONE) {
putBytesReverse(bytes);
} else {
for (int i = bytes.length - 1; i >= 0; i--) {
put(DataType.BYTE, transformation, bytes[i]);
}
}
}
/**
* Puts a single bit into the buffer. If {@code flag} is {@code true}, the value of the bit is
* {@code 1}. If {@code flag} is {@code false}, the value of the bit is {@code 0}.
*
* @param flag The flag.
* @throws IllegalStateException if the builder is not in bit access mode.
*/
public void putBit(boolean flag) {
putBit(flag ? 1 : 0);
}
/**
* Puts a single bit into the buffer with the value {@code value}.
*
* @param value The value.
* @throws IllegalStateException if the builder is not in bit access mode.
*/
public void putBit(int value) {
putBits(1, value);
}
/**
* Puts {@code numBits} into the buffer with the value {@code value}.
*
* @param numBits The number of bits to put into the buffer.
* @param value The value.
* @throws IllegalStateException if the builder is not in bit access mode.
* @throws IllegalArgumentException if the number of bits is not between 1 and 31 inclusive.
*/
public void putBits(int numBits, int value) {
if (numBits <= 0 || numBits > 32) {
throw new IllegalArgumentException("Number of bits must be between 1 and 31 inclusive");
}
checkBitAccess();
int bytePos = bitIndex >> 3;
buffer.writerIndex(bytePos + 1);
if (buffer.writableBytes() < ((numBits + 7) / 8)) {
buffer.capacity(buffer.capacity() * 2);
}
int bitOffset = 8 - (bitIndex & 7);
bitIndex += numBits;
for (; numBits > bitOffset; bitOffset = 8) {
int tmp = buffer.getByte(bytePos);
tmp &= ~BITMASKS[bitOffset];
tmp |= (value >> (numBits - bitOffset)) & BITMASKS[bitOffset];
buffer.setByte(bytePos++, tmp);
numBits -= bitOffset;
}
if (numBits == bitOffset) {
int tmp = buffer.getByte(bytePos);
tmp &= ~BITMASKS[bitOffset];
tmp |= value & BITMASKS[bitOffset];
buffer.setByte(bytePos, tmp);
} else {
int tmp = buffer.getByte(bytePos);
tmp &= ~(BITMASKS[numBits] << (bitOffset - numBits));
tmp |= (value & BITMASKS[numBits]) << (bitOffset - numBits);
buffer.setByte(bytePos, tmp);
}
}
/**
* Checks that this builder is in the byte access mode.
*
* @throws IllegalStateException if the builder is not in byte access mode.
*/
private void checkByteAccess() {
if (mode != AccessMode.BYTE_ACCESS) {
throw new IllegalStateException("For byte-based calls to work, the mode must be byte access");
}
}
/**
* Checks that this builder is in the bit access mode.
*
* @throws IllegalStateException if the builder is not in bit access mode.
*/
private void checkBitAccess() {
if (mode != AccessMode.BIT_ACCESS) {
throw new IllegalStateException("For bit-based calls to work, the mode must be bit access");
}
}
}