Skip to content

Commit

Permalink
The nyaboom
Browse files Browse the repository at this point in the history
* Modified root gradle project and `gradle.properties` in order to apply mappings and include minecraft only in projects that are modded.
* Started writing server side of Figura, added bunch of packet classes.
* Added interface for platform independent FriendlyByteBuf, so all the needed functions can be also used in Bukkit plugin
* Added platform independent Identifier record, which is basically ResourceLocation, but both for Bukkit and Fabric/Forge.
* Started writing AvatarManager for server side, gonna rewrite a lot tho.
* Added a little Events system to make figura server side extendable but also secure enough. There are several types of events: Regular ones, cancellable, and returnable. Returnable events can be used
for custom avatar/metadata/usedata loading implementation, for example from DB (by default everything including avatars, avatar metadata, and userdata, will be stored in a folder)

From now i am going to take a big break from working on this, don't want to tell why but if short then i've got through something not good and just need some time.
I will get back to working on this project as soon as i can.
  • Loading branch information
lexize committed Jun 30, 2024
1 parent 35d1b59 commit 727b20d
Show file tree
Hide file tree
Showing 42 changed files with 2,006 additions and 2 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ architectury {
minecraft = rootProject.minecraft_version
}

subprojects {
rootProject.mod_projects.split(",").each { project(":$it") {
apply plugin: "dev.architectury.loom"
apply plugin: "io.github.juuxel.loom-vineflower"

Expand Down Expand Up @@ -39,7 +39,7 @@ subprojects {
modCompileOnly "maven.modrinth:immediatelyfast:$immediately_fast"
modCompileOnly "maven.modrinth:iris:$iris"
}
}
}}

allprojects {
apply plugin: "java"
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ java_version = 17
minecraft_version = 1.20.1
mappings = 1
enabled_platforms = fabric,forge
mod_projects = common,common:mojmap,fabric,forge

# Mod Properties
mod_version = 0.1.4
Expand Down
7 changes: 7 additions & 0 deletions server-common/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repositories {

}

dependencies {
implementation 'com.google.code.gson:gson:2.11.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.figuramc.figura.server;

import org.figuramc.figura.server.events.Events;
import org.figuramc.figura.server.events.OutcomingPacketEvent;
import org.figuramc.figura.server.packets.Packet;
import org.figuramc.figura.server.packets.PacketHandler;
import org.figuramc.figura.server.utils.Identifier;
import org.figuramc.figura.server.utils.Utils;

import java.nio.file.Path;
import java.util.UUID;

public abstract class FiguraServer {
private static FiguraServer INSTANCE;
private FiguraUserManager userManager;
private FiguraServerConfig config;
protected FiguraServer() {
if (INSTANCE != null) throw new IllegalStateException("Can't create more than one instance of FiguraServer");
INSTANCE = this;
}

public static FiguraServer getInstance() {
return INSTANCE;
}

public abstract Path getFiguraFolder();

public abstract void registerHandler(Identifier packetId, PacketHandler<?> handler);
public abstract void unregisterHandler(Identifier packetId);

public Path getUsersFolder() {
return getFiguraFolder().resolve("users");
}

public Path getAvatarsFolder() {
return getFiguraFolder().resolve("avatars");
}

public Path getAvatar(byte[] hash) {
return getAvatarsFolder().resolve("%s.nbt".formatted(Utils.hexFromBytes(hash)));
}

public Path getAvatarMetadata(byte[] hash) {
return getAvatarsFolder().resolve("%s.mtd".formatted(Utils.hexFromBytes(hash)));
}

public Path getUserdataFile(UUID user) {
return getUsersFolder().resolve("%s.pl".formatted(Utils.uuidToHex(user)));
}

public final void init() {

}

public final void close() {

INSTANCE = null;
}

public FiguraServerConfig config() {
return config;
}

public FiguraUserManager userManager() {
return userManager;
}

public final void sendPacket(UUID receiver, Packet packet) {
OutcomingPacketEvent event = new OutcomingPacketEvent(receiver, packet);
Events.call(event);
if (!event.isCancelled()) {
sendPacketInternal(receiver, packet);
}
}

protected abstract void sendPacketInternal(UUID receiver, Packet packet);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.figuramc.figura.server;

public final class FiguraServerConfig {
private boolean pings = true;
private boolean avatars = true;

private int pingsRateLimit = 20;
private int pingsSizeLimit = 10240;

private int avatarSizeLimit = 102400;
private int avatarsCountLimit = 1;

private int garbageCollectionTicks = 1200;
private int maxOutcomingChunkSize = 50000;

public boolean pings() {
return pings;
}

public boolean avatars() {
return avatars;
}

public int pingsRateLimit() {
return pingsRateLimit;
}

public int pingsSizeLimit() {
return pingsSizeLimit;
}

public int avatarSizeLimit() {
return avatarSizeLimit;
}

public int avatarsCountLimit() {
return avatarsCountLimit;
}

public int garbageCollectionTicks() {
return garbageCollectionTicks;
}

public int maxOutcomingChunkSize() {
return maxOutcomingChunkSize;
}
}
149 changes: 149 additions & 0 deletions server-common/src/main/java/org/figuramc/figura/server/FiguraUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package org.figuramc.figura.server;

import org.figuramc.figura.server.packets.Packet;
import org.figuramc.figura.server.packets.s2c.S2CUserdataPacket;
import org.figuramc.figura.server.utils.IFriendlyByteBuf;
import org.figuramc.figura.server.utils.InputStreamByteBuf;
import org.figuramc.figura.server.utils.OutputStreamByteBuf;

import java.io.*;
import java.nio.file.Path;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

public final class FiguraUser {
private final UUID player;
private boolean avatars;
private boolean pings;
private final PingCounter pingCounter = new PingCounter();
private final BitSet prideBadges;
private final HashMap<String, byte[]> equippedAvatars;

private final HashMap<String, byte[]> ownedAvatars;

public FiguraUser(UUID player, boolean allowAvatars, boolean allowPings, BitSet prideBadges, HashMap<String, byte[]> equippedAvatars, HashMap<String, byte[]> ownedAvatars) {
this.player = player;
this.avatars = allowAvatars;
this.pings = allowPings;
this.prideBadges = prideBadges;
this.equippedAvatars = equippedAvatars;
this.ownedAvatars = ownedAvatars;
}

public UUID player() {
return player;
}

public boolean avatarsAllowed() {
return avatars;
}

public boolean pingsAllowed() {
return pings;
}

public PingCounter pingCounter() {
return pingCounter;
}

public BitSet prideBadges() {
return prideBadges;
}

public HashMap<String, byte[]> equippedAvatars() {
return equippedAvatars;
}

public HashMap<String, byte[]> ownedAvatars() {
return ownedAvatars;
}

public void sendPacket(Packet packet) {
FiguraServer.getInstance().sendPacket(player, packet);
}

public void save(Path file) {
file.getParent().toFile().mkdirs();
File playerFile = file.toFile();
try {
FileOutputStream fos = new FileOutputStream(playerFile);
OutputStreamByteBuf buf = new OutputStreamByteBuf(fos);
save(buf);
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void save(IFriendlyByteBuf buf) {
buf.writeBytes(prideBadges.toByteArray());
buf.writeVarInt(equippedAvatars.size());
for (var equippedAvatar : equippedAvatars.entrySet()) {
buf.writeByteArray(equippedAvatar.getKey().getBytes(UTF_8));
buf.writeBytes(equippedAvatar.getValue());
}
buf.writeVarInt(ownedAvatars.size());
for (var ownedAvatar : ownedAvatars.entrySet()) {
buf.writeByteArray(ownedAvatar.getKey().getBytes(UTF_8));
buf.writeBytes(ownedAvatar.getValue());
}
}

public static FiguraUser load(UUID player, boolean pings, boolean avatars, Path playerFile) {
try (FileInputStream fis = new FileInputStream(playerFile.toFile())) {
InputStreamByteBuf buf = new InputStreamByteBuf(fis);
return load(player, pings, avatars, buf);
} catch (FileNotFoundException e) {
return new FiguraUser(player, avatars, pings, new BitSet(), new HashMap<>(), new HashMap<>());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static FiguraUser load(UUID player, boolean pings, boolean avatars, IFriendlyByteBuf buf) {
BitSet prideBadges = BitSet.valueOf(buf.readByteArray(256));
int equippedAvatarsCount = buf.readVarInt();
HashMap<String, byte[]> equippedAvatars = new HashMap<>();
for (int i = 0; i < equippedAvatarsCount; i++) {
String id = new String(buf.readByteArray(256), UTF_8);
byte[] hash = buf.readHash();
equippedAvatars.put(id, hash);
}
HashMap<String, byte[]> ownedAvatars = new HashMap<>();
int ownedAvatarsCount = buf.readVarInt();
for (int i = 0; i < ownedAvatarsCount; i++) {
String id = new String(buf.readByteArray(256), UTF_8);
byte[] hash = buf.readHash();
ownedAvatars.put(id, hash);
}
return new FiguraUser(player, avatars, pings, prideBadges, equippedAvatars, ownedAvatars);
}

public byte[] findEHash(byte[] hash) {
return null;
}

private static class PingCounter {
private int bytesSent; // Amount of total bytes sent in last 20 ticks
private int pingsSent; // Amount of pings sent in last 20 ticks

public int bytesSent() {
return bytesSent;
}

public int pingsSent() {
return pingsSent;
}

public void addPing(int size) {
pingsSent++;
bytesSent += size;
}

public void reset() {
bytesSent = 0;
pingsSent = 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.figuramc.figura.server;

import org.figuramc.figura.server.events.Events;
import org.figuramc.figura.server.events.LoadPlayerDataEvent;
import org.figuramc.figura.server.packets.s2c.S2CBackendHandshakePacket;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.UUID;

public final class FiguraUserManager {
private final FiguraServer parent;
private final HashMap<UUID, FiguraUser> users = new HashMap<>();

public FiguraUserManager(FiguraServer parent) {
this.parent = parent;
}

public FiguraUser getUser(UUID playerUUID) {
return users.get(playerUUID);
}

public void onPlayerJoin(UUID player) {
parent.sendPacket(player, new S2CBackendHandshakePacket(
parent.config().pings(),
parent.config().pingsRateLimit(),
parent.config().pingsSizeLimit(),
parent.config().avatars(),
parent.config().avatarSizeLimit(),
parent.config().avatarsCountLimit()
));
}

public FiguraUser authorisePlayer(UUID player, boolean allowPings, boolean allowAvatars) {
return users.computeIfAbsent(player, (k) -> loadPlayerData(k, allowPings, allowAvatars));
}

private FiguraUser loadPlayerData(UUID player, boolean allowPings, boolean allowAvatars) {
LoadPlayerDataEvent playerDataEvent = Events.call(new LoadPlayerDataEvent(player));
if (playerDataEvent.returned()) return playerDataEvent.returnValue();
Path dataFile = parent.getUserdataFile(player);
return FiguraUser.load(player, allowPings, allowAvatars, dataFile);
}

public void onPlayerLeave(UUID player) {
users.computeIfPresent(player, (uuid, pl) -> {
pl.save(parent.getUserdataFile(pl.player()));
return null;
});
}

public void close() {
for (FiguraUser pl: users.values()) {
pl.save(parent.getUserdataFile(pl.player()));
}
users.clear();
}
}
Loading

0 comments on commit 727b20d

Please sign in to comment.