diff --git a/build.gradle b/build.gradle index 270c2c5d..b9d1f22e 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ repositories { } dependencies { - minecraft "net.minecraftforge:forge:1.19.2-43.2.10" + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" annotationProcessor "org.spongepowered:mixin:0.8.5:processor" // Specify the libs embedded in the library mod explicitly for local development, where @@ -90,6 +90,7 @@ dependencies { compileOnly "li.cil.sedna:sedna-buildroot:0.0.8" } implementation "curse.maven:sedna-511276:3885542" + minecraftLibrary "org.apache.commons:commons-collections4:4.4" implementation fg.deobf("curse.maven:markdownmanual-502485:4306669") implementation fg.deobf("curse.maven:architectury-api-419699:4521273") @@ -186,6 +187,7 @@ jar { "Implementation-Vendor" : "Sangar", "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), "MixinConfigs" : "mixins.oc2.json", + "ContainedDeps" : "commons-collections4-4.4.jar" ]) } } diff --git a/src/main/java/li/cil/oc2/common/CommonSetup.java b/src/main/java/li/cil/oc2/common/CommonSetup.java index c15adabb..ad552621 100644 --- a/src/main/java/li/cil/oc2/common/CommonSetup.java +++ b/src/main/java/li/cil/oc2/common/CommonSetup.java @@ -6,6 +6,7 @@ import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters; import li.cil.oc2.common.integration.IMC; import li.cil.oc2.common.network.Network; import li.cil.oc2.common.util.ServerScheduler; +import li.cil.oc2.common.vxlan.TunnelManager; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -16,5 +17,6 @@ public final class CommonSetup { Network.initialize(); RPCMethodParameterTypeAdapters.initialize(); ServerScheduler.initialize(); + TunnelManager.initialize(); } } diff --git a/src/main/java/li/cil/oc2/common/Config.java b/src/main/java/li/cil/oc2/common/Config.java index 317aba63..391f321f 100644 --- a/src/main/java/li/cil/oc2/common/Config.java +++ b/src/main/java/li/cil/oc2/common/Config.java @@ -20,6 +20,8 @@ public final class Config { @Path("energy.blocks") public static int chargerEnergyStorage = 10000; @Path("energy.blocks") public static int projectorEnergyPerTick = 20; @Path("energy.blocks") public static int projectorEnergyStorage = 2000; + @Path("energy.blocks") public static int cardCageEnergyPerTick = 20; + @Path("energy.blocks") public static int cardCageEnergyStorage = 2000; @Path("energy.entities") public static int robotEnergyPerTick = 5; @Path("energy.entities") public static int robotEnergyStorage = 750000; @@ -42,6 +44,12 @@ public final class Config { @Path("admin.virtual_network") public static int ethernetFrameTimeToLive = 12; @Path("admin.virtual_network") public static int hubEthernetFramesPerTick = 32; + @Path("vxlan") public static boolean enable = true; + @Path("vxlan") public static String remoteHost = "::1"; + @Path("vxlan") public static int remotePort = 4789; + @Path("vxlan") public static String bindHost = "::1"; + @Path("vxlan") public static int bindPort = 4789; + public static boolean computersUseEnergy() { return computerEnergyPerTick > 0 && computerEnergyStorage > 0; } @@ -54,6 +62,10 @@ public final class Config { return projectorEnergyStorage > 0 && projectorEnergyPerTick > 0; } + public static boolean cardCagesUseEnergy() { + return cardCageEnergyStorage > 0 && cardCageEnergyPerTick > 0; + } + public static boolean robotsUseEnergy() { return robotEnergyPerTick > 0 && robotEnergyStorage > 0; } diff --git a/src/main/java/li/cil/oc2/common/ConfigManager.java b/src/main/java/li/cil/oc2/common/ConfigManager.java index cd6f8359..8c31a184 100644 --- a/src/main/java/li/cil/oc2/common/ConfigManager.java +++ b/src/main/java/li/cil/oc2/common/ConfigManager.java @@ -65,8 +65,10 @@ public final class ConfigManager { static { PARSERS.put(int.class, ConfigManager::parseIntField); + PARSERS.put(short.class, ConfigManager::parseShortField); PARSERS.put(long.class, ConfigManager::parseLongField); PARSERS.put(double.class, ConfigManager::parseDoubleField); + PARSERS.put(boolean.class, ConfigManager::parseBooleanField); PARSERS.put(String.class, ConfigManager::parseStringField); PARSERS.put(UUID.class, ConfigManager::parseUUIDField); PARSERS.put(ResourceLocation.class, ConfigManager::parseResourceLocationField); @@ -134,6 +136,16 @@ public final class ConfigManager { return new ConfigFieldPair<>(field, configValue); } + private static ConfigFieldPair parseShortField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException { + final short defaultValue = field.getShort(instance); + final short minValue = (short) Math.max(getMin(field), Short.MIN_VALUE); + final short maxValue = (short) Math.min(getMax(field), Short.MAX_VALUE); + + final ForgeConfigSpec.IntValue configValue = builder.defineInRange(path, defaultValue, minValue, maxValue); + + return new ConfigFieldPair<>(field, configValue); + } + private static ConfigFieldPair parseLongField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException { final long defaultValue = field.getLong(instance); final long minValue = (long) Math.max(getMin(field), Long.MIN_VALUE); @@ -170,6 +182,14 @@ public final class ConfigManager { return new ConfigFieldPair<>(field, configValue, UUID::fromString); } + private static ConfigFieldPair parseBooleanField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException { + final boolean defaultValue = (boolean) field.get(instance); + + final ForgeConfigSpec.ConfigValue configValue = builder.define(path, defaultValue); + + return new ConfigFieldPair<>(field, configValue); + } + private static ConfigFieldPair parseResourceLocationField(final Object instance, final Field field, final String path, final ForgeConfigSpec.Builder builder) throws IllegalAccessException { final ResourceLocation defaultValue = (ResourceLocation) field.get(instance); diff --git a/src/main/java/li/cil/oc2/common/Main.java b/src/main/java/li/cil/oc2/common/Main.java index bab40055..efe4de56 100644 --- a/src/main/java/li/cil/oc2/common/Main.java +++ b/src/main/java/li/cil/oc2/common/Main.java @@ -55,6 +55,7 @@ public final class Main { ProviderRegistry.initialize(); DeviceTypes.initialize(); + BlockDeviceDataRegistry.initialize(); FirmwareRegistry.initialize(); diff --git a/src/main/java/li/cil/oc2/common/block/Blocks.java b/src/main/java/li/cil/oc2/common/block/Blocks.java index c4d47d26..97e27ddf 100644 --- a/src/main/java/li/cil/oc2/common/block/Blocks.java +++ b/src/main/java/li/cil/oc2/common/block/Blocks.java @@ -3,9 +3,11 @@ package li.cil.oc2.common.block; import li.cil.oc2.api.API; +import li.cil.oc2.common.blockentity.VxlanBlockEntity; import li.cil.oc2.common.util.RegistryUtils; import net.minecraft.world.level.block.Block; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; @@ -24,8 +26,12 @@ public final class Blocks { public static final RegistryObject KEYBOARD = BLOCKS.register("keyboard", KeyboardBlock::new); public static final RegistryObject NETWORK_CONNECTOR = BLOCKS.register("network_connector", NetworkConnectorBlock::new); public static final RegistryObject NETWORK_HUB = BLOCKS.register("network_hub", NetworkHubBlock::new); + public static final RegistryObject NETWORK_SWITCH = BLOCKS.register("network_switch", NetworkSwitchBlock::new); public static final RegistryObject PROJECTOR = BLOCKS.register("projector", ProjectorBlock::new); public static final RegistryObject REDSTONE_INTERFACE = BLOCKS.register("redstone_interface", RedstoneInterfaceBlock::new); + public static final RegistryObject VXLAN_HUB = BLOCKS.register("vxlan_hub", VxlanBlock::new); + public static final RegistryObject PCI_CARD_CAGE = BLOCKS.register("pci_card_cage", PciCardCageBlock::new); + /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/block/NetworkSwitchBlock.java b/src/main/java/li/cil/oc2/common/block/NetworkSwitchBlock.java new file mode 100644 index 00000000..03b62cc2 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/NetworkSwitchBlock.java @@ -0,0 +1,61 @@ +package li.cil.oc2.common.block; + +import li.cil.oc2.common.blockentity.BlockEntities; +import li.cil.oc2.common.blockentity.NetworkHubBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.material.Material; + +import javax.annotation.Nullable; + +public final class NetworkSwitchBlock extends HorizontalDirectionalBlock implements EntityBlock { + public NetworkSwitchBlock() { + super(Properties + .of(Material.METAL) + .sound(SoundType.METAL) + .strength(1.5f, 6.0f)); + registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH)); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public BlockState getStateForPlacement(final BlockPlaceContext context) { + return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite()); + } + + @SuppressWarnings("deprecation") + @Override + public void neighborChanged(final BlockState state, final Level level, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) { + final BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof final NetworkHubBlockEntity networkHub) { + networkHub.handleNeighborChanged(); + } + } + + /////////////////////////////////////////////////////////////////// + // EntityBlock + + @Nullable + @Override + public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) { + return BlockEntities.NETWORK_SWITCH.get().create(pos, state); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void createBlockStateDefinition(final StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING); + } +} diff --git a/src/main/java/li/cil/oc2/common/block/PciCardCageBlock.java b/src/main/java/li/cil/oc2/common/block/PciCardCageBlock.java new file mode 100644 index 00000000..978965b9 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/PciCardCageBlock.java @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.block; + +import li.cil.oc2.common.Config; +import li.cil.oc2.common.blockentity.BlockEntities; +import li.cil.oc2.common.blockentity.TickableBlockEntity; +import li.cil.oc2.common.util.VoxelShapeUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import javax.annotation.Nullable; + +public final class PciCardCageBlock extends HorizontalDirectionalBlock implements EntityBlock, EnergyConsumingBlock { + public static final BooleanProperty LIT = BlockStateProperties.LIT; + + // We bake the visual indents on the front and sides into the collision shape, to prevent stuff being + // placeable on those sides, such as network connectors, torches, etc. + private static final VoxelShape NEG_Z_SHAPE = Shapes.join(Shapes.block(), Shapes.or( + Shapes.box(0 / 16f, 2 / 16f, 2 / 16f, 1 / 16f, 6 / 16f, 14 / 16f), + Shapes.box(15 / 16f, 2 / 16f, 2 / 16f, 16 / 16f, 6 / 16f, 14 / 16f), + Shapes.box(4 / 16f, 4 / 16f, 0 / 16f, 12 / 16f, 12 / 16f, 2 / 16f) + ), (a, b) -> a && !b); + private static final VoxelShape NEG_X_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(NEG_Z_SHAPE); + private static final VoxelShape POS_Z_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(NEG_X_SHAPE); + private static final VoxelShape POS_X_SHAPE = VoxelShapeUtils.rotateHorizontalClockwise(POS_Z_SHAPE); + + public PciCardCageBlock() { + super(Properties + .of(Material.METAL) + .sound(SoundType.METAL) + .lightLevel(state -> state.getValue(LIT) ? 8 : 0) + .strength(1.5f, 6.0f)); + registerDefaultState(getStateDefinition().any() + .setValue(FACING, Direction.NORTH) + .setValue(LIT, false)); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public int getEnergyConsumption() { + if (Config.cardCagesUseEnergy()) { + return Config.cardCageEnergyPerTick; + } else { + return 0; + } + } + + @Override + public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) { + return BlockEntities.PCI_CARD_CAGE.get().create(pos, state); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(final Level level, final BlockState state, final BlockEntityType type) { + return TickableBlockEntity.createServerTicker(level, type, BlockEntities.PROJECTOR.get()); + } + + @SuppressWarnings("deprecation") + @Override + public VoxelShape getShape(final BlockState state, final BlockGetter level, final BlockPos blockPos, final CollisionContext context) { + return switch (state.getValue(FACING)) { + case NORTH -> NEG_Z_SHAPE; + case SOUTH -> POS_Z_SHAPE; + case WEST -> NEG_X_SHAPE; + default -> POS_X_SHAPE; + }; + } + + @Override + public BlockState getStateForPlacement(final BlockPlaceContext context) { + return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite()); + } + + /////////////////////////////////////////////////////////////////// + + protected void createBlockStateDefinition(final StateDefinition.Builder builder) { + builder.add(FACING, LIT); + } +} diff --git a/src/main/java/li/cil/oc2/common/block/VxlanBlock.java b/src/main/java/li/cil/oc2/common/block/VxlanBlock.java new file mode 100644 index 00000000..57c4b364 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/VxlanBlock.java @@ -0,0 +1,70 @@ +package li.cil.oc2.common.block; + +import li.cil.oc2.common.blockentity.BlockEntities; +import li.cil.oc2.common.blockentity.TickableBlockEntity; +import li.cil.oc2.common.blockentity.VxlanBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.material.Material; + +import javax.annotation.Nullable; + +public final class VxlanBlock extends HorizontalDirectionalBlock implements EntityBlock { + public VxlanBlock() { + super(Properties + .of(Material.METAL) + .sound(SoundType.METAL) + .strength(1.5f, 6.0f)); + registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH)); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public BlockState getStateForPlacement(final BlockPlaceContext context) { + return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite()); + } + + @SuppressWarnings("deprecation") + @Override + public void neighborChanged(final BlockState state, final Level level, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) { + final BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof final VxlanBlockEntity vxlanEntity) { + vxlanEntity.handleNeighborChanged(); + } + } + + /////////////////////////////////////////////////////////////////// + // EntityBlock + + @Nullable + @Override + public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) { + return BlockEntities.VXLAN_HUB.get().create(pos, state); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(final Level level, final BlockState state, final BlockEntityType type) { + return TickableBlockEntity.createServerTicker(level, type, BlockEntities.VXLAN_HUB.get()); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void createBlockStateDefinition(final StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING); + } +} diff --git a/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java b/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java index 3e1d1f49..e811092b 100644 --- a/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java +++ b/src/main/java/li/cil/oc2/common/blockentity/BlockEntities.java @@ -27,8 +27,12 @@ public final class BlockEntities { public static final RegistryObject> KEYBOARD = register(Blocks.KEYBOARD, KeyboardBlockEntity::new); public static final RegistryObject> NETWORK_CONNECTOR = register(Blocks.NETWORK_CONNECTOR, NetworkConnectorBlockEntity::new); public static final RegistryObject> NETWORK_HUB = register(Blocks.NETWORK_HUB, NetworkHubBlockEntity::new); + public static final RegistryObject> NETWORK_SWITCH = register(Blocks.NETWORK_SWITCH, NetworkSwitchBlockEntity::new); public static final RegistryObject> PROJECTOR = register(Blocks.PROJECTOR, ProjectorBlockEntity::new); public static final RegistryObject> REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE, RedstoneInterfaceBlockEntity::new); + public static final RegistryObject> VXLAN_HUB = register(Blocks.VXLAN_HUB, VxlanBlockEntity::new); + public static final RegistryObject> PCI_CARD_CAGE = register(Blocks.PCI_CARD_CAGE, PciCardCageBlockEntity::new); + /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/blockentity/NetworkSwitchBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/NetworkSwitchBlockEntity.java new file mode 100644 index 00000000..d73dd382 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/blockentity/NetworkSwitchBlockEntity.java @@ -0,0 +1,498 @@ +package li.cil.oc2.common.blockentity; + +import java.util.*; +import java.util.stream.Collectors; + +import com.google.gson.internal.LinkedTreeMap; +import com.mojang.datafixers.util.Pair; +import li.cil.oc2.api.bus.device.object.Callback; +import li.cil.oc2.api.bus.device.object.DocumentedDevice; +import li.cil.oc2.api.bus.device.object.NamedDevice; +import li.cil.oc2.api.capabilities.NetworkInterface; +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.block.NetworkSwitchBlock; +import li.cil.oc2.common.blockentity.BlockEntities; +import li.cil.oc2.common.blockentity.NetworkHubBlockEntity; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.util.LazyOptionalUtils; +import li.cil.oc2.common.util.LevelUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.*; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.util.LazyOptional; + + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +public final class NetworkSwitchBlockEntity extends ModBlockEntity implements NamedDevice, DocumentedDevice, NetworkInterface, TickableBlockEntity { + private final String GET_LINK_STATE = "getLinkState"; + private final String GET_HOST_TABLE = "getHostTable"; + private final String GET_PORT_CONFIG = "getPortConfig"; + private final String SET_PORT_CONFIG = "setPortConfig"; + + private final long HOST_TTL = 20 * 60 * 2; + private final int TTL_COST = 1; + private final Map hostTable = new HashMap<>(); + private final PortSettings[] portSettings = new PortSettings[Constants.BLOCK_FACE_COUNT]; + private int tickCount = 0; + private final NetworkInterface[] adjacentBlockInterfaces = new NetworkInterface[Constants.BLOCK_FACE_COUNT]; + private boolean haveAdjacentBlocksChanged = true; + + public NetworkSwitchBlockEntity(final BlockPos pos, final BlockState state) { + super(BlockEntities.NETWORK_SWITCH.get(), pos, state); + for (int i = 0; i < portSettings.length; i++) { + portSettings[i] = new PortSettings(); + } + } + + public void writeEthernetFrame(final NetworkInterface source, byte[] frame, final int timeToLive) { + validateAdjacentBlocks(); + long tickTime = getLevel().getGameTime(); + long destMac = macToLong(frame, 0); + long srcMac = macToLong(frame, 6); + short vlan = getVLAN(frame); + Optional optSide = sideReverseLookup(source); + if (!optSide.isPresent()) { + return; + } + int side = optSide.get(); + if (hostTable.size() <= 256) { + hostTable.put(srcMac, new HostEntry(side, tickTime)); + } + PortSettings ingressSettings = portSettings[side]; + SwitchLog log = new SwitchLog(vlan, side, srcMac, destMac); + if (vlan == 0) { + // Untagged packet + Pair pair = removeVLANTag(frame); // Remove tag in case there is a 0-tag + frame = pair.getSecond(); + if (ingressSettings.untagged != 0) { + frame = addVLANTag(frame, ingressSettings.untagged); + vlan = ingressSettings.untagged; + } + } else { + if (!(ingressSettings.trunkAll || ingressSettings.tagged.contains(vlan))) { + // drop packet with disallowed vlan + log.drop("Tag not allowed for ingress"); + return; + } + } + + HostEntry host = hostTable.get(destMac); + if (host != null) { + if (host.iface == side && !ingressSettings.hairpin) { + // if packet is to same port and hairpin is disabled, drop + log.drop("hairpin disabled"); + return; + } + writeToSide(frame, host.iface, vlan, log, timeToLive); + } else { + log.flood(); + for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) { + if (i != side) { + writeToSide(frame, i, vlan, log, timeToLive); + } + } + } + } + + @Override + public byte[] readEthernetFrame() { + return null; + } + + private void writeToSide(byte[] frame, int side, short vlan, SwitchLog log, int timeToLive) { + log.egressPort(side); + NetworkInterface iface = adjacentBlockInterfaces[side]; + if (iface != null) { + PortSettings egressSettings = portSettings[side]; + if (egressSettings.untagged != 0 && vlan == 0) { + log.drop("inner tag untagged"); + return; + } + + if (egressSettings.untagged == vlan) { + Pair pair = removeVLANTag(frame); + frame = pair.getSecond(); + log.egressVlan = 0; + } else if (!(egressSettings.trunkAll || egressSettings.tagged.contains(vlan))) { + // Drop packets with wrong tag + log.drop("Tag not allowed for egress"); + return; + } else { + log.egressVlan = vlan; + } + log.emit(); + iface.writeEthernetFrame(this, frame, timeToLive - TTL_COST); + } + } + + private long macToLong(final byte[] mac, int offset) { + long ret = 0; + for (int i = 0; i < 6; i++) { + ret |= ((((long) mac[i + offset]) & 0xff) << (i * 8)); + } + return ret; + } + + @Override + public void clientTick() + { + return; + } + + @Override + public void serverTick() { + if (level == null) { + return; + } + if (tickCount++ % 20 == 0) { + long threshold = getLevel().getGameTime() - HOST_TTL; + if (threshold < 0) { + return; + } + hostTable.entrySet().removeIf(e -> e.getValue().timestamp < threshold); + } + } + + @Override + public void getDeviceDocumentation(final DeviceVisitor visitor) { + visitor.visitCallback(GET_HOST_TABLE) + .description("Returns the MAC address table of the switch") + .returnValueDescription("The MAC table. For each host the mac address, the age (in ticks) and the face is returned"); + } + + @Override + public Collection getDeviceTypeNames() { + return singletonList("switch"); + } + + @Override + public void saveAdditional(final CompoundTag tag) { + super.saveAdditional(tag); + + ListTag hosts = new ListTag(); + for (Map.Entry host : hostTable.entrySet()) { + CompoundTag thisHost = new CompoundTag(); + thisHost.put("mac", LongTag.valueOf(host.getKey())); + thisHost.put("side", IntTag.valueOf(host.getValue().iface)); + thisHost.put("timestamp", LongTag.valueOf(host.getValue().timestamp)); + hosts.add(thisHost); + } + tag.put("hosts", hosts); + + ListTag ports = new ListTag(); + for (PortSettings myPort : portSettings) { + CompoundTag port = new CompoundTag(); + myPort.save(port); + ports.add(port); + } + tag.put("ports", ports); + } + + @Override + public void load(final CompoundTag tag) { + super.load(tag); + + Tag hosts = tag.get("hosts"); + if (hosts != null) { + for (Tag host_ : ((ListTag) hosts)) { + CompoundTag host = (CompoundTag) host_; + hostTable.put( + host.getLong("mac"), + new HostEntry( + tag.getInt("side"), + tag.getLong("timestamp") + ) + ); + } + } + + Tag ports = tag.get("ports"); + if (ports != null) { + int i = 0; + for (Tag port : ((ListTag) ports)) { + portSettings[i++] = PortSettings.load((CompoundTag) port); + } + } + + } + + @Callback(name = GET_HOST_TABLE) + public List getHostTable() { + long now = getLevel().getGameTime(); + return hostTable + .entrySet() + .stream() + .map(e -> new LuaHostEntry(macLongToString(e.getKey()), now - e.getValue().timestamp, e.getValue().iface)) + .collect(Collectors.toList()); + } + + @Callback(name = GET_PORT_CONFIG, synchronize = false) + public PortSettings[] getPortSettings() { + return portSettings; + } + + @Callback(name = SET_PORT_CONFIG) + public void setPortSettings(List settings) { + int max = Math.min(portSettings.length, settings.size()); + for (int i = 0; i < max; i++) { + portSettings[i].untagged = ((Double) settings.get(i).get("untagged")).shortValue(); + } + } + + @Callback(name = GET_LINK_STATE) + public boolean[] getLinkState() { + validateAdjacentBlocks(); + boolean[] sides = new boolean[Constants.BLOCK_FACE_COUNT]; + for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) { + sides[i] = adjacentBlockInterfaces[i] != null; + } + return sides; + } + + private Optional sideReverseLookup(NetworkInterface iface) { + for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) { + if (iface == adjacentBlockInterfaces[i]) { + return Optional.of(i); + } + } + return Optional.empty(); + } + + private static String macLongToString(long mac) { + StringBuilder ret = new StringBuilder(); + for (int i = 0; i < 6; i++) { + if (i != 0) { + ret.append(":"); + } + ret.append(String.format("%02x", (mac >> (i * 8)) & 0xff)); + } + return ret.toString(); + } + + private byte[] addVLANTag(byte[] packet, short tag) { + if (tag != 0) { + byte[] ret = new byte[packet.length + 4]; + copyBytes(packet, ret, 0, 0, 12); // Copy Ethernet Header + copyBytes(packet, ret, 12, 16, packet.length - 12); + ret[12] = (byte) 0x81; + ret[13] = (byte) 0x00; + ret[14] = (byte) ((tag >> 8) & 0x0f); + ret[15] = (byte) (tag & 0xff); + return ret; + } else { + return packet; + } + } + + private short getVLAN(byte[] packet) { + if (packet[12] == ((byte) 0x81) && packet[13] == 0x00) { + return (short) (packet[15] | ((((short) packet[14]) & 0x0f) << 8)); + } else { + return (short) 0; + } + } + + private Pair removeVLANTag(byte[] packet) { + if (packet[12] == ((byte) 0x81) && packet[13] == 0x00) { + byte[] ret = new byte[packet.length - 4]; + copyBytes(packet, ret, 0, 0, 12); // Copy Ethernet Header + copyBytes(packet, ret, 16, 12, packet.length - 16); // Copy payload + short tag = (short) (packet[15] | ((((short) packet[14]) & 0x0f) << 8)); // Extract vlan tag + return new Pair<>(tag, ret); + } else { + return new Pair<>((short) 0, packet); + } + } + + private void copyBytes(byte[] input, byte[] output, int inputOffset, int outputOffset, int length) { + for (int i = 0; i < length; i++) { + output[outputOffset + i] = input[inputOffset + i]; + } + } + + private void validateAdjacentBlocks() { + if (isRemoved() || !haveAdjacentBlocksChanged) { + return; + } + + for (final Direction side : Constants.DIRECTIONS) { + adjacentBlockInterfaces[side.get3DDataValue()] = null; + } + + haveAdjacentBlocksChanged = false; + + if (level == null || level.isClientSide()) { + return; + } + + final BlockPos pos = getBlockPos(); + for (final Direction side : Constants.DIRECTIONS) { + final BlockEntity neighborBlockEntity = LevelUtils.getBlockEntityIfChunkExists(level, pos.relative(side)); + if (neighborBlockEntity != null) { + final LazyOptional optional = neighborBlockEntity.getCapability(Capabilities.networkInterface(), side.getOpposite()); + optional.ifPresent(adjacentInterface -> { + adjacentBlockInterfaces[side.get3DDataValue()] = adjacentInterface; + LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged()); + }); + } + } + } + + private void handleNeighborChanged() { + haveAdjacentBlocksChanged = true; + } + + private static class HostEntry { + public int iface; + public long timestamp; + public HostEntry(int iface, long timestamp) { + this.iface = iface; + this.timestamp = timestamp; + } + } + + public static class LuaHostEntry { + public String mac; + public long age; + public int side; + + public LuaHostEntry(String mac, long age, int iface) { + this.mac = mac; + this.age = age; + this.side = iface; + } + } + + private static class PortSettings { + /** + * The VLAN that is both PVID and untagged vlan. It will be removed on egress and added on ingress. If set to 0 + * this port is put on the global untagged vlan. The global untagged vlan can ever only be used as an untagged vlan + */ + public short untagged; + /** + * A list of tagged vlans that will be accepted on both ingress and egress. 0 (the global untagged vlan) is not a valid + * value + */ + public List tagged; + /** + * If enabled, packets entering on this port may also leave via this port again + */ + public boolean hairpin; + /** + * If this is set, tagged will be ignored. Instead all tagged vlans will be accepted. untagged will still be honored + */ + public boolean trunkAll; + + public PortSettings(final short untagged, final List tagged, final boolean hairpin, final boolean trunkAll) { + this.untagged = untagged; + this.tagged = tagged; + this.hairpin = hairpin; + this.trunkAll = trunkAll; + } + + /** + * Default configuration of an unmanaged switch, which just forwards all tagged vlans as well as the untagged vlan + * straight through + */ + public PortSettings() { + this((short) 0, emptyList(), false, true); + } + + public void save(final CompoundTag tag) { + tag.put("untagged", ShortTag.valueOf(untagged)); + tag.put("tagged", new IntArrayTag(tagged.stream().map(s -> (int) s).collect(Collectors.toList()))); + tag.put("hairpin", ByteTag.valueOf(hairpin)); + tag.put("trunkAll", ByteTag.valueOf(trunkAll)); + } + + public static PortSettings load(final CompoundTag tag) { + short untagged = tag.getShort("untagged"); + List tagged = Arrays.stream(tag.getIntArray("tagged")) + .mapToObj(i -> (short) i) + .collect(Collectors.toList()); + boolean hairpin = tag.getBoolean("hairpin"); + boolean trunkAll = tag.getBoolean("trunkAll"); + + return new PortSettings(untagged, tagged, hairpin, trunkAll); + } + } + + private static class SwitchLog { + private static final boolean ENABLED = true; + private short ingressVlan = 0; + private short egressVlan = 0; + private int ingressSide = 0; + private final long srcMac; + private final long destMac; + private Integer egressSide = null; + + public SwitchLog(short ingressVlan, int ingressSide, long srcMac, long destMac) { + this.ingressVlan = ingressVlan; + this.ingressSide = ingressSide; + this.srcMac = srcMac; + this.destMac = destMac; + } + + public void egressPort(int side) { + egressSide = side; + } + + public void drop(String reason) { + if (!ENABLED) return; + String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac); + String outMac = NetworkSwitchBlockEntity.macLongToString(destMac); + if (egressSide == null) { + System.out.printf( + "Switch Packet %s (Port %s, VLAN %s) -> %s drop (%s)\n", + inMac, + ingressSide, + ingressVlan, + outMac, + reason + ); + } else { + System.out.printf( + "Switch Packet %s (Port %s, VLAN %s) -> %s (Port %s) drop (%s)\n", + inMac, + ingressSide, + ingressVlan, + outMac, + egressSide, + reason + ); + } + } + + public void emit() { + if (!ENABLED) return; + String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac); + String outMac = NetworkSwitchBlockEntity.macLongToString(destMac); + System.out.printf( + "Switch Packet %s (Port %s, VLAN %s) -> %s (Port %s, VLAN %s)\n", + inMac, + ingressSide, + ingressVlan, + outMac, + egressSide, + egressVlan + ); + } + + public void flood() { + if (!ENABLED) return; + String inMac = NetworkSwitchBlockEntity.macLongToString(srcMac); + String outMac = NetworkSwitchBlockEntity.macLongToString(destMac); + System.out.printf( + "Switch Packet %s (Port %s, VLAN %s) -> %s flood\n", + inMac, + ingressSide, + ingressVlan, + outMac + ); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/blockentity/PciCardCageBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/PciCardCageBlockEntity.java new file mode 100644 index 00000000..3d9d8dbf --- /dev/null +++ b/src/main/java/li/cil/oc2/common/blockentity/PciCardCageBlockEntity.java @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.blockentity; + +import li.cil.oc2.common.Config; +import li.cil.oc2.common.block.PciCardCageBlock; +import li.cil.oc2.common.bus.device.vm.block.PciCardCageDevice; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.energy.FixedEnergyStorage; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.state.BlockState; + +import javax.annotation.Nullable; + + +public final class PciCardCageBlockEntity extends ModBlockEntity implements TickableBlockEntity { + + private static final String ENERGY_TAG_NAME = "energy"; + private static final String HAS_ENERGY_TAG_NAME = "has_energy"; + + /////////////////////////////////////////////////////////////// + + private final PciCardCageDevice cardCageDevice = new PciCardCageDevice(this, this::handleMountedChanged); + private boolean isMounted, hasEnergy; + private final FixedEnergyStorage energy = new FixedEnergyStorage(Config.cardCageEnergyStorage); + + + /////////////////////////////////////////////////////////////// + + public PciCardCageBlockEntity(final BlockPos pos, final BlockState state) { + super(BlockEntities.PCI_CARD_CAGE.get(), pos, state); + } + + /////////////////////////////////////////////////////////////// + + private void handleMountedChanged(final boolean value) { + + } + + + public boolean hasEnergy() { + return hasEnergy; + } + + @Override + public void serverTick() { + if (!isMounted) { + return; + } + + final boolean isPowered; + if (Config.cardCagesUseEnergy()) { + isPowered = energy.extractEnergy(Config.cardCageEnergyPerTick, true) >= Config.cardCageEnergyPerTick; + if (isPowered) { + energy.extractEnergy(Config.cardCageEnergyPerTick, false); + } + } else { + isPowered = true; + } + + + } + + @Override + public CompoundTag getUpdateTag() { + final CompoundTag tag = super.getUpdateTag(); + + tag.putBoolean(HAS_ENERGY_TAG_NAME, hasEnergy); + + return tag; + } + + @Override + public void handleUpdateTag(final CompoundTag tag) { + super.handleUpdateTag(tag); + + hasEnergy = tag.getBoolean(HAS_ENERGY_TAG_NAME); + } + + @Override + protected void saveAdditional(final CompoundTag tag) { + super.saveAdditional(tag); + + tag.put(ENERGY_TAG_NAME, energy.serializeNBT()); + } + + @Override + public void load(final CompoundTag tag) { + super.load(tag); + + energy.deserializeNBT(tag.getCompound(ENERGY_TAG_NAME)); + } + + + @SuppressWarnings("deprecation") + @Override + public void setBlockState(final BlockState state) { + super.setBlockState(state); + + } + + /////////////////////////////////////////////////////////////// + + @Override + protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) { + if (Config.cardCagesUseEnergy()) { + collector.offer(Capabilities.energyStorage(), energy); + } + + if (direction == getBlockState().getValue(PciCardCageBlock.FACING).getOpposite()) { + collector.offer(Capabilities.device(), cardCageDevice); + } + } + + /////////////////////////////////////////////////////////////// + + + + +} diff --git a/src/main/java/li/cil/oc2/common/blockentity/VxlanBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/VxlanBlockEntity.java new file mode 100644 index 00000000..63778158 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/blockentity/VxlanBlockEntity.java @@ -0,0 +1,175 @@ +package li.cil.oc2.common.blockentity; + +import li.cil.oc2.api.capabilities.NetworkInterface; +import li.cil.oc2.common.Config; +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.util.LazyOptionalUtils; +import li.cil.oc2.common.util.LevelUtils; +import li.cil.oc2.common.vxlan.TunnelManager; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.util.LazyOptional; +import org.apache.commons.collections4.QueueUtils; +import org.apache.commons.collections4.queue.CircularFifoQueue; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.stream.Stream; + +public final class VxlanBlockEntity extends ModBlockEntity implements NetworkInterface, TickableBlockEntity { + private static final int TTL_COST = 1; + //private int vti = ((int) (Math.random() * Integer.MAX_VALUE)) & 0x00ff_ffff; + private int vti = 1000; + private int frameCount; + private long lastGameTime; + + private final Queue packetQueue = new ArrayBlockingQueue(32); + + /////////////////////////////////////////////////////////////////// + + // Each face and the default TunnelInterface connecting to the outernet + private final NetworkInterface[] adjacentBlockInterfaces = new NetworkInterface[Constants.BLOCK_FACE_COUNT + 1]; + private boolean haveAdjacentBlocksChanged = true; + + /////////////////////////////////////////////////////////////////// + + public VxlanBlockEntity(final BlockPos pos, final BlockState state) { + super(BlockEntities.VXLAN_HUB.get(), pos, state); + } + + + /////////////////////////////////////////////////////////////////// + + public void handleNeighborChanged() { + haveAdjacentBlocksChanged = true; + } + + @Override + public byte[] readEthernetFrame() { + return null; + } + + @Override + public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) { + if (level == null) { + return; + } + + final long gameTime = level.getGameTime(); + if (gameTime > lastGameTime) { + lastGameTime = gameTime; + frameCount = 1; + } else if (frameCount > Config.hubEthernetFramesPerTick) { + return; + } else { + frameCount++; + } + + getAdjacentInterfaces().forEach(adjacentInterface -> { + if (adjacentInterface != source) { + adjacentInterface.writeEthernetFrame(this, frame, timeToLive - TTL_COST); + } + }); + } + + @Override + public void serverTick() { + if (level == null) { + return; + } + + if (adjacentBlockInterfaces[0] != null) { + // CircularFifoQueue isn't thread-safe, so we have to synchronize on it. + synchronized (packetQueue) { + packetQueue.forEach(packet -> writeEthernetFrame(adjacentBlockInterfaces[0], packet, 255)); + packetQueue.clear(); + } + } else { + System.out.printf("VXLAN block is unregistered upstream: VTI=%d\n", vti); + } + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void load(CompoundTag tag) { + super.load(tag); + if (level != null && !level.isClientSide() && tag.contains("vti")) { + vti = tag.getInt("vti"); + } + } + + @Override + public void saveAdditional(CompoundTag tag) { + super.saveAdditional(tag); + if (level != null && !level.isClientSide()) { + tag.putInt("vti", vti); + } + } + + @Override + protected void onUnload(final boolean isRemove) { + if (level != null && !level.isClientSide()) { + adjacentBlockInterfaces[0] = null; + TunnelManager.instance().unregisterVti(vti); + } + + super.onUnload(isRemove); + } + + @Override + public void loadServer() { + adjacentBlockInterfaces[0] = TunnelManager.instance().registerVti(vti, this.packetQueue); + } + + /////////////////////////////////////////////////////////////////// + + + + @Override + protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) { + collector.offer(Capabilities.networkInterface(), this); + } + + /////////////////////////////////////////////////////////////////// + + private Stream getAdjacentInterfaces() { + validateAdjacentBlocks(); + return Arrays.stream(adjacentBlockInterfaces).filter(Objects::nonNull); + } + + private void validateAdjacentBlocks() { + if (isRemoved() || !haveAdjacentBlocksChanged) { + return; + } + + for (final Direction side : Constants.DIRECTIONS) { + adjacentBlockInterfaces[side.get3DDataValue() + 1] = null; + } + + haveAdjacentBlocksChanged = false; + + if (level == null || level.isClientSide()) { + return; + } + + final BlockPos pos = getBlockPos(); + for (final Direction side : Constants.DIRECTIONS) { + final BlockEntity neighborBlockEntity = LevelUtils.getBlockEntityIfChunkExists(level, pos.relative(side)); + if (neighborBlockEntity != null) { + final LazyOptional optional = neighborBlockEntity.getCapability(Capabilities.networkInterface(), side.getOpposite()); + optional.ifPresent(adjacentInterface -> { + adjacentBlockInterfaces[side.get3DDataValue() + 1] = adjacentInterface; + LazyOptionalUtils.addWeakListener(optional, this, (hub, unused) -> hub.handleNeighborChanged()); + }); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/bus/device/vm/block/PciCardCageDevice.java b/src/main/java/li/cil/oc2/common/bus/device/vm/block/PciCardCageDevice.java new file mode 100644 index 00000000..2bfb8050 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/vm/block/PciCardCageDevice.java @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.bus.device.vm.block; + +import it.unimi.dsi.fastutil.booleans.BooleanConsumer; +import li.cil.oc2.api.bus.device.vm.VMDevice; +import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; +import li.cil.oc2.api.bus.device.vm.context.VMContext; +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.bus.device.util.IdentityProxy; +import li.cil.oc2.common.bus.device.util.OptionalAddress; +import li.cil.oc2.common.serialization.BlobStorage; +import li.cil.oc2.common.util.NBTTagIds; +import li.cil.oc2.common.vm.device.PciRootPortDevice; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.block.entity.BlockEntity; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.UUID; + +public final class PciCardCageDevice extends IdentityProxy implements VMDevice { + private static final String ADDRESS_TAG_NAME = "address"; + private static final String BLOB_HANDLE_TAG_NAME = "blob"; + + public static final int BUS_COUNT = 16; + public static final int WINDOW_SIZE = 16 * 1024 * 1024; + + + /////////////////////////////////////////////////////////////// + + private final BooleanConsumer onMountedChanged; + + @Nullable private PciRootPortDevice device; + + /////////////////////////////////////////////////////////////// + + private final OptionalAddress address = new OptionalAddress(); + @Nullable private UUID blobHandle; + + /////////////////////////////////////////////////////////////// + + public PciCardCageDevice(final BlockEntity identity, final BooleanConsumer onMountedChanged) { + super(identity); + this.onMountedChanged = onMountedChanged; + } + + /////////////////////////////////////////////////////////////// + + @Override + public VMDeviceLoadResult mount(final VMContext context) { + if (!allocateDevice(context)) { + return VMDeviceLoadResult.fail(); + } + + assert device != null; + if (!address.claim(context, device)) { + return VMDeviceLoadResult.fail(); + } + + onMountedChanged.accept(true); + + return VMDeviceLoadResult.success(); + } + + @Override + public void unmount() { + final PciRootPortDevice pciRootPortDevice = device; + device = null; + if (pciRootPortDevice != null) { + pciRootPortDevice.close(); + } + + if (blobHandle != null) { + BlobStorage.close(blobHandle); + } + + onMountedChanged.accept(false); + } + + @Override + public void dispose() { + if (blobHandle != null) { + BlobStorage.delete(blobHandle); + blobHandle = null; + } + + address.clear(); + } + + @Override + public CompoundTag serializeNBT() { + final CompoundTag tag = new CompoundTag(); + + if (blobHandle != null) { + tag.putUUID(BLOB_HANDLE_TAG_NAME, blobHandle); + } + if (address.isPresent()) { + tag.putLong(ADDRESS_TAG_NAME, address.getAsLong()); + } + + return tag; + } + + @Override + public void deserializeNBT(final CompoundTag tag) { + if (tag.hasUUID(BLOB_HANDLE_TAG_NAME)) { + blobHandle = tag.getUUID(BLOB_HANDLE_TAG_NAME); + } + if (tag.contains(ADDRESS_TAG_NAME, NBTTagIds.TAG_LONG)) { + address.set(tag.getLong(ADDRESS_TAG_NAME)); + } + } + + /////////////////////////////////////////////////////////////// + + private boolean allocateDevice(final VMContext context) { + if (!context.getMemoryAllocator().claimMemory(Constants.PAGE_SIZE)) { + return false; + } + + try { + device = createPciRootPortDevice(); + } catch (final IOException e) { + return false; + } + + return true; + } + + private PciRootPortDevice createPciRootPortDevice() throws IOException { + blobHandle = BlobStorage.validateHandle(blobHandle); + final FileChannel channel = BlobStorage.getOrOpen(blobHandle); + final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, WINDOW_SIZE * 2); + return new PciRootPortDevice(BUS_COUNT, WINDOW_SIZE, buffer); + } +} diff --git a/src/main/java/li/cil/oc2/common/item/Items.java b/src/main/java/li/cil/oc2/common/item/Items.java index ed913f48..a3cda6eb 100644 --- a/src/main/java/li/cil/oc2/common/item/Items.java +++ b/src/main/java/li/cil/oc2/common/item/Items.java @@ -34,8 +34,11 @@ public final class Items { public static final RegistryObject KEYBOARD = register(Blocks.KEYBOARD); public static final RegistryObject NETWORK_CONNECTOR = register(Blocks.NETWORK_CONNECTOR); public static final RegistryObject NETWORK_HUB = register(Blocks.NETWORK_HUB); + public static final RegistryObject NETWORK_SWITCH = register(Blocks.NETWORK_SWITCH); public static final RegistryObject PROJECTOR = register(Blocks.PROJECTOR); public static final RegistryObject REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE); + public static final RegistryObject VXLAN_HUB = register(Blocks.VXLAN_HUB); + public static final RegistryObject PCI_CARD_CAGE = register(Blocks.PCI_CARD_CAGE); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/vm/device/PciRootPortDevice.java b/src/main/java/li/cil/oc2/common/vm/device/PciRootPortDevice.java new file mode 100644 index 00000000..4d44b457 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/device/PciRootPortDevice.java @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.device; + +import li.cil.sedna.api.device.MemoryMappedDevice; +import li.cil.sedna.api.memory.MemoryAccessException; +import li.cil.sedna.utils.DirectByteBufferUtils; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + +public final class PciRootPortDevice implements MemoryMappedDevice { + + + /////////////////////////////////////////////////////////////// + + private final ByteBuffer buffer; + private int length; + + /////////////////////////////////////////////////////////////// + + public PciRootPortDevice(final int bus_count, final int window_size, final ByteBuffer buffer) { + + length = window_size * 2; + if (buffer.capacity() < length) { + throw new IllegalArgumentException("Buffer too small."); + } + + this.buffer = buffer.order(ByteOrder.LITTLE_ENDIAN); + + this.buffer.putInt(0, 0x12345678); + this.buffer.putInt(4, 2); + this.buffer.putInt(8, 0xFF000000); + this.buffer.putInt(12, 0x00000101); + this.buffer.putInt(0x10, 0x00000000); + this.buffer.putInt(0x2C, 0x12345678); + + + } + + /////////////////////////////////////////////////////////////// + + public void close() { + synchronized (buffer) { + length = 0; + DirectByteBufferUtils.release(buffer); + } + } + + + public boolean hasChanges() { + return false; + } + + + @Override + public int getLength() { + return length; + } + + @Override + public long load(final int offset, final int sizeLog2) throws MemoryAccessException { + if (offset >= 0 && offset <= length - (1 << sizeLog2)) { + System.out.println(String.format("PCI config read: %x %x", offset, sizeLog2)); + if (offset == 0x10) { + long res = buffer.getInt(offset); + System.out.println(String.format(" 00:00.0 BAR0 read %x", res)); + res = res & 0xFFFFF000L; + System.out.println(String.format("Clipped 00:00.0 BAR0 read to %x", res)); + return res; + } + return switch (sizeLog2) { + case 0 -> buffer.get(offset); + case 1 -> buffer.getShort(offset); + case 2 -> buffer.getInt(offset); + case 3 -> buffer.getLong(offset); + default -> throw new IllegalArgumentException(); + }; + } else { + return 0; + } + } + + @Override + public void store(final int offset, final long value, final int sizeLog2) throws MemoryAccessException { + if (offset >= 0 && offset <= length - (1 << sizeLog2)) { + System.out.println(String.format("PCI config write: %x %x %x", offset, value, sizeLog2)); + switch (sizeLog2) { + case 0 -> buffer.put(offset, (byte) value); + case 1 -> buffer.putShort(offset, (short) value); + case 2 -> buffer.putInt(offset, (int) value); + case 3 -> buffer.putLong(offset, value); + default -> throw new IllegalArgumentException(); + } + } + } + + /////////////////////////////////////////////////////////////// + +} diff --git a/src/main/java/li/cil/oc2/common/vm/provider/DeviceTreeProviders.java b/src/main/java/li/cil/oc2/common/vm/provider/DeviceTreeProviders.java index 5ee7bd4b..912a47f6 100644 --- a/src/main/java/li/cil/oc2/common/vm/provider/DeviceTreeProviders.java +++ b/src/main/java/li/cil/oc2/common/vm/provider/DeviceTreeProviders.java @@ -2,11 +2,13 @@ package li.cil.oc2.common.vm.provider; +import li.cil.oc2.common.vm.device.PciRootPortDevice; import li.cil.oc2.common.vm.device.SimpleFramebufferDevice; import li.cil.sedna.devicetree.DeviceTreeRegistry; public final class DeviceTreeProviders { public static void initialize() { DeviceTreeRegistry.putProvider(SimpleFramebufferDevice.class, new SimpleFramebufferDeviceProvider()); + DeviceTreeRegistry.putProvider(PciRootPortDevice.class, new PciRootPortDeviceProvider()); } } diff --git a/src/main/java/li/cil/oc2/common/vm/provider/PciRootPortDeviceProvider.java b/src/main/java/li/cil/oc2/common/vm/provider/PciRootPortDeviceProvider.java new file mode 100644 index 00000000..514af517 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/provider/PciRootPortDeviceProvider.java @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.vm.provider; + +import li.cil.oc2.common.vm.device.PciRootPortDevice; +import li.cil.sedna.api.device.Device; +import li.cil.sedna.api.device.MemoryMappedDevice; +import li.cil.sedna.api.devicetree.DeviceNames; +import li.cil.sedna.api.devicetree.DevicePropertyNames; +import li.cil.sedna.api.devicetree.DeviceTree; +import li.cil.sedna.api.devicetree.DeviceTreeProvider; +import li.cil.sedna.api.memory.MappedMemoryRange; +import li.cil.sedna.api.memory.MemoryMap; + +import java.util.Optional; + +public final class PciRootPortDeviceProvider implements DeviceTreeProvider { + @Override + public Optional getName(final Device device) { + return Optional.of("pci"); + } + + @Override + public Optional createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) { + final Optional range = memoryMap.getMemoryRange((MemoryMappedDevice) device); + return range.map(r -> { + final DeviceTree pci = root.find("/pci"); + return pci.getChild(deviceName, r.address()); + }); + } + + @Override + public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) { + final PciRootPortDevice pr = (PciRootPortDevice) device; + final Optional range = memoryMap.getMemoryRange((MemoryMappedDevice) device); + node + .addProp(DevicePropertyNames.COMPATIBLE, "pci-host-cam-generic") + .addProp(DevicePropertyNames.DEVICE_TYPE, DeviceNames.PCI) + .addProp(DevicePropertyNames.NUM_ADDRESS_CELLS,3) + .addProp(DevicePropertyNames.NUM_SIZE_CELLS, 2) + .addProp("bus-range", 0, 1) + //.addProp("linux,pci-probe-only", 1) + .addProp(DevicePropertyNames.RANGES, + // type pci.hi pci.lo cpu.hi cpu.lo len.hi len.lo + 0x02000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x20000000); // + } +} + diff --git a/src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java b/src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java new file mode 100644 index 00000000..def122ef --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vxlan/TunnelManager.java @@ -0,0 +1,162 @@ +package li.cil.oc2.common.vxlan; + +import li.cil.oc2.api.capabilities.NetworkInterface; +import li.cil.oc2.common.Config; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.net.*; +import java.util.HashMap; +import java.util.Queue; + +public class TunnelManager { + private static final Logger LOGGER = LogManager.getLogger(); + + private final HashMap tunnels = new HashMap<>(); + private DatagramSocket socket; + private static TunnelManager INSTANCE; + private final InetAddress remoteHost; + private final short remotePort; + private final InetAddress bindHost; + private final short bindPort; + + public TunnelManager(InetAddress bindHost, short bindPort, InetAddress remoteHost, short remotePort) throws SocketException { + this.remoteHost = remoteHost; + this.remotePort = remotePort; + this.bindHost = bindHost; + this.bindPort = bindPort; + } + + public static void initialize() { + LOGGER.info("Initializing outernet tunnel manager"); + + try { + INSTANCE = new TunnelManager( + InetAddress.getByName(Config.bindHost), (short) Config.bindPort, + InetAddress.getByName(Config.remoteHost), (short) Config.remotePort + ); + } catch (SocketException | UnknownHostException e) { + LOGGER.error("Failed to bind to configured address: " + e.getMessage()); + LOGGER.error(e); + } + + if (Config.enable) { + Thread bgThread = new Thread(() -> { + try { + INSTANCE.listen(); + } catch (IOException e) { + LOGGER.error(e); + } + }); + bgThread.setName("VXLAN Background Thread"); + bgThread.start(); + } + } + + public void listen() throws IOException { + LOGGER.printf(Level.INFO, "Binding %s:%s\n", bindHost, bindPort); + + if (Config.enable) { + socket = new DatagramSocket(bindPort, bindHost); + } else { + return; + } + LOGGER.printf(Level.INFO, "Bind successful: connected=%s bound=%s\n", socket.isConnected(), socket.isBound()); + + byte[] buffer = new byte[65535]; + // TODO shut this thread down more cleanly on server shutdown? + //noinspection InfiniteLoopStatement + while (true) { + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + socket.receive(packet); + + if (packet.getLength() < 8) { + continue; + } + + byte flags = packet.getData()[0]; + int vni = (packet.getData()[6] & 0xFF ) + | ( ( packet.getData()[5] & 0xFF ) << 8 ) + | ( ( packet.getData()[4] & 0xFF ) << 16 ); + + if ((flags & 0x08) != 0x08) { + continue; + } + + LOGGER.debug("recv on vti " + vni); + + TunnelInterface iface = tunnels.get(vni); + + if (iface != null) { + byte[] inner = new byte[packet.getLength() - 8]; + System.arraycopy(packet.getData(), 8, inner, 0, packet.getLength() - 8); + + // CircularFifoQueue isn't thread-safe, so we have to synchronize on it. + synchronized (iface.packetQueue) { + iface.packetQueue.offer(inner); + } + } + } + } + + public static TunnelManager instance() { + return INSTANCE; + } + + public void sendToOuternet(int vti, byte[] payload) { + if (socket != null) { + + byte[] buffer = new byte[payload.length + 8]; + + System.arraycopy(payload, 0, buffer, 8, payload.length); + + buffer[0] = 0x08; + buffer[4] = (byte) ((vti >> 16) & 0xff); + buffer[5] = (byte) ((vti >> 8) & 0xff); + buffer[6] = (byte) (vti & 0xff); + + DatagramPacket packet = new DatagramPacket(buffer, buffer.length, this.remoteHost, this.remotePort); + + try { + socket.send(packet); + } catch (IOException e) { + LOGGER.error(e); + } + } else { + LOGGER.error("No socket in TunnelManager\n"); + } + } + + public NetworkInterface registerVti(int vti, Queue packetQueue) { + TunnelInterface tuniface = new TunnelInterface(vti, packetQueue); + tunnels.put(vti, tuniface); + return tuniface; + } + + public void unregisterVti(int vti) { + tunnels.remove(vti); + } + + public class TunnelInterface implements NetworkInterface { + final Queue packetQueue; + private final int vti; + + public TunnelInterface(int vti, Queue packetQueue) { + this.vti = vti; + this.packetQueue = packetQueue; + } + + @Override + public byte[] readEthernetFrame() { + return null; + } + + @Override + public void writeEthernetFrame(final @NotNull NetworkInterface source, final byte @NotNull [] frame, final int timeToLive) { + TunnelManager.this.sendToOuternet(vti, frame); + } + } +} diff --git a/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java b/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java index 118d7d38..ce0b170f 100644 --- a/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java +++ b/src/main/java/li/cil/oc2/data/ModBlockStateProvider.java @@ -29,6 +29,8 @@ public final class ModBlockStateProvider extends BlockStateProvider { private static final ResourceLocation NETWORK_HUB_MODEL = new ResourceLocation(API.MOD_ID, "block/network_hub"); private static final ResourceLocation PROJECTOR_MODEL = new ResourceLocation(API.MOD_ID, "block/projector"); private static final ResourceLocation REDSTONE_INTERFACE_MODEL = new ResourceLocation(API.MOD_ID, "block/redstone_interface"); + private static final ResourceLocation PCI_CARD_CAGE_MODEL = new ResourceLocation(API.MOD_ID, "block/pci_card_cage"); + public ModBlockStateProvider(final DataGenerator generator, final ExistingFileHelper existingFileHelper) { super(generator, API.MOD_ID, existingFileHelper); @@ -55,8 +57,10 @@ public final class ModBlockStateProvider extends BlockStateProvider { .end() .end(); horizontalBlock(Blocks.NETWORK_HUB, Items.NETWORK_HUB, NETWORK_HUB_MODEL); + horizontalBlock(Blocks.NETWORK_SWITCH, Items.NETWORK_SWITCH, NETWORK_HUB_MODEL); horizontalBlock(Blocks.PROJECTOR, Items.PROJECTOR, PROJECTOR_MODEL); horizontalBlock(Blocks.REDSTONE_INTERFACE, Items.REDSTONE_INTERFACE, REDSTONE_INTERFACE_MODEL); + horizontalBlock(Blocks.PCI_CARD_CAGE, Items.PCI_CARD_CAGE, PCI_CARD_CAGE_MODEL); registerCableStates(); } diff --git a/src/main/resources/META-INF/libraries/commons-collections4-4.4.jar.meta b/src/main/resources/META-INF/libraries/commons-collections4-4.4.jar.meta new file mode 100644 index 00000000..7f6c74b1 --- /dev/null +++ b/src/main/resources/META-INF/libraries/commons-collections4-4.4.jar.meta @@ -0,0 +1 @@ +Maven-Artifact: org.apache.commons:commons-collections4:4.4 diff --git a/src/main/resources/assets/oc2/blockstates/network_switch.json b/src/main/resources/assets/oc2/blockstates/network_switch.json new file mode 100644 index 00000000..cb558822 --- /dev/null +++ b/src/main/resources/assets/oc2/blockstates/network_switch.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=north": { + "model": "oc2:block/network_switch" + }, + "facing=south": { + "model": "oc2:block/network_switch", + "y": 180 + }, + "facing=west": { + "model": "oc2:block/network_switch", + "y": 270 + }, + "facing=east": { + "model": "oc2:block/network_switch", + "y": 90 + } + } +} diff --git a/src/main/resources/assets/oc2/blockstates/pci_card_cage.json b/src/main/resources/assets/oc2/blockstates/pci_card_cage.json new file mode 100644 index 00000000..769b106f --- /dev/null +++ b/src/main/resources/assets/oc2/blockstates/pci_card_cage.json @@ -0,0 +1,34 @@ +{ + "variants": { + "facing=north,lit=false": { + "model": "oc2:block/pci_card_cage" + }, + "facing=south,lit=false": { + "model": "oc2:block/pci_card_cage", + "y": 180 + }, + "facing=west,lit=false": { + "model": "oc2:block/pci_card_cage", + "y": 270 + }, + "facing=east,lit=false": { + "model": "oc2:block/pci_card_cage", + "y": 90 + }, + "facing=north,lit=true": { + "model": "oc2:block/pci_card_cage" + }, + "facing=south,lit=true": { + "model": "oc2:block/pci_card_cage", + "y": 180 + }, + "facing=west,lit=true": { + "model": "oc2:block/pci_card_cage", + "y": 270 + }, + "facing=east,lit=true": { + "model": "oc2:block/pci_card_cage", + "y": 90 + } + } +} diff --git a/src/main/resources/assets/oc2/blockstates/vxlan_hub.json b/src/main/resources/assets/oc2/blockstates/vxlan_hub.json new file mode 100644 index 00000000..c951dc4e --- /dev/null +++ b/src/main/resources/assets/oc2/blockstates/vxlan_hub.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=north": { + "model": "oc2:block/vxlan_hub" + }, + "facing=south": { + "model": "oc2:block/vxlan_hub", + "y": 180 + }, + "facing=west": { + "model": "oc2:block/vxlan_hub", + "y": 270 + }, + "facing=east": { + "model": "oc2:block/vxlan_hub", + "y": 90 + } + } +} diff --git a/src/main/resources/assets/oc2/models/block/network_switch.json b/src/main/resources/assets/oc2/models/block/network_switch.json new file mode 100644 index 00000000..36b365b8 --- /dev/null +++ b/src/main/resources/assets/oc2/models/block/network_switch.json @@ -0,0 +1,293 @@ +{ + "parent": "block/block", + "textures": { + "particle": "oc2:block/network_switch/network_switch_atlas0", + "atlas0": "oc2:block/network_switch/network_switch_atlas0", + "atlas1": "oc2:block/network_switch/network_switch_atlas1", + "atlas2": "oc2:block/network_switch/network_switch_atlas2", + "atlas3": "oc2:block/network_switch/network_switch_atlas3" + }, + "elements": [ + { + "from": [0, 0, 10], + "to": [16, 1, 16], + "faces": { + "north": {"uv": [0, 3, 8, 3.5], "texture": "#atlas0"}, + "east": {"uv": [8, 7, 11, 7.5], "texture": "#atlas1", "cullface": "east"}, + "south": {"uv": [0, 3.5, 8, 4], "texture": "#atlas0", "cullface": "south"}, + "west": {"uv": [8, 7.5, 11, 8], "texture": "#atlas1", "cullface": "west"}, + "down": {"uv": [0, 0, 8, 3], "texture": "#atlas0", "cullface": "down"} + } + }, + { + "from": [0, 0, 6], + "to": [6, 1, 10], + "faces": { + "north": {"uv": [11, 7.5, 14, 8], "texture": "#atlas1"}, + "east": {"uv": [14, 7.5, 16, 8], "texture": "#atlas1"}, + "south": {"uv": [11, 7, 14, 7.5], "texture": "#atlas1"}, + "west": {"uv": [14, 7, 16, 7.5], "texture": "#atlas1", "cullface": "west"}, + "down": {"uv": [0, 0, 3, 2], "texture": "#atlas3", "cullface": "down"} + } + }, + { + "from": [10, 0, 6], + "to": [16, 1, 10], + "faces": { + "north": {"uv": [0, 4, 3, 4.5], "texture": "#atlas3"}, + "east": {"uv": [9, 14.5, 11, 15], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [0, 4.5, 3, 5], "texture": "#atlas3"}, + "west": {"uv": [11, 14.5, 13, 15], "texture": "#atlas3"}, + "down": {"uv": [0, 2, 3, 4], "texture": "#atlas3", "cullface": "down"} + } + }, + { + "from": [0, 0, 0], + "to": [16, 1, 6], + "faces": { + "north": {"uv": [0, 7, 8, 7.5], "texture": "#atlas0", "cullface": "north"}, + "east": {"uv": [0, 5, 3, 5.5], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [0, 7.5, 8, 8], "texture": "#atlas0"}, + "west": {"uv": [0, 5.5, 3, 6], "texture": "#atlas3", "cullface": "west"}, + "down": {"uv": [0, 4, 8, 7], "texture": "#atlas0", "cullface": "down"} + } + }, + { + "from": [0, 1, 0], + "to": [16, 6, 16], + "faces": { + "north": {"uv": [0, 13, 8, 15.5], "texture": "#atlas0", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 10.5], "texture": "#atlas0", "cullface": "east"}, + "south": {"uv": [8, 13, 16, 15.5], "texture": "#atlas0", "cullface": "south"}, + "west": {"uv": [0, 10.5, 8, 13], "texture": "#atlas0", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#atlas1"}, + "down": {"uv": [0, 8, 8, 16], "texture": "#atlas1"} + } + }, + { + "from": [0, 6, 15], + "to": [6, 10, 16], + "faces": { + "east": {"uv": [15, 0, 15.5, 2], "texture": "#atlas2"}, + "south": {"uv": [0, 7, 3, 9], "texture": "#atlas3", "cullface": "south"}, + "west": {"uv": [15.5, 0, 16, 2], "texture": "#atlas2", "cullface": "west"}, + "up": {"uv": [0, 6, 3, 6.5], "texture": "#atlas3"}, + "down": {"uv": [0, 6.5, 3, 7], "texture": "#atlas3"} + } + }, + { + "from": [10, 6, 15], + "to": [16, 10, 16], + "faces": { + "east": {"uv": [15, 2, 15.5, 4], "texture": "#atlas2", "cullface": "east"}, + "south": {"uv": [0, 10, 3, 12], "texture": "#atlas3", "cullface": "south"}, + "west": {"uv": [15.5, 2, 16, 4], "texture": "#atlas2"}, + "up": {"uv": [0, 9, 3, 9.5], "texture": "#atlas3"}, + "down": {"uv": [0, 9.5, 3, 10], "texture": "#atlas3"} + } + }, + { + "from": [1, 6, 6], + "to": [15, 10, 10], + "faces": { + "north": {"uv": [8, 4, 15, 6], "texture": "#atlas2"}, + "east": {"uv": [13, 10, 15, 12], "texture": "#atlas3"}, + "south": {"uv": [8, 6, 15, 8], "texture": "#atlas2"}, + "west": {"uv": [3, 7, 5, 9], "texture": "#atlas3"}, + "up": {"uv": [8, 0, 15, 2], "texture": "#atlas2"}, + "down": {"uv": [8, 2, 15, 4], "texture": "#atlas2"} + } + }, + { + "from": [0, 6, 1], + "to": [16, 10, 6], + "faces": { + "north": {"uv": [8, 13, 16, 15], "texture": "#atlas1"}, + "east": {"uv": [8, 10, 10.5, 12], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 0, 16, 2], "texture": "#atlas1"}, + "west": {"uv": [10.5, 10, 13, 12], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 8, 16, 10.5], "texture": "#atlas1"}, + "down": {"uv": [8, 10.5, 16, 13], "texture": "#atlas1"} + } + }, + { + "from": [0, 6, 0], + "to": [6, 10, 1], + "faces": { + "north": {"uv": [0, 13, 3, 15], "texture": "#atlas3", "cullface": "north"}, + "east": {"uv": [15, 4, 15.5, 6], "texture": "#atlas2"}, + "west": {"uv": [15.5, 4, 16, 6], "texture": "#atlas2", "cullface": "west"}, + "up": {"uv": [0, 12, 3, 12.5], "texture": "#atlas3"}, + "down": {"uv": [0, 12.5, 3, 13], "texture": "#atlas3"} + } + }, + { + "from": [10, 6, 0], + "to": [16, 10, 1], + "faces": { + "north": {"uv": [3, 13, 6, 15], "texture": "#atlas3", "cullface": "north"}, + "east": {"uv": [15, 6, 15.5, 8], "texture": "#atlas2", "cullface": "east"}, + "west": {"uv": [15.5, 6, 16, 8], "texture": "#atlas2"}, + "up": {"uv": [0, 15, 3, 15.5], "texture": "#atlas3"}, + "down": {"uv": [0, 15.5, 3, 16], "texture": "#atlas3"} + } + }, + { + "from": [0, 10, 0], + "to": [16, 15, 16], + "faces": { + "north": {"uv": [8, 8, 16, 10.5], "texture": "#atlas2", "cullface": "north"}, + "east": {"uv": [8, 2, 16, 4.5], "texture": "#atlas1", "cullface": "east"}, + "south": {"uv": [8, 10.5, 16, 13], "texture": "#atlas2", "cullface": "south"}, + "west": {"uv": [8, 4.5, 16, 7], "texture": "#atlas1", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#atlas2"}, + "down": {"uv": [0, 8, 8, 16], "texture": "#atlas2"} + } + }, + { + "from": [0, 15, 13], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 15.5, 8, 16], "texture": "#atlas0"}, + "east": {"uv": [11, 13, 12.5, 13.5], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas0", "cullface": "south"}, + "west": {"uv": [12.5, 13, 14, 13.5], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 13, 16, 14.5], "texture": "#atlas2", "cullface": "up"} + } + }, + { + "from": [0, 15, 12], + "to": [6, 16, 13], + "faces": { + "north": {"uv": [6, 15.5, 9, 16], "texture": "#atlas3"}, + "east": {"uv": [15.5, 13, 16, 13.5], "texture": "#atlas3"}, + "south": {"uv": [9, 15.5, 12, 16], "texture": "#atlas3"}, + "west": {"uv": [13.5, 12.5, 14, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [3, 15.5, 6, 16], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [10, 15, 12], + "to": [16, 16, 13], + "faces": { + "north": {"uv": [3, 15, 6, 15.5], "texture": "#atlas3"}, + "east": {"uv": [14, 12.5, 14.5, 13], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [6, 15, 9, 15.5], "texture": "#atlas3"}, + "west": {"uv": [14.5, 12.5, 15, 13], "texture": "#atlas3"}, + "up": {"uv": [12, 15.5, 15, 16], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [0, 15, 10], + "to": [16, 16, 12], + "faces": { + "north": {"uv": [8, 7.5, 16, 8], "texture": "#atlas0"}, + "east": {"uv": [15, 15.5, 16, 16], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 7, 16, 7.5], "texture": "#atlas0"}, + "west": {"uv": [15, 15, 16, 15.5], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 6, 16, 7], "texture": "#atlas0", "cullface": "up"} + } + }, + { + "from": [0, 15, 6], + "to": [3, 16, 10], + "faces": { + "north": {"uv": [14, 13, 15.5, 13.5], "texture": "#atlas3"}, + "east": {"uv": [13, 14.5, 15, 15], "texture": "#atlas3"}, + "south": {"uv": [3, 12.5, 4.5, 13], "texture": "#atlas3"}, + "west": {"uv": [9, 14, 11, 14.5], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [3, 0, 4.5, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [4, 15, 6], + "to": [6, 16, 10], + "faces": { + "north": {"uv": [15, 14.5, 16, 15], "texture": "#atlas3"}, + "east": {"uv": [11, 14, 13, 14.5], "texture": "#atlas3"}, + "south": {"uv": [15, 14, 16, 14.5], "texture": "#atlas3"}, + "west": {"uv": [13, 14, 15, 14.5], "texture": "#atlas3"}, + "up": {"uv": [4.5, 0, 5.5, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [10, 15, 6], + "to": [12, 16, 10], + "faces": { + "north": {"uv": [15, 13.5, 16, 14], "texture": "#atlas3"}, + "east": {"uv": [9, 13.5, 11, 14], "texture": "#atlas3"}, + "south": {"uv": [10.5, 12.5, 11.5, 13], "texture": "#atlas3"}, + "west": {"uv": [11, 13.5, 13, 14], "texture": "#atlas3"}, + "up": {"uv": [5.5, 0, 6.5, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [13, 15, 6], + "to": [16, 16, 10], + "faces": { + "north": {"uv": [4.5, 12.5, 6, 13], "texture": "#atlas3"}, + "east": {"uv": [13, 13.5, 15, 14], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [6, 12.5, 7.5, 13], "texture": "#atlas3"}, + "west": {"uv": [9, 13, 11, 13.5], "texture": "#atlas3"}, + "up": {"uv": [6.5, 0, 8, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [0, 15, 4], + "to": [16, 16, 6], + "faces": { + "north": {"uv": [8, 3.5, 16, 4], "texture": "#atlas0"}, + "east": {"uv": [11.5, 12.5, 12.5, 13], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 3, 16, 3.5], "texture": "#atlas0"}, + "west": {"uv": [12.5, 12.5, 13.5, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 2, 16, 3], "texture": "#atlas0", "cullface": "up"} + } + }, + { + "from": [0, 15, 3], + "to": [6, 16, 4], + "faces": { + "north": {"uv": [12, 15, 15, 15.5], "texture": "#atlas3"}, + "east": {"uv": [15, 12.5, 15.5, 13], "texture": "#atlas3"}, + "south": {"uv": [6, 13, 9, 13.5], "texture": "#atlas3"}, + "west": {"uv": [15.5, 12.5, 16, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [9, 15, 12, 15.5], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [10, 15, 3], + "to": [16, 16, 4], + "faces": { + "north": {"uv": [6, 14, 9, 14.5], "texture": "#atlas3"}, + "east": {"uv": [3, 12, 3.5, 12.5], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [6, 14.5, 9, 15], "texture": "#atlas3"}, + "west": {"uv": [3.5, 12, 4, 12.5], "texture": "#atlas3"}, + "up": {"uv": [6, 13.5, 9, 14], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [0, 15, 0], + "to": [16, 16, 3], + "faces": { + "north": {"uv": [8, 15, 16, 15.5], "texture": "#atlas1", "cullface": "north"}, + "east": {"uv": [7.5, 12.5, 9, 13], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas1"}, + "west": {"uv": [9, 12.5, 10.5, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 14.5, 16, 16], "texture": "#atlas2", "cullface": "up"} + } + }, + { + "from": [0, 6, 10], + "to": [16, 10, 15], + "faces": { + "north": {"uv": [8, 4, 16, 6], "texture": "#atlas0"}, + "east": {"uv": [3, 10, 5.5, 12], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 0, 16, 2], "texture": "#atlas0"}, + "west": {"uv": [5.5, 10, 8, 12], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 10.5, 16, 13], "texture": "#atlas0"}, + "down": {"uv": [8, 8, 16, 10.5], "texture": "#atlas0"} + } + } + ], + "display": {} +} diff --git a/src/main/resources/assets/oc2/models/block/pci_card_cage.json b/src/main/resources/assets/oc2/models/block/pci_card_cage.json new file mode 100644 index 00000000..1267853b --- /dev/null +++ b/src/main/resources/assets/oc2/models/block/pci_card_cage.json @@ -0,0 +1 @@ +{"parent":"block/block","textures":{"atlas0":"oc2:block/pci_card_cage/pci_card_cage_atlas0","atlas1":"oc2:block/pci_card_cage/pci_card_cage_atlas1","atlas2":"oc2:block/pci_card_cage/pci_card_cage_atlas2","atlas3":"oc2:block/pci_card_cage/pci_card_cage_atlas3","atlas4":"oc2:block/pci_card_cage/pci_card_cage_atlas4","atlas5":"oc2:block/pci_card_cage/pci_card_cage_atlas5","particle":"#atlas0"},"elements":[{"from":[0,0,0],"to":[16,2,16],"faces":{"east":{"texture":"atlas0","cullface":"east","uv":[0.0,0.0,8.0,1.0]},"west":{"texture":"atlas0","cullface":"west","uv":[0.0,1.0,8.0,2.0]},"up":{"texture":"atlas0","uv":[0.0,2.0,8.0,10.0]},"down":{"texture":"atlas0","cullface":"down","uv":[8.0,2.0,16.0,10.0]},"north":{"texture":"atlas0","cullface":"north","uv":[0.0,10.0,8.0,11.0]},"south":{"texture":"atlas0","cullface":"south","uv":[0.0,11.0,8.0,12.0]}}},{"from":[0,2,14],"to":[16,6,16],"faces":{"east":{"texture":"atlas4","cullface":"east","uv":[15.0,4.0,16.0,6.0]},"west":{"texture":"atlas4","cullface":"west","uv":[15.0,6.0,16.0,8.0]},"up":{"texture":"atlas0","uv":[0.0,12.0,8.0,13.0]},"north":{"texture":"atlas0","uv":[0.0,13.0,8.0,15.0]},"south":{"texture":"atlas0","cullface":"south","uv":[8.0,13.0,16.0,15.0]}}},{"from":[1,2,2],"to":[15,6,14],"faces":{"east":{"texture":"atlas3","uv":[7.0,12.0,13.0,14.0]},"west":{"texture":"atlas4","uv":[8.0,14.0,14.0,16.0]},"up":{"texture":"atlas4","uv":[8.0,8.0,15.0,14.0]},"down":{"texture":"atlas4","uv":[8.0,0.0,15.0,6.0]},"north":{"texture":"atlas3","uv":[0.0,12.0,7.0,14.0]}}},{"from":[0,2,0],"to":[16,3,2],"faces":{"east":{"texture":"atlas2","cullface":"east","uv":[15.0,15.5,16.0,16.0]},"west":{"texture":"atlas2","cullface":"west","uv":[15.0,15.0,16.0,15.5]},"up":{"texture":"atlas0","uv":[0.0,15.0,8.0,16.0]},"north":{"texture":"atlas0","cullface":"north","uv":[8.0,15.0,16.0,15.5]},"south":{"texture":"atlas0","uv":[8.0,15.5,16.0,16.0]}}},{"from":[0,3,1],"to":[16,4,2],"faces":{"east":{"texture":"atlas3","cullface":"east","uv":[15.5,15.5,16.0,16.0]},"west":{"texture":"atlas3","cullface":"west","uv":[15.5,15.0,16.0,15.5]},"up":{"texture":"atlas0","uv":[8.0,12.0,16.0,12.5]},"north":{"texture":"atlas0","uv":[8.0,12.5,16.0,13.0]},"south":{"texture":"atlas0","uv":[8.0,11.0,16.0,11.5]}}},{"from":[0,3,0],"to":[7,4,1],"faces":{"east":{"texture":"atlas3","uv":[15.5,14.5,16.0,15.0]},"west":{"texture":"atlas3","cullface":"west","uv":[15.5,14.0,16.0,14.5]},"up":{"texture":"atlas3","uv":[12.0,15.5,15.5,16.0]},"north":{"texture":"atlas3","cullface":"north","uv":[4.0,15.0,7.5,15.5]}}},{"from":[9,3,0],"to":[16,4,1],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[4.0,14.5,4.5,15.0]},"west":{"texture":"atlas5","uv":[4.5,14.5,5.0,15.0]},"up":{"texture":"atlas3","uv":[7.5,15.0,11.0,15.5]},"north":{"texture":"atlas3","cullface":"north","uv":[11.0,15.0,14.5,15.5]}}},{"from":[0,4,0],"to":[4,12,2],"faces":{"east":{"texture":"atlas4","uv":[15.0,0.0,16.0,4.0]},"west":{"texture":"atlas4","cullface":"west","uv":[15.0,8.0,16.0,12.0]},"up":{"texture":"atlas4","uv":[14.0,14.0,16.0,15.0]},"down":{"texture":"atlas4","uv":[14.0,15.0,16.0,16.0]},"north":{"texture":"atlas5","cullface":"north","uv":[0.0,10.5,2.0,14.5]},"south":{"texture":"atlas5","uv":[2.0,10.5,4.0,14.5]}}},{"from":[6,4,0],"to":[10,5,2],"faces":{"east":{"texture":"atlas2","uv":[15.0,6.5,16.0,7.0]},"west":{"texture":"atlas2","uv":[15.0,3.0,16.0,3.5]},"up":{"texture":"atlas5","uv":[0.0,15.0,2.0,16.0]},"down":{"texture":"atlas5","uv":[2.0,15.0,4.0,16.0]},"north":{"texture":"atlas3","cullface":"north","uv":[13.5,14.5,15.5,15.0]}}},{"from":[12,4,0],"to":[16,12,2],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[4.0,10.5,5.0,14.5]},"west":{"texture":"atlas5","uv":[5.0,10.5,6.0,14.5]},"up":{"texture":"atlas5","uv":[4.0,15.0,6.0,16.0]},"down":{"texture":"atlas5","uv":[6.0,15.0,8.0,16.0]},"north":{"texture":"atlas5","cullface":"north","uv":[6.0,10.5,8.0,14.5]},"south":{"texture":"atlas5","uv":[8.0,10.5,10.0,14.5]}}},{"from":[0,6,15],"to":[2,7,16],"faces":{"east":{"texture":"atlas5","uv":[5.0,14.5,5.5,15.0]},"west":{"texture":"atlas5","cullface":"west","uv":[5.5,14.5,6.0,15.0]},"up":{"texture":"atlas2","uv":[15.0,2.5,16.0,3.0]},"down":{"texture":"atlas2","uv":[15.0,2.0,16.0,2.5]},"south":{"texture":"atlas3","cullface":"south","uv":[14.5,15.0,15.5,15.5]}}},{"from":[5,6,15],"to":[6,7,16],"faces":{"east":{"texture":"atlas5","uv":[6.0,14.5,6.5,15.0]},"west":{"texture":"atlas5","uv":[6.5,14.5,7.0,15.0]},"up":{"texture":"atlas5","uv":[7.0,14.5,7.5,15.0]},"down":{"texture":"atlas5","uv":[7.5,14.5,8.0,15.0]},"south":{"texture":"atlas5","cullface":"south","uv":[8.0,14.5,8.5,15.0]}}},{"from":[10,6,15],"to":[11,7,16],"faces":{"east":{"texture":"atlas5","uv":[8.5,14.5,9.0,15.0]},"west":{"texture":"atlas5","uv":[9.0,14.5,9.5,15.0]},"up":{"texture":"atlas5","uv":[9.5,14.5,10.0,15.0]},"down":{"texture":"atlas5","uv":[10.0,14.5,10.5,15.0]},"south":{"texture":"atlas5","cullface":"south","uv":[10.5,14.5,11.0,15.0]}}},{"from":[14,6,15],"to":[16,7,16],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[11.0,14.5,11.5,15.0]},"west":{"texture":"atlas5","uv":[11.5,14.5,12.0,15.0]},"up":{"texture":"atlas3","uv":[14.0,11.5,15.0,12.0]},"down":{"texture":"atlas3","uv":[15.0,11.5,16.0,12.0]},"south":{"texture":"atlas3","cullface":"south","uv":[14.0,7.5,15.0,8.0]}}},{"from":[0,6,2],"to":[16,7,15],"faces":{"east":{"texture":"atlas3","cullface":"east","uv":[0.0,14.0,6.5,14.5]},"west":{"texture":"atlas3","cullface":"west","uv":[0.0,14.5,6.5,15.0]},"up":{"texture":"atlas1","uv":[0.0,0.0,8.0,6.5]},"down":{"texture":"atlas1","uv":[0.0,6.5,8.0,13.0]},"north":{"texture":"atlas0","uv":[8.0,11.5,16.0,12.0]},"south":{"texture":"atlas0","uv":[8.0,10.0,16.0,10.5]}}},{"from":[0,7,15],"to":[6,8,16],"faces":{"east":{"texture":"atlas5","uv":[12.0,14.5,12.5,15.0]},"west":{"texture":"atlas5","cullface":"west","uv":[12.5,14.5,13.0,15.0]},"up":{"texture":"atlas3","uv":[6.5,14.0,9.5,14.5]},"down":{"texture":"atlas3","uv":[9.5,14.0,12.5,14.5]},"south":{"texture":"atlas3","cullface":"south","uv":[12.5,14.0,15.5,14.5]}}},{"from":[10,7,15],"to":[16,8,16],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[13.0,14.5,13.5,15.0]},"west":{"texture":"atlas5","uv":[13.5,14.5,14.0,15.0]},"up":{"texture":"atlas3","uv":[13.0,12.0,16.0,12.5]},"down":{"texture":"atlas3","uv":[13.0,12.5,16.0,13.0]},"south":{"texture":"atlas3","cullface":"south","uv":[13.0,13.0,16.0,13.5]}}},{"from":[0,7,8],"to":[16,11,15],"faces":{"east":{"texture":"atlas4","cullface":"east","uv":[8.0,6.0,11.5,8.0]},"west":{"texture":"atlas4","cullface":"west","uv":[11.5,6.0,15.0,8.0]},"up":{"texture":"atlas1","uv":[8.0,6.5,16.0,10.0]},"down":{"texture":"atlas1","uv":[8.0,0.0,16.0,3.5]},"north":{"texture":"atlas1","uv":[0.0,13.0,8.0,15.0]},"south":{"texture":"atlas1","uv":[8.0,13.0,16.0,15.0]}}},{"from":[1,7,7],"to":[15,14,8],"faces":{"east":{"texture":"atlas3","uv":[15.0,0.0,15.5,3.5]},"west":{"texture":"atlas3","uv":[15.5,0.0,16.0,3.5]},"up":{"texture":"atlas2","uv":[8.0,15.5,15.0,16.0]},"down":{"texture":"atlas2","uv":[8.0,15.0,15.0,15.5]},"north":{"texture":"atlas3","uv":[8.0,8.0,15.0,11.5]},"south":{"texture":"atlas3","uv":[8.0,4.0,15.0,7.5]}}},{"from":[0,7,6],"to":[16,14,7],"faces":{"east":{"texture":"atlas3","cullface":"east","uv":[15.0,4.0,15.5,7.5]},"west":{"texture":"atlas3","cullface":"west","uv":[15.5,4.0,16.0,7.5]},"up":{"texture":"atlas0","uv":[8.0,10.5,16.0,11.0]},"down":{"texture":"atlas0","uv":[8.0,1.0,16.0,1.5]},"north":{"texture":"atlas2","uv":[0.0,0.0,8.0,3.5]},"south":{"texture":"atlas2","uv":[0.0,3.5,8.0,7.0]}}},{"from":[1,7,5],"to":[15,14,6],"faces":{"east":{"texture":"atlas3","uv":[15.0,8.0,15.5,11.5]},"west":{"texture":"atlas3","uv":[15.5,8.0,16.0,11.5]},"up":{"texture":"atlas2","uv":[8.0,6.5,15.0,7.0]},"down":{"texture":"atlas2","uv":[8.0,2.0,15.0,2.5]},"north":{"texture":"atlas3","uv":[8.0,0.0,15.0,3.5]},"south":{"texture":"atlas5","uv":[0.0,0.0,7.0,3.5]}}},{"from":[0,7,4],"to":[16,14,5],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[7.0,0.0,7.5,3.5]},"west":{"texture":"atlas5","cullface":"west","uv":[7.5,0.0,8.0,3.5]},"up":{"texture":"atlas0","uv":[8.0,1.5,16.0,2.0]},"down":{"texture":"atlas0","uv":[8.0,0.0,16.0,0.5]},"north":{"texture":"atlas2","uv":[0.0,7.0,8.0,10.5]},"south":{"texture":"atlas2","uv":[0.0,10.5,8.0,14.0]}}},{"from":[1,7,3],"to":[15,14,4],"faces":{"east":{"texture":"atlas5","uv":[8.0,0.0,8.5,3.5]},"west":{"texture":"atlas5","uv":[8.5,0.0,9.0,3.5]},"up":{"texture":"atlas2","uv":[8.0,2.5,15.0,3.0]},"down":{"texture":"atlas2","uv":[8.0,3.0,15.0,3.5]},"north":{"texture":"atlas5","uv":[0.0,3.5,7.0,7.0]},"south":{"texture":"atlas5","uv":[0.0,7.0,7.0,10.5]}}},{"from":[0,7,2],"to":[16,12,3],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[9.0,0.0,9.5,2.5]},"west":{"texture":"atlas5","cullface":"west","uv":[9.5,0.0,10.0,2.5]},"up":{"texture":"atlas0","uv":[8.0,0.5,16.0,1.0]},"down":{"texture":"atlas1","uv":[0.0,15.0,8.0,15.5]},"north":{"texture":"atlas1","uv":[8.0,10.0,16.0,12.5]},"south":{"texture":"atlas1","uv":[8.0,3.5,16.0,6.0]}}},{"from":[0,8,15],"to":[2,9,16],"faces":{"east":{"texture":"atlas5","uv":[14.0,14.5,14.5,15.0]},"west":{"texture":"atlas5","cullface":"west","uv":[14.5,14.5,15.0,15.0]},"up":{"texture":"atlas3","uv":[15.0,7.5,16.0,8.0]},"down":{"texture":"atlas3","uv":[14.0,3.5,15.0,4.0]},"south":{"texture":"atlas3","cullface":"south","uv":[15.0,3.5,16.0,4.0]}}},{"from":[5,8,15],"to":[6,9,16],"faces":{"east":{"texture":"atlas5","uv":[15.0,14.5,15.5,15.0]},"west":{"texture":"atlas5","uv":[15.5,14.5,16.0,15.0]},"up":{"texture":"atlas5","uv":[10.0,10.5,10.5,11.0]},"down":{"texture":"atlas5","uv":[10.0,11.0,10.5,11.5]},"south":{"texture":"atlas5","cullface":"south","uv":[10.0,11.5,10.5,12.0]}}},{"from":[10,8,15],"to":[11,9,16],"faces":{"east":{"texture":"atlas5","uv":[10.0,12.0,10.5,12.5]},"west":{"texture":"atlas5","uv":[10.0,12.5,10.5,13.0]},"up":{"texture":"atlas5","uv":[10.0,13.0,10.5,13.5]},"down":{"texture":"atlas5","uv":[10.0,13.5,10.5,14.0]},"south":{"texture":"atlas5","cullface":"south","uv":[10.0,14.0,10.5,14.5]}}},{"from":[14,8,15],"to":[16,9,16],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[10.5,14.0,11.0,14.5]},"west":{"texture":"atlas5","uv":[11.0,14.0,11.5,14.5]},"up":{"texture":"atlas4","uv":[15.0,12.0,16.0,12.5]},"down":{"texture":"atlas4","uv":[15.0,12.5,16.0,13.0]},"south":{"texture":"atlas4","cullface":"south","uv":[15.0,13.0,16.0,13.5]}}},{"from":[0,9,15],"to":[6,10,16],"faces":{"east":{"texture":"atlas5","uv":[11.5,14.0,12.0,14.5]},"west":{"texture":"atlas5","cullface":"west","uv":[12.0,14.0,12.5,14.5]},"up":{"texture":"atlas3","uv":[13.0,13.5,16.0,14.0]},"down":{"texture":"atlas3","uv":[8.0,11.5,11.0,12.0]},"south":{"texture":"atlas3","cullface":"south","uv":[11.0,11.5,14.0,12.0]}}},{"from":[10,9,15],"to":[16,10,16],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[12.5,14.0,13.0,14.5]},"west":{"texture":"atlas5","uv":[13.0,14.0,13.5,14.5]},"up":{"texture":"atlas3","uv":[8.0,7.5,11.0,8.0]},"down":{"texture":"atlas3","uv":[11.0,7.5,14.0,8.0]},"south":{"texture":"atlas3","cullface":"south","uv":[8.0,3.5,11.0,4.0]}}},{"from":[0,10,15],"to":[2,11,16],"faces":{"east":{"texture":"atlas5","uv":[13.5,14.0,14.0,14.5]},"west":{"texture":"atlas5","cullface":"west","uv":[14.0,14.0,14.5,14.5]},"up":{"texture":"atlas4","uv":[15.0,13.5,16.0,14.0]},"down":{"texture":"atlas5","uv":[11.0,15.0,12.0,15.5]},"south":{"texture":"atlas5","cullface":"south","uv":[11.0,15.5,12.0,16.0]}}},{"from":[5,10,15],"to":[11,11,16],"faces":{"east":{"texture":"atlas5","uv":[14.5,14.0,15.0,14.5]},"west":{"texture":"atlas5","uv":[15.0,14.0,15.5,14.5]},"down":{"texture":"atlas3","uv":[11.0,3.5,14.0,4.0]},"south":{"texture":"atlas5","cullface":"south","uv":[0.0,14.5,3.0,15.0]}}},{"from":[14,10,15],"to":[16,11,16],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[15.5,14.0,16.0,14.5]},"west":{"texture":"atlas5","uv":[10.5,13.5,11.0,14.0]},"up":{"texture":"atlas5","uv":[12.0,15.5,13.0,16.0]},"down":{"texture":"atlas5","uv":[13.0,15.5,14.0,16.0]},"south":{"texture":"atlas5","cullface":"south","uv":[14.0,15.5,15.0,16.0]}}},{"from":[0,11,8],"to":[16,12,16],"faces":{"east":{"texture":"atlas3","cullface":"east","uv":[0.0,15.0,4.0,15.5]},"west":{"texture":"atlas3","cullface":"west","uv":[0.0,15.5,4.0,16.0]},"up":{"texture":"atlas3","uv":[0.0,0.0,8.0,4.0]},"down":{"texture":"atlas3","uv":[0.0,4.0,8.0,8.0]},"north":{"texture":"atlas1","uv":[0.0,15.5,8.0,16.0]},"south":{"texture":"atlas1","cullface":"south","uv":[8.0,15.5,16.0,16.0]}}},{"from":[0,12,15],"to":[2,13,16],"faces":{"east":{"texture":"atlas5","uv":[11.0,13.5,11.5,14.0]},"west":{"texture":"atlas5","cullface":"west","uv":[11.5,13.5,12.0,14.0]},"up":{"texture":"atlas5","uv":[15.0,15.5,16.0,16.0]},"down":{"texture":"atlas5","uv":[12.0,15.0,13.0,15.5]},"south":{"texture":"atlas5","cullface":"south","uv":[13.0,15.0,14.0,15.5]}}},{"from":[14,12,15],"to":[16,13,16],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[12.0,13.5,12.5,14.0]},"west":{"texture":"atlas5","uv":[12.5,13.5,13.0,14.0]},"up":{"texture":"atlas5","uv":[14.0,15.0,15.0,15.5]},"down":{"texture":"atlas5","uv":[15.0,15.0,16.0,15.5]},"south":{"texture":"atlas5","cullface":"south","uv":[3.0,14.5,4.0,15.0]}}},{"from":[0,12,8],"to":[16,13,15],"faces":{"east":{"texture":"atlas3","cullface":"east","uv":[6.5,14.5,10.0,15.0]},"west":{"texture":"atlas3","cullface":"west","uv":[10.0,14.5,13.5,15.0]},"up":{"texture":"atlas2","uv":[8.0,10.5,16.0,14.0]},"down":{"texture":"atlas2","uv":[8.0,7.0,16.0,10.5]},"north":{"texture":"atlas1","uv":[8.0,15.0,16.0,15.5]},"south":{"texture":"atlas1","uv":[8.0,12.5,16.0,13.0]}}},{"from":[0,12,0],"to":[16,14,3],"faces":{"east":{"texture":"atlas5","cullface":"east","uv":[8.0,15.0,9.5,16.0]},"west":{"texture":"atlas5","cullface":"west","uv":[9.5,15.0,11.0,16.0]},"down":{"texture":"atlas2","uv":[0.0,14.0,8.0,15.5]},"north":{"texture":"atlas2","cullface":"north","uv":[8.0,14.0,16.0,15.0]},"south":{"texture":"atlas2","uv":[8.0,3.5,16.0,4.5]}}},{"from":[0,13,8],"to":[16,14,16],"faces":{"east":{"texture":"atlas3","cullface":"east","uv":[4.0,15.5,8.0,16.0]},"west":{"texture":"atlas3","cullface":"west","uv":[8.0,15.5,12.0,16.0]},"down":{"texture":"atlas3","uv":[0.0,8.0,8.0,12.0]},"north":{"texture":"atlas1","uv":[8.0,6.0,16.0,6.5]},"south":{"texture":"atlas2","cullface":"south","uv":[0.0,15.5,8.0,16.0]}}},{"from":[0,14,0],"to":[16,16,16],"faces":{"east":{"texture":"atlas2","cullface":"east","uv":[8.0,4.5,16.0,5.5]},"west":{"texture":"atlas2","cullface":"west","uv":[8.0,5.5,16.0,6.5]},"up":{"texture":"atlas4","cullface":"up","uv":[0.0,0.0,8.0,8.0]},"down":{"texture":"atlas4","uv":[0.0,8.0,8.0,16.0]},"north":{"texture":"atlas2","cullface":"north","uv":[8.0,0.0,16.0,1.0]},"south":{"texture":"atlas2","cullface":"south","uv":[8.0,1.0,16.0,2.0]}}}]} diff --git a/src/main/resources/assets/oc2/models/block/vxlan_hub.json b/src/main/resources/assets/oc2/models/block/vxlan_hub.json new file mode 100644 index 00000000..622cc9b3 --- /dev/null +++ b/src/main/resources/assets/oc2/models/block/vxlan_hub.json @@ -0,0 +1,292 @@ +{ + "parent": "block/block", + "textures": { + "particle": "oc2:block/vxlan_hub/vxlan_hub_atlas0", + "atlas0": "oc2:block/vxlan_hub/vxlan_hub_atlas0", + "atlas1": "oc2:block/vxlan_hub/vxlan_hub_atlas1", + "atlas2": "oc2:block/vxlan_hub/vxlan_hub_atlas2", + "atlas3": "oc2:block/vxlan_hub/vxlan_hub_atlas3" + }, + "elements": [ + { + "from": [0, 0, 10], + "to": [16, 1, 16], + "faces": { + "north": {"uv": [0, 3, 8, 3.5], "texture": "#atlas0"}, + "east": {"uv": [8, 7, 11, 7.5], "texture": "#atlas1", "cullface": "east"}, + "south": {"uv": [0, 3.5, 8, 4], "texture": "#atlas0", "cullface": "south"}, + "west": {"uv": [8, 7.5, 11, 8], "texture": "#atlas1", "cullface": "west"}, + "down": {"uv": [0, 0, 8, 3], "texture": "#atlas0", "cullface": "down"} + } + }, + { + "from": [0, 0, 6], + "to": [6, 1, 10], + "faces": { + "north": {"uv": [11, 7.5, 14, 8], "texture": "#atlas1"}, + "east": {"uv": [14, 7.5, 16, 8], "texture": "#atlas1"}, + "south": {"uv": [11, 7, 14, 7.5], "texture": "#atlas1"}, + "west": {"uv": [14, 7, 16, 7.5], "texture": "#atlas1", "cullface": "west"}, + "down": {"uv": [0, 0, 3, 2], "texture": "#atlas3", "cullface": "down"} + } + }, + { + "from": [10, 0, 6], + "to": [16, 1, 10], + "faces": { + "north": {"uv": [0, 4, 3, 4.5], "texture": "#atlas3"}, + "east": {"uv": [9, 14.5, 11, 15], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [0, 4.5, 3, 5], "texture": "#atlas3"}, + "west": {"uv": [11, 14.5, 13, 15], "texture": "#atlas3"}, + "down": {"uv": [0, 2, 3, 4], "texture": "#atlas3", "cullface": "down"} + } + }, + { + "from": [0, 0, 0], + "to": [16, 1, 6], + "faces": { + "north": {"uv": [0, 7, 8, 7.5], "texture": "#atlas0", "cullface": "north"}, + "east": {"uv": [0, 5, 3, 5.5], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [0, 7.5, 8, 8], "texture": "#atlas0"}, + "west": {"uv": [0, 5.5, 3, 6], "texture": "#atlas3", "cullface": "west"}, + "down": {"uv": [0, 4, 8, 7], "texture": "#atlas0", "cullface": "down"} + } + }, + { + "from": [0, 1, 0], + "to": [16, 6, 16], + "faces": { + "north": {"uv": [0, 13, 8, 15.5], "texture": "#atlas0", "cullface": "north"}, + "east": {"uv": [0, 8, 8, 10.5], "texture": "#atlas0", "cullface": "east"}, + "south": {"uv": [8, 13, 16, 15.5], "texture": "#atlas0", "cullface": "south"}, + "west": {"uv": [0, 10.5, 8, 13], "texture": "#atlas0", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#atlas1"}, + "down": {"uv": [0, 8, 8, 16], "texture": "#atlas1"} + } + }, + { + "from": [0, 6, 15], + "to": [6, 10, 16], + "faces": { + "east": {"uv": [15, 0, 15.5, 2], "texture": "#atlas2"}, + "south": {"uv": [0, 7, 3, 9], "texture": "#atlas3", "cullface": "south"}, + "west": {"uv": [15.5, 0, 16, 2], "texture": "#atlas2", "cullface": "west"}, + "up": {"uv": [0, 6, 3, 6.5], "texture": "#atlas3"}, + "down": {"uv": [0, 6.5, 3, 7], "texture": "#atlas3"} + } + }, + { + "from": [10, 6, 15], + "to": [16, 10, 16], + "faces": { + "east": {"uv": [15, 2, 15.5, 4], "texture": "#atlas2", "cullface": "east"}, + "south": {"uv": [0, 10, 3, 12], "texture": "#atlas3", "cullface": "south"}, + "west": {"uv": [15.5, 2, 16, 4], "texture": "#atlas2"}, + "up": {"uv": [0, 9, 3, 9.5], "texture": "#atlas3"}, + "down": {"uv": [0, 9.5, 3, 10], "texture": "#atlas3"} + } + }, + { + "from": [0, 6, 10], + "to": [16, 10, 15], + "faces": { + "north": {"uv": [8, 4, 16, 6], "texture": "#atlas0"}, + "east": {"uv": [3, 10, 5.5, 12], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 0, 16, 2], "texture": "#atlas0"}, + "west": {"uv": [5.5, 10, 8, 12], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 10.5, 16, 13], "texture": "#atlas0"}, + "down": {"uv": [8, 8, 16, 10.5], "texture": "#atlas0"} + } + }, + { + "from": [1, 6, 6], + "to": [15, 10, 10], + "faces": { + "north": {"uv": [8, 4, 15, 6], "texture": "#atlas2"}, + "east": {"uv": [13, 10, 15, 12], "texture": "#atlas3"}, + "south": {"uv": [8, 6, 15, 8], "texture": "#atlas2"}, + "west": {"uv": [3, 7, 5, 9], "texture": "#atlas3"}, + "up": {"uv": [8, 0, 15, 2], "texture": "#atlas2"}, + "down": {"uv": [8, 2, 15, 4], "texture": "#atlas2"} + } + }, + { + "from": [0, 6, 1], + "to": [16, 10, 6], + "faces": { + "north": {"uv": [8, 13, 16, 15], "texture": "#atlas1"}, + "east": {"uv": [8, 10, 10.5, 12], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 0, 16, 2], "texture": "#atlas1"}, + "west": {"uv": [10.5, 10, 13, 12], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 8, 16, 10.5], "texture": "#atlas1"}, + "down": {"uv": [8, 10.5, 16, 13], "texture": "#atlas1"} + } + }, + { + "from": [0, 6, 0], + "to": [6, 10, 1], + "faces": { + "north": {"uv": [0, 13, 3, 15], "texture": "#atlas3", "cullface": "north"}, + "east": {"uv": [15, 4, 15.5, 6], "texture": "#atlas2"}, + "west": {"uv": [15.5, 4, 16, 6], "texture": "#atlas2", "cullface": "west"}, + "up": {"uv": [0, 12, 3, 12.5], "texture": "#atlas3"}, + "down": {"uv": [0, 12.5, 3, 13], "texture": "#atlas3"} + } + }, + { + "from": [10, 6, 0], + "to": [16, 10, 1], + "faces": { + "north": {"uv": [3, 13, 6, 15], "texture": "#atlas3", "cullface": "north"}, + "east": {"uv": [15, 6, 15.5, 8], "texture": "#atlas2", "cullface": "east"}, + "west": {"uv": [15.5, 6, 16, 8], "texture": "#atlas2"}, + "up": {"uv": [0, 15, 3, 15.5], "texture": "#atlas3"}, + "down": {"uv": [0, 15.5, 3, 16], "texture": "#atlas3"} + } + }, + { + "from": [0, 10, 0], + "to": [16, 15, 16], + "faces": { + "north": {"uv": [8, 8, 16, 10.5], "texture": "#atlas2", "cullface": "north"}, + "east": {"uv": [8, 2, 16, 4.5], "texture": "#atlas1", "cullface": "east"}, + "south": {"uv": [8, 10.5, 16, 13], "texture": "#atlas2", "cullface": "south"}, + "west": {"uv": [8, 4.5, 16, 7], "texture": "#atlas1", "cullface": "west"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#atlas2"}, + "down": {"uv": [0, 8, 8, 16], "texture": "#atlas2"} + } + }, + { + "from": [0, 15, 13], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 15.5, 8, 16], "texture": "#atlas0"}, + "east": {"uv": [11, 13, 12.5, 13.5], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas0", "cullface": "south"}, + "west": {"uv": [12.5, 13, 14, 13.5], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 13, 16, 14.5], "texture": "#atlas2", "cullface": "up"} + } + }, + { + "from": [0, 15, 12], + "to": [6, 16, 13], + "faces": { + "north": {"uv": [6, 15.5, 9, 16], "texture": "#atlas3"}, + "east": {"uv": [15.5, 13, 16, 13.5], "texture": "#atlas3"}, + "south": {"uv": [9, 15.5, 12, 16], "texture": "#atlas3"}, + "west": {"uv": [13.5, 12.5, 14, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [3, 15.5, 6, 16], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [10, 15, 12], + "to": [16, 16, 13], + "faces": { + "north": {"uv": [3, 15, 6, 15.5], "texture": "#atlas3"}, + "east": {"uv": [14, 12.5, 14.5, 13], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [6, 15, 9, 15.5], "texture": "#atlas3"}, + "west": {"uv": [14.5, 12.5, 15, 13], "texture": "#atlas3"}, + "up": {"uv": [12, 15.5, 15, 16], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [0, 15, 10], + "to": [16, 16, 12], + "faces": { + "north": {"uv": [8, 7.5, 16, 8], "texture": "#atlas0"}, + "east": {"uv": [15, 15.5, 16, 16], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 7, 16, 7.5], "texture": "#atlas0"}, + "west": {"uv": [15, 15, 16, 15.5], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 6, 16, 7], "texture": "#atlas0", "cullface": "up"} + } + }, + { + "from": [0, 15, 6], + "to": [3, 16, 10], + "faces": { + "north": {"uv": [14, 13, 15.5, 13.5], "texture": "#atlas3"}, + "east": {"uv": [13, 14.5, 15, 15], "texture": "#atlas3"}, + "south": {"uv": [3, 12.5, 4.5, 13], "texture": "#atlas3"}, + "west": {"uv": [9, 14, 11, 14.5], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [3, 0, 4.5, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [4, 15, 6], + "to": [6, 16, 10], + "faces": { + "north": {"uv": [15, 14.5, 16, 15], "texture": "#atlas3"}, + "east": {"uv": [11, 14, 13, 14.5], "texture": "#atlas3"}, + "south": {"uv": [15, 14, 16, 14.5], "texture": "#atlas3"}, + "west": {"uv": [13, 14, 15, 14.5], "texture": "#atlas3"}, + "up": {"uv": [4.5, 0, 5.5, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [10, 15, 6], + "to": [12, 16, 10], + "faces": { + "north": {"uv": [15, 13.5, 16, 14], "texture": "#atlas3"}, + "east": {"uv": [9, 13.5, 11, 14], "texture": "#atlas3"}, + "south": {"uv": [10.5, 12.5, 11.5, 13], "texture": "#atlas3"}, + "west": {"uv": [11, 13.5, 13, 14], "texture": "#atlas3"}, + "up": {"uv": [5.5, 0, 6.5, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [13, 15, 6], + "to": [16, 16, 10], + "faces": { + "north": {"uv": [4.5, 12.5, 6, 13], "texture": "#atlas3"}, + "east": {"uv": [13, 13.5, 15, 14], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [6, 12.5, 7.5, 13], "texture": "#atlas3"}, + "west": {"uv": [9, 13, 11, 13.5], "texture": "#atlas3"}, + "up": {"uv": [6.5, 0, 8, 2], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [0, 15, 4], + "to": [16, 16, 6], + "faces": { + "north": {"uv": [8, 3.5, 16, 4], "texture": "#atlas0"}, + "east": {"uv": [11.5, 12.5, 12.5, 13], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 3, 16, 3.5], "texture": "#atlas0"}, + "west": {"uv": [12.5, 12.5, 13.5, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 2, 16, 3], "texture": "#atlas0", "cullface": "up"} + } + }, + { + "from": [0, 15, 3], + "to": [6, 16, 4], + "faces": { + "north": {"uv": [12, 15, 15, 15.5], "texture": "#atlas3"}, + "east": {"uv": [15, 12.5, 15.5, 13], "texture": "#atlas3"}, + "south": {"uv": [6, 13, 9, 13.5], "texture": "#atlas3"}, + "west": {"uv": [15.5, 12.5, 16, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [9, 15, 12, 15.5], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [10, 15, 3], + "to": [16, 16, 4], + "faces": { + "north": {"uv": [6, 14, 9, 14.5], "texture": "#atlas3"}, + "east": {"uv": [3, 12, 3.5, 12.5], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [6, 14.5, 9, 15], "texture": "#atlas3"}, + "west": {"uv": [3.5, 12, 4, 12.5], "texture": "#atlas3"}, + "up": {"uv": [6, 13.5, 9, 14], "texture": "#atlas3", "cullface": "up"} + } + }, + { + "from": [0, 15, 0], + "to": [16, 16, 3], + "faces": { + "north": {"uv": [8, 15, 16, 15.5], "texture": "#atlas1", "cullface": "north"}, + "east": {"uv": [7.5, 12.5, 9, 13], "texture": "#atlas3", "cullface": "east"}, + "south": {"uv": [8, 15.5, 16, 16], "texture": "#atlas1"}, + "west": {"uv": [9, 12.5, 10.5, 13], "texture": "#atlas3", "cullface": "west"}, + "up": {"uv": [8, 14.5, 16, 16], "texture": "#atlas2", "cullface": "up"} + } + } + ] +} diff --git a/src/main/resources/assets/oc2/models/item/network_switch.json b/src/main/resources/assets/oc2/models/item/network_switch.json new file mode 100644 index 00000000..657bd289 --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/network_switch.json @@ -0,0 +1,3 @@ +{ + "parent": "oc2:block/network_switch" +} diff --git a/src/main/resources/assets/oc2/models/item/pci_card_cage.json b/src/main/resources/assets/oc2/models/item/pci_card_cage.json new file mode 100644 index 00000000..b01fc003 --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/pci_card_cage.json @@ -0,0 +1,3 @@ +{ + "parent": "oc2:block/pci_card_cage" +} diff --git a/src/main/resources/assets/oc2/models/item/vxlan_hub.json b/src/main/resources/assets/oc2/models/item/vxlan_hub.json new file mode 100644 index 00000000..eee4e7ff --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/vxlan_hub.json @@ -0,0 +1,3 @@ +{ + "parent": "oc2:block/vxlan_hub" +} diff --git a/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas0.png b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas0.png new file mode 100644 index 00000000..4271a0a7 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas0.png differ diff --git a/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas1.png b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas1.png new file mode 100644 index 00000000..80f9ddee Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas1.png differ diff --git a/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas2.png b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas2.png new file mode 100644 index 00000000..abf885f6 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas2.png differ diff --git a/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas3.png b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas3.png new file mode 100644 index 00000000..f64bee0b Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/network_switch/network_switch_atlas3.png differ diff --git a/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas0.png b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas0.png new file mode 100644 index 00000000..cc49ffef Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas0.png differ diff --git a/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas1.png b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas1.png new file mode 100644 index 00000000..d68bbc35 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas1.png differ diff --git a/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas2.png b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas2.png new file mode 100644 index 00000000..787d9203 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas2.png differ diff --git a/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas3.png b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas3.png new file mode 100644 index 00000000..738c4657 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas3.png differ diff --git a/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas4.png b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas4.png new file mode 100644 index 00000000..113d34a6 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas4.png differ diff --git a/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas5.png b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas5.png new file mode 100644 index 00000000..3f126b89 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas5.png differ diff --git a/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas6.png b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas6.png new file mode 100644 index 00000000..65320de0 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/pci_card_cage/pci_card_cage_atlas6.png differ diff --git a/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas0.png b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas0.png new file mode 100644 index 00000000..b2e4afe9 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas0.png differ diff --git a/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas1.png b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas1.png new file mode 100644 index 00000000..ccc13cb4 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas1.png differ diff --git a/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas2.png b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas2.png new file mode 100644 index 00000000..de130c4c Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas2.png differ diff --git a/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas3.png b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas3.png new file mode 100644 index 00000000..c16e2168 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/block/vxlan_hub/vxlan_hub_atlas3.png differ diff --git a/src/main/scripts/bin/swconfig.lua b/src/main/scripts/bin/swconfig.lua new file mode 100644 index 00000000..6ef47192 --- /dev/null +++ b/src/main/scripts/bin/swconfig.lua @@ -0,0 +1,59 @@ +#!/usr/bin/lua + +function usage() + print("Usage:") + print(" swconfig show_hosts") + print(" swconfig show_ports") + print(" swconfig set_port untagged ") + print(" swconfig set_port trunk_all (on|off)") +end + +if not arg[1] then + usage() + return +end + +local cjson = require("cjson").new() + +local devbus = require('devices') +local switch = devbus:find("switch") + +if not switch then + print("No switch found") + return +end + +if arg[1] == "show_hosts" then + host_table = switch:getHostTable() + + for _, v in ipairs(host_table) do + print(v.mac .. " : " .. v.side .. ", Age: " .. v.age) + end +elseif arg[1] == "show_ports" then + local link_state = switch:getLinkState() + for i, port in ipairs(switch:getPortConfig()) do + print("Port #" .. (i - 1) .. " " .. (link_state[i] and "UP" or "DOWN")) + print(" Untagged VLAN: " .. port.untagged) + print(" Tagged: " .. table.concat(port.tagged, ", ")) + print(" Hairpin: " .. (port.hairpin and "on" or "off")) + print(" Trunk All: " .. (port.trunk_all and "on" or "off")) + end +elseif arg[1] == "set_port" then + if #arg < 4 then + usage() + return + end + local config = switch:getPortConfig() + local port = config[tonumber(arg[2]) + 1] + if not port then + print("Invalid Port Number") + return + end + if arg[3] == "untagged" then + port.untagged = tonumber(arg[4]) + end + switch:setPortConfig(config) +else + usage() + return +end