New structure

This commit is contained in:
2026-02-20 00:31:19 +01:00
parent 08c001afbb
commit 418c47f483
14 changed files with 86 additions and 473 deletions
@@ -1,283 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.Utils;
import org.saturnclient.ui2.elements.Notification.NotificationKind;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import org.saturnclient.saturnclient.SaturnClient;
public class Auth {
public static Map<String, SaturnPlayer> players = new HashMap<>();
public static Map<String, String> playerNames = new HashMap<>();
public static String uuid;
private static Thread listenerThread;
private static Thread pingThread;
private static volatile boolean running = false;
public static boolean authenticate() {
// Register shutdown hook ONCE
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> close());
try {
if (SaturnClient.client.getSession() == null) {
SaturnClient.LOGGER.error("No active Minecraft session found");
return false;
}
String accessToken = SaturnClient.client.getSession().getAccessToken();
SaturnClient.LOGGER.info("Authenticating");
Network.init();
Network.write(accessToken);
String response = Network.read();
SaturnParser parser = new SaturnParser(response);
uuid = parser.getString("uuid");
String cloak = parser.getString("cloak");
String hat = parser.getString("hat");
for (String availableCloak : parser.getArray("cloaks")) {
Cloaks.availableCloaks.add(availableCloak);
}
for (String availableHat : parser.getArray("hats")) {
Hats.availableHats.add(availableHat);
}
players.put(uuid, new SaturnPlayer(cloak, hat));
Cloaks.loadCloak(uuid);
playerNames.put(SaturnClient.client.getSession().getUsername(), uuid);
afterAuth();
startListenerThread();
startPingThread();
return true;
} catch (Exception e) {
SaturnClient.LOGGER.error("Authentication failed", e);
return false;
}
}
private static void startListenerThread() {
running = true;
listenerThread = new Thread(() -> {
try {
while (running) {
String message = Network.read();
if (message == null) {
SaturnClient.LOGGER.error("Connection closed");
running = false;
break;
}
SaturnClient.LOGGER.info("Received: " + message);
SaturnParser parser = new SaturnParser(message);
switch (parser.method) {
case "player":
String uuid = parser.getString("uuid");
if (parser.getBool("saturn")) {
String cloak = parser.getString("cloak");
String hat = parser.getString("hat");
players.put(uuid, new SaturnPlayer(cloak, hat));
Cloaks.loadCloak(uuid);
} else {
playerNames.entrySet().removeIf(entry -> entry.getValue().equals(uuid));
}
break;
case "successful_transaction":
String cloak = parser.getStringOrNull("cloak");
String hat = parser.getStringOrNull("hat");
if (cloak != null) {
Cloaks.availableCloaks.add(cloak);
}
if (hat != null) {
Hats.availableHats.add(hat);
}
case "update_cloak":
String targetUuid = parser.getStringOrNull("uuid");
String targetCloak = parser.getStringOrNull("cloak");
if (targetUuid != null && targetCloak != null) {
SaturnPlayer player = players.get(targetUuid);
if (player != null) {
player.cloak = targetCloak;
}
}
break;
case "update_hat":
targetUuid = parser.getStringOrNull("uuid");
String targetHat = parser.getStringOrNull("hat");
if (targetUuid != null && targetHat != null) {
SaturnPlayer player = players.get(targetUuid);
if (player != null) {
player.hat = targetHat;
}
}
break;
case "emote":
targetUuid = parser.getStringOrNull("uuid");
String emote = parser.getStringOrNull("name");
for (AbstractClientPlayerEntity player : SaturnClient.client.world.getPlayers()) {
if (player.getUuidAsString().replace("-", "").equals(targetUuid)) {
AnimationStack animationStack = PlayerAnimationAccess.getPlayerAnimLayer(player);
if (emote != null && !emote.isEmpty()) {
if (animationStack.isActive() && animationStack.getPriority() == 1000) {
animationStack.removeLayer(1000);
}
animationStack.addAnimLayer(1000,
PlayerAnimationRegistry
.getAnimation(Identifier.of("saturnclient", emote))
.playAnimation());
} else {
animationStack.removeLayer(1000);
}
}
}
break;
default:
if (parser.error != null) {
String[] a = parser.error.replace("!", "").split(": ");
Utils.notify(NotificationKind.Error, a[0], a[1]);
}
break;
}
}
} catch (Exception e) {
SaturnClient.LOGGER.error("Error reading from server", e);
} finally {
close();
}
});
listenerThread.setDaemon(true);
listenerThread.start();
}
private static void startPingThread() {
pingThread = new Thread(() -> {
try {
while (running && !Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(25000); // 25 seconds
if (running) {
Network.write("ping");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupt status
break; // Exit the loop on interruption
}
}
} catch (Exception e) {
if (!(e instanceof InterruptedException)) {
SaturnClient.LOGGER.error("Error in ping thread", e);
}
}
});
pingThread.setDaemon(true);
pingThread.start();
}
public static void close() {
running = false;
if (pingThread != null) {
pingThread.interrupt();
}
if (listenerThread != null) {
listenerThread.interrupt();
}
try {
close();
SaturnClient.LOGGER.info("SaturnApi connection closed.");
} catch (Exception e) {
SaturnClient.LOGGER.error("Failed to close resources", e);
}
}
public static void afterAuth() {
PlayerTracker.initialize();
}
public static void setCloak(String cloakName) {
try {
Network.write("set_cloak@cloak=" + cloakName + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void setHat(String hatName) {
try {
Network.write("set_hat@hat=" + hatName + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void emote(String name) {
try {
Network.write("emote@name=" + name + "@notify=" + stringifyPlayers());
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static String stringifyPlayers() {
return players.keySet().stream()
.filter(key -> key != uuid)
.collect(Collectors.joining("$"));
}
public static void player(String name, String uuid) {
try {
playerNames.put(name, uuid.replaceAll("-", ""));
Network.write("player@uuid=" + uuid.replaceAll("-", ""));
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void buyCloak(String name) {
try {
Network.write("buy_cloak@cloak=" + name);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
public static void buyHat(String name) {
try {
Network.write("buy_hat@hat=" + name);
} catch (Exception e) {
SaturnClient.LOGGER.error("Request failed", e);
}
}
}
@@ -1,106 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.io.*;
import java.net.*;
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.interfaces.*;
import java.security.spec.X509EncodedKeySpec;
public class Network {
// static Session session;
static Socket socket;
static PrintWriter out;
static BufferedReader in;
static byte[] key = new byte[32];
private static final int CONNECTION_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
public static class AES {
public static byte[] encryptAES256(String message, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encryptedMessage = cipher.doFinal(message.getBytes());
return encryptedMessage;
}
public static String decryptAES256(byte[] encryptedMessage, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
return new String(decryptedMessage);
}
}
public static void init() throws Exception {
// Generate AES key
new SecureRandom().nextBytes(key);
socket = new Socket();
socket.connect(new InetSocketAddress("77.247.92.168", 8080), CONNECTION_TIMEOUT);
socket.setSoTimeout(READ_TIMEOUT);
// out = new DataOutputStream(socket.getOutputStream());
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder publicKeyBuilder = new StringBuilder();
String line;
while (!(line = in.readLine()).contains("-----END PUBLIC KEY-----")) {
publicKeyBuilder.append(line).append("\n");
}
publicKeyBuilder.append(line);
String publicKeyPem = publicKeyBuilder.toString();
String publicKeyBase64 = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "").replace("\n", "").replace("\r", "");
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedKey = rsaCipher.doFinal(key);
out.println(Base64.getEncoder().encodeToString(encryptedKey));
}
public static void write(String message) {
try {
out.println(Base64.getEncoder().encodeToString(AES.encryptAES256(message, key)));
} catch (Exception e) {
throw new RuntimeException("Failed to write to server: " + e.getMessage());
}
}
public static String read() {
try {
String msg = in.readLine();
if (msg != null) {
return AES.decryptAES256(Base64.getDecoder().decode(msg), key);
} else {
return "";
}
} catch (Exception e) {
return "";
}
}
public static void close() {
try {
socket.close();
out.close();
in.close();
} catch (Exception e) {
}
}
}
@@ -1,71 +0,0 @@
package org.saturnclient.saturnclient.auth;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class SaturnParser {
public String method = "";
public String error;
private Map<String, String> params = new HashMap<>();
public SaturnParser(String input) throws IOException {
String[] parts = input.split("@");
if (parts.length == 0) {
throw new IOException("Invalid input string");
}
if (parts[0].startsWith("!")) {
error = input;
}
method = parts[0];
for (int i = 1; i < parts.length; i++) {
String[] keyValue = parts[i].split("=");
if (keyValue.length == 2) {
params.put(keyValue[0].trim(), keyValue[1].trim());
} else if (keyValue.length == 1) {
params.put(keyValue[0].trim(), "");
} else {
throw new IOException("Invalid parameter format: " + parts[i]);
}
}
}
public String getString(String i) throws IOException {
String val = params.get(i);
if (val != null) {
return val;
} else {
throw new IOException("Parameter not found " + i);
}
}
public String getStringOrNull(String i) throws IOException {
String val = params.get(i);
return val;
}
public boolean getBool(String i) throws IOException {
String val = params.get(i);
if (val != null) {
return val.equals("true");
} else {
throw new IOException("Parameter not found " + i);
}
}
public String[] getArray(String i) throws IOException {
String val = params.get(i);
if (val != null) {
if (val.isEmpty()) {
return new String[] {};
}
return val.split("\\$");
} else {
throw new IOException("Parameter not found " + i);
}
}
}
@@ -0,0 +1,68 @@
package org.saturnclient.saturnclient.client;
import java.net.http.WebSocket;
import java.util.UUID;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import dev.selimaj.session.Session;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
public class ServiceClient {
private static Session session;
public static UUID uuid;
public static boolean authenticate() {
ClientLifecycleEvents.CLIENT_STOPPING.register(_o -> {
if (session != null)
try {
session.close();
} catch (Exception e) {
SaturnClient.LOGGER.error("Unable to close Saturn session");
}
});
try {
var mcSession = SaturnClient.client.getSession();
if (mcSession == null) {
SaturnClient.LOGGER.error("No active Minecraft session found");
return false;
}
String accessToken = mcSession.getAccessToken();
uuid = mcSession.getUuidOrNull();
SaturnClient.LOGGER.info("Authenticating with UUID: " + accessToken);
// String cloak = parser.getString("cloak");
// String hat = parser.getString("hat");
// for (String availableCloak : parser.getArray("cloaks")) {
// Cloaks.availableCloaks.add(availableCloak);
// }
// for (String availableHat : parser.getArray("hats")) {
// Hats.availableHats.add(availableHat);
// }
// players.put(uuid, new SaturnPlayer(cloak, hat));
// Cloaks.loadCloak(uuid);
// playerNames.put(SaturnClient.client.getSession().getUsername(), uuid);
// afterAuth();
// startListenerThread();
// startPingThread();
return true;
} catch (Exception e) {
SaturnClient.LOGGER.error("Authentication failed", e);
return false;
}
}
}
@@ -0,0 +1,5 @@
package org.saturnclient.saturnclient.client;
public class ServiceMethods {
}
@@ -1,4 +1,4 @@
package org.saturnclient.saturnclient.auth;
package org.saturnclient.saturnclient.client.player;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
@@ -45,8 +45,8 @@ public class PlayerTracker {
private static void onPlayerJoin(MinecraftClient client, PlayerListEntry player) {
GameProfile profile = player.getProfile();
String uuid = profile.getId().toString();
if (uuid != Auth.uuid)
Auth.player(profile.getName(), uuid);
UUID uuid = profile.getId();
// if (uuid != ServiceClient.uuid)
// ServiceClient.player(profile.getName(), uuid);
}
}
@@ -1,4 +1,4 @@
package org.saturnclient.saturnclient.auth;
package org.saturnclient.saturnclient.client.player;
public class SaturnPlayer {
public String cloak = "";
@@ -1,8 +1,7 @@
package org.saturnclient.saturnclient.cosmetics;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider;
@@ -2,8 +2,9 @@ package org.saturnclient.saturnclient.cosmetics;
import java.util.ArrayList;
import java.util.List;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
public class Hats {
public static final String[] ALL_HATS = { "horns_black", "horns_white", "halo", "halo_black" };
@@ -2,9 +2,9 @@ package org.saturnclient.saturnclient.cosmetics.cloaks;
import net.minecraft.util.Identifier;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.AnimatedCloakData;
import org.saturnclient.saturnclient.cosmetics.cloaks.utils.IdentifierUtils;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
@@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.cosmetics;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.SaturnScreen;
@@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.cosmetics;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.ui2.resources.Textures;
import org.saturnclient.ui2.SaturnScreen;
@@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.store;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.cloaks.Cloaks;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.Utils;
@@ -2,7 +2,7 @@ package org.saturnclient.ui2.screens.store;
import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.saturnclient.auth.Auth;
import org.saturnclient.saturnclient.auth.SaturnPlayer;
import org.saturnclient.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.saturnclient.cosmetics.Hats;
import org.saturnclient.ui2.SaturnScreen;
import org.saturnclient.ui2.Utils;