This commit is contained in:
Brian Neumann-Fopiano committed 2026-07-09 13:54:59 -04:00
1 parent d1fec88769
commit 0a7a531646
259 files changed
+11055 -884

No files matched your search

@@ -34,6 +34,10 @@ public final class IrisPlatforms {
platform = p;
}
public static synchronized void unbind() {
platform = null;
}
public static IrisPlatform get() {
IrisPlatform bound = platform;
if (bound == null) {
@@ -38,6 +38,15 @@ public final class IrisServices {
return type.cast(implementation);
}
public static <T> T getOrNull(Class<T> type) {
Object implementation = SERVICES.get(type);
return implementation == null ? null : type.cast(implementation);
}
public static void remove(Class<?> type) {
SERVICES.remove(type);
}
public static void clear() {
SERVICES.clear();
}
@@ -0,0 +1,27 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi;
import java.util.List;
public record PlatformBlockProperty(String name, String jsonType, Object defaultValue, List<Object> allowedValues, PlatformNumericRange numericRange) {
public boolean hasNumericRange() {
return numericRange != null;
}
}
@@ -0,0 +1,22 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi;
public record PlatformNumericRange(double minimum, double maximum, boolean exclusiveMinimum, boolean exclusiveMaximum) {
}
@@ -19,6 +19,7 @@
package art.arcane.iris.spi;
import java.util.List;
import java.util.Map;
/**
* Resolves namespaced string keys against the platform's live registries into interned neutral handles.
@@ -45,4 +46,16 @@ public interface PlatformRegistries {
List<String> biomeKeys();
List<String> structureKeys();
List<String> itemKeys();
List<String> entityKeys();
List<String> blockTypeKeys();
List<String> enchantmentKeys();
List<String> potionEffectKeys();
Map<String, List<PlatformBlockProperty>> blockStateProperties();
}
@@ -0,0 +1,131 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi.protocol;
import java.util.List;
public sealed interface IrisMessage {
int messageTypeId();
record ClientHello(int protocolVersion, long capabilities) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_CLIENT_HELLO;
}
}
record ServerHello(int protocolVersion, long capabilities, String serverBrand, boolean irisActive) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_SERVER_HELLO;
}
}
record PregenProgress(long jobId, long chunksDone, long chunksTotal, double chunksPerSecond, long etaMillis, int state) implements IrisMessage {
public static final int STATE_RUNNING = 0;
public static final int STATE_PAUSED = 1;
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_PREGEN_PROGRESS;
}
}
record PregenEnd(long jobId, boolean completed) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_PREGEN_END;
}
}
record DimensionStatus(String dimensionKey, String packKey, long seed, int minY, int maxY, boolean irisWorld) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_DIMENSION_STATUS;
}
}
record CursorInfoRequest(int blockX, int blockZ) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_CURSOR_INFO_REQUEST;
}
}
record CursorInfo(int blockX, int blockZ, String biomeKey, String regionKey, String caveBiomeKey, int height, String dimensionKey) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_CURSOR_INFO;
}
}
record VisionTileRequest(int tileX, int tileZ, int zoomLevel) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_VISION_TILE_REQUEST;
}
}
record VisionTile(int tileX, int tileZ, int zoomLevel, int sequence, int chunkIndex, int chunkCount, byte[] data) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_VISION_TILE;
}
}
record VisionMarkers(int tileX, int tileZ, int zoomLevel, List<Marker> markers) implements IrisMessage {
public record Marker(int blockX, int blockZ, int kind, String label) {
}
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_VISION_MARKERS;
}
}
record PregenRegionDelta(long jobId, int regionX, int regionZ, int state) implements IrisMessage {
public static final int STATE_PENDING = 0;
public static final int STATE_GENERATING = 1;
public static final int STATE_DONE = 2;
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_PREGEN_REGION_DELTA;
}
}
record StudioHotload(String packKey, int changedFiles, boolean failed, String message) implements IrisMessage {
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_STUDIO_HOTLOAD;
}
}
record Toast(int kind, String title, String body) implements IrisMessage {
public static final int KIND_INFO = 0;
public static final int KIND_SUCCESS = 1;
public static final int KIND_WARNING = 2;
public static final int KIND_ERROR = 3;
@Override
public int messageTypeId() {
return IrisProtocol.TYPE_TOAST;
}
}
}
@@ -0,0 +1,164 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi.protocol;
import java.util.ArrayList;
import java.util.List;
public final class IrisMessageCodec {
private IrisMessageCodec() {
}
public static byte[] encode(IrisMessage message) {
IrisWireWriter writer = new IrisWireWriter();
writer.writeVarInt(message.messageTypeId());
switch (message) {
case IrisMessage.ClientHello clientHello -> {
writer.writeInt(clientHello.protocolVersion());
writer.writeLong(clientHello.capabilities());
}
case IrisMessage.ServerHello serverHello -> {
writer.writeInt(serverHello.protocolVersion());
writer.writeLong(serverHello.capabilities());
writer.writeString(serverHello.serverBrand());
writer.writeBoolean(serverHello.irisActive());
}
case IrisMessage.PregenProgress pregenProgress -> {
writer.writeLong(pregenProgress.jobId());
writer.writeLong(pregenProgress.chunksDone());
writer.writeLong(pregenProgress.chunksTotal());
writer.writeDouble(pregenProgress.chunksPerSecond());
writer.writeLong(pregenProgress.etaMillis());
writer.writeInt(pregenProgress.state());
}
case IrisMessage.PregenEnd pregenEnd -> {
writer.writeLong(pregenEnd.jobId());
writer.writeBoolean(pregenEnd.completed());
}
case IrisMessage.DimensionStatus dimensionStatus -> {
writer.writeString(dimensionStatus.dimensionKey());
writer.writeString(dimensionStatus.packKey());
writer.writeLong(dimensionStatus.seed());
writer.writeInt(dimensionStatus.minY());
writer.writeInt(dimensionStatus.maxY());
writer.writeBoolean(dimensionStatus.irisWorld());
}
case IrisMessage.CursorInfoRequest cursorInfoRequest -> {
writer.writeInt(cursorInfoRequest.blockX());
writer.writeInt(cursorInfoRequest.blockZ());
}
case IrisMessage.CursorInfo cursorInfo -> {
writer.writeInt(cursorInfo.blockX());
writer.writeInt(cursorInfo.blockZ());
writer.writeString(cursorInfo.biomeKey());
writer.writeString(cursorInfo.regionKey());
writer.writeString(cursorInfo.caveBiomeKey());
writer.writeInt(cursorInfo.height());
writer.writeString(cursorInfo.dimensionKey());
}
case IrisMessage.VisionTileRequest visionTileRequest -> {
writer.writeInt(visionTileRequest.tileX());
writer.writeInt(visionTileRequest.tileZ());
writer.writeInt(visionTileRequest.zoomLevel());
}
case IrisMessage.VisionTile visionTile -> {
writer.writeInt(visionTile.tileX());
writer.writeInt(visionTile.tileZ());
writer.writeInt(visionTile.zoomLevel());
writer.writeInt(visionTile.sequence());
writer.writeInt(visionTile.chunkIndex());
writer.writeInt(visionTile.chunkCount());
writer.writeBytes(visionTile.data());
}
case IrisMessage.VisionMarkers visionMarkers -> {
writer.writeInt(visionMarkers.tileX());
writer.writeInt(visionMarkers.tileZ());
writer.writeInt(visionMarkers.zoomLevel());
List<IrisMessage.VisionMarkers.Marker> markers = visionMarkers.markers();
writer.writeVarInt(markers.size());
for (IrisMessage.VisionMarkers.Marker marker : markers) {
writer.writeInt(marker.blockX());
writer.writeInt(marker.blockZ());
writer.writeInt(marker.kind());
writer.writeString(marker.label());
}
}
case IrisMessage.PregenRegionDelta pregenRegionDelta -> {
writer.writeLong(pregenRegionDelta.jobId());
writer.writeInt(pregenRegionDelta.regionX());
writer.writeInt(pregenRegionDelta.regionZ());
writer.writeInt(pregenRegionDelta.state());
}
case IrisMessage.StudioHotload studioHotload -> {
writer.writeString(studioHotload.packKey());
writer.writeInt(studioHotload.changedFiles());
writer.writeBoolean(studioHotload.failed());
writer.writeString(studioHotload.message());
}
case IrisMessage.Toast toast -> {
writer.writeInt(toast.kind());
writer.writeString(toast.title());
writer.writeString(toast.body());
}
}
return writer.toByteArray();
}
public static IrisMessage decode(byte[] frame) throws ProtocolException {
if (frame == null) {
throw new ProtocolException("null frame");
}
if (frame.length > IrisProtocol.MAX_FRAME_BYTES) {
throw new ProtocolException("frame exceeds " + IrisProtocol.MAX_FRAME_BYTES + " byte cap");
}
IrisWireReader reader = new IrisWireReader(frame);
int messageTypeId = reader.readVarInt();
return switch (messageTypeId) {
case IrisProtocol.TYPE_CLIENT_HELLO -> new IrisMessage.ClientHello(reader.readInt(), reader.readLong());
case IrisProtocol.TYPE_SERVER_HELLO -> new IrisMessage.ServerHello(reader.readInt(), reader.readLong(), reader.readString(), reader.readBoolean());
case IrisProtocol.TYPE_PREGEN_PROGRESS -> new IrisMessage.PregenProgress(reader.readLong(), reader.readLong(), reader.readLong(), reader.readDouble(), reader.readLong(), reader.readInt());
case IrisProtocol.TYPE_PREGEN_END -> new IrisMessage.PregenEnd(reader.readLong(), reader.readBoolean());
case IrisProtocol.TYPE_DIMENSION_STATUS -> new IrisMessage.DimensionStatus(reader.readString(), reader.readString(), reader.readLong(), reader.readInt(), reader.readInt(), reader.readBoolean());
case IrisProtocol.TYPE_CURSOR_INFO_REQUEST -> new IrisMessage.CursorInfoRequest(reader.readInt(), reader.readInt());
case IrisProtocol.TYPE_CURSOR_INFO -> new IrisMessage.CursorInfo(reader.readInt(), reader.readInt(), reader.readString(), reader.readString(), reader.readString(), reader.readInt(), reader.readString());
case IrisProtocol.TYPE_VISION_TILE_REQUEST -> new IrisMessage.VisionTileRequest(reader.readInt(), reader.readInt(), reader.readInt());
case IrisProtocol.TYPE_VISION_TILE -> new IrisMessage.VisionTile(reader.readInt(), reader.readInt(), reader.readInt(), reader.readInt(), reader.readInt(), reader.readInt(), reader.readBytes());
case IrisProtocol.TYPE_VISION_MARKERS -> decodeVisionMarkers(reader);
case IrisProtocol.TYPE_PREGEN_REGION_DELTA -> new IrisMessage.PregenRegionDelta(reader.readLong(), reader.readInt(), reader.readInt(), reader.readInt());
case IrisProtocol.TYPE_STUDIO_HOTLOAD -> new IrisMessage.StudioHotload(reader.readString(), reader.readInt(), reader.readBoolean(), reader.readString());
case IrisProtocol.TYPE_TOAST -> new IrisMessage.Toast(reader.readInt(), reader.readString(), reader.readString());
default -> null;
};
}
private static IrisMessage.VisionMarkers decodeVisionMarkers(IrisWireReader reader) throws ProtocolException {
int tileX = reader.readInt();
int tileZ = reader.readInt();
int zoomLevel = reader.readInt();
int count = reader.readVarInt();
if (count < 0) {
throw new ProtocolException("negative marker count");
}
List<IrisMessage.VisionMarkers.Marker> markers = new ArrayList<>(Math.min(count, IrisProtocol.MAX_VISION_MARKERS));
for (int index = 0; index < count; index++) {
markers.add(new IrisMessage.VisionMarkers.Marker(reader.readInt(), reader.readInt(), reader.readInt(), reader.readString()));
}
return new IrisMessage.VisionMarkers(tileX, tileZ, zoomLevel, markers);
}
}
@@ -0,0 +1,50 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi.protocol;
public final class IrisProtocol {
public static final int PROTOCOL_VERSION = 1;
public static final String CHANNEL = "irisworldgen:main";
public static final int MAX_FRAME_BYTES = 24576;
public static final int MAX_INBOUND_FRAMES_PER_SECOND = 32;
public static final int MAX_VISION_TILE_REQUESTS_PER_SECOND = 8;
public static final int VISION_TILE_HEADER_BYTES = 25;
public static final int VISION_TILE_MAX_CHUNK_BYTES = 24000;
public static final int MAX_VISION_MARKERS = 256;
public static final long CAPABILITY_PREGEN = 1L << 0;
public static final long CAPABILITY_VISION = 1L << 1;
public static final long CAPABILITY_CURSOR = 1L << 2;
public static final long CAPABILITY_STUDIO = 1L << 3;
public static final int TYPE_CLIENT_HELLO = 1;
public static final int TYPE_SERVER_HELLO = 2;
public static final int TYPE_PREGEN_PROGRESS = 3;
public static final int TYPE_PREGEN_END = 4;
public static final int TYPE_DIMENSION_STATUS = 5;
public static final int TYPE_CURSOR_INFO_REQUEST = 6;
public static final int TYPE_CURSOR_INFO = 7;
public static final int TYPE_VISION_TILE_REQUEST = 8;
public static final int TYPE_VISION_TILE = 9;
public static final int TYPE_VISION_MARKERS = 10;
public static final int TYPE_PREGEN_REGION_DELTA = 11;
public static final int TYPE_STUDIO_HOTLOAD = 12;
public static final int TYPE_TOAST = 13;
private IrisProtocol() {
}
}
@@ -0,0 +1,118 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi.protocol;
import java.nio.charset.StandardCharsets;
public final class IrisWireReader {
private final byte[] frame;
private final int limit;
private int position;
public IrisWireReader(byte[] frame) {
this.frame = frame;
this.limit = frame.length;
this.position = 0;
}
public int readVarInt() throws ProtocolException {
int result = 0;
int shift = 0;
for (int index = 0; index < 5; index++) {
requireRemaining(1);
int current = frame[position++] & 0xFF;
result |= (current & 0x7F) << shift;
if ((current & 0x80) == 0) {
return result;
}
shift += 7;
}
throw new ProtocolException("varint exceeds 5 bytes");
}
public int readInt() throws ProtocolException {
requireRemaining(4);
int value = ((frame[position] & 0xFF) << 24)
| ((frame[position + 1] & 0xFF) << 16)
| ((frame[position + 2] & 0xFF) << 8)
| (frame[position + 3] & 0xFF);
position += 4;
return value;
}
public long readLong() throws ProtocolException {
requireRemaining(8);
long value = ((long) (frame[position] & 0xFF) << 56)
| ((long) (frame[position + 1] & 0xFF) << 48)
| ((long) (frame[position + 2] & 0xFF) << 40)
| ((long) (frame[position + 3] & 0xFF) << 32)
| ((long) (frame[position + 4] & 0xFF) << 24)
| ((long) (frame[position + 5] & 0xFF) << 16)
| ((long) (frame[position + 6] & 0xFF) << 8)
| (frame[position + 7] & 0xFF);
position += 8;
return value;
}
public double readDouble() throws ProtocolException {
return Double.longBitsToDouble(readLong());
}
public boolean readBoolean() throws ProtocolException {
requireRemaining(1);
return frame[position++] != 0;
}
public String readString() throws ProtocolException {
int declaredLength = readVarInt();
if (declaredLength < 0) {
throw new ProtocolException("negative string length");
}
if (declaredLength > remaining()) {
throw new ProtocolException("string length exceeds remaining frame bytes");
}
String value = new String(frame, position, declaredLength, StandardCharsets.UTF_8);
position += declaredLength;
return value;
}
public byte[] readBytes() throws ProtocolException {
int declaredLength = readVarInt();
if (declaredLength < 0) {
throw new ProtocolException("negative byte array length");
}
if (declaredLength > remaining()) {
throw new ProtocolException("byte array length exceeds remaining frame bytes");
}
byte[] value = new byte[declaredLength];
System.arraycopy(frame, position, value, 0, declaredLength);
position += declaredLength;
return value;
}
private int remaining() {
return limit - position;
}
private void requireRemaining(int count) throws ProtocolException {
if (position + count > limit) {
throw new ProtocolException("truncated frame: needed " + count + " bytes, had " + remaining());
}
}
}
@@ -0,0 +1,116 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi.protocol;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public final class IrisWireWriter {
private byte[] buffer;
private int length;
public IrisWireWriter() {
this.buffer = new byte[64];
this.length = 0;
}
public void writeVarInt(int value) {
int remaining = value;
while (true) {
int chunk = remaining & 0x7F;
remaining >>>= 7;
if (remaining != 0) {
writeByte(chunk | 0x80);
} else {
writeByte(chunk);
return;
}
}
}
public void writeInt(int value) {
ensure(4);
buffer[length++] = (byte) (value >>> 24);
buffer[length++] = (byte) (value >>> 16);
buffer[length++] = (byte) (value >>> 8);
buffer[length++] = (byte) value;
}
public void writeLong(long value) {
ensure(8);
buffer[length++] = (byte) (value >>> 56);
buffer[length++] = (byte) (value >>> 48);
buffer[length++] = (byte) (value >>> 40);
buffer[length++] = (byte) (value >>> 32);
buffer[length++] = (byte) (value >>> 24);
buffer[length++] = (byte) (value >>> 16);
buffer[length++] = (byte) (value >>> 8);
buffer[length++] = (byte) value;
}
public void writeDouble(double value) {
writeLong(Double.doubleToLongBits(value));
}
public void writeBoolean(boolean value) {
writeByte(value ? 1 : 0);
}
public void writeString(String value) {
byte[] encoded = value.getBytes(StandardCharsets.UTF_8);
writeVarInt(encoded.length);
ensure(encoded.length);
System.arraycopy(encoded, 0, buffer, length, encoded.length);
length += encoded.length;
}
public void writeBytes(byte[] value) {
writeVarInt(value.length);
ensure(value.length);
System.arraycopy(value, 0, buffer, length, value.length);
length += value.length;
}
public byte[] toByteArray() {
return Arrays.copyOf(buffer, length);
}
private void writeByte(int value) {
ensure(1);
buffer[length++] = (byte) value;
}
private void ensure(int extra) {
if ((long) length + extra > IrisProtocol.MAX_FRAME_BYTES) {
throw new IllegalStateException("Iris protocol frame exceeds " + IrisProtocol.MAX_FRAME_BYTES + " byte cap");
}
int required = length + extra;
if (required <= buffer.length) {
return;
}
int grown = buffer.length;
while (grown < required) {
grown <<= 1;
}
if (grown > IrisProtocol.MAX_FRAME_BYTES) {
grown = IrisProtocol.MAX_FRAME_BYTES;
}
buffer = Arrays.copyOf(buffer, grown);
}
}
@@ -0,0 +1,25 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.spi.protocol;
public class ProtocolException extends Exception {
public ProtocolException(String message) {
super(message);
}
}