14 Commits
Author SHA1 Message Date
ciomek dbf1d6a798 Version bump
build / build (push) Successful in 3m49s
2026-08-19 18:14:40 +02:00
ciomek 5f6a28fd4d Domain
build / build (push) Successful in 3m57s
2026-08-19 18:02:06 +02:00
ciomek 312596b2dd PWD Fix
build / build (push) Failing after 1m25s
2026-08-19 17:51:23 +02:00
ciomek 44858ccb98 Fix pwd leak
build / build (push) Successful in 6m57s
2026-08-19 17:22:27 +02:00
ciomek e1e0908ef1 Username show
build / build (push) Successful in 5m9s
2026-08-17 21:35:24 +02:00
ciomek 52c904d896 Password hashing
build / build (push) Successful in 4m15s
2026-08-17 21:01:37 +02:00
ciomek 24f6670203 Version bump
build / build (push) Successful in 3m1s
2026-08-16 23:47:17 +02:00
ciomek d0f9791b20 Fix
build / build (push) Successful in 3m48s
2026-08-16 23:44:22 +02:00
ciomek f04d2c3260 Correct thread
build / build (push) Successful in 3m43s
2026-08-16 23:28:05 +02:00
ciomek ba9070eddd Fix
build / build (push) Successful in 2m45s
2026-08-16 14:43:59 +02:00
ciomek 859d901d9a UUID Fix
build / build (push) Successful in 3m2s
2026-08-16 14:36:11 +02:00
ciomek 9ceca5f7b3 MQTT reconnect
build / build (push) Successful in 3m4s
2026-08-16 14:34:58 +02:00
ciomek d45920cfe3 Fix
build / build (push) Successful in 3m32s
2026-08-15 16:54:33 +02:00
ciomek ed049d3710 Build
build / build (push) Failing after 2m16s
2026-08-15 16:46:56 +02:00
18 changed files with 280 additions and 77 deletions

No files matched your search

+7
View File
@@ -58,9 +58,16 @@ dependencies {
// MQTT // MQTT
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5") implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
// JSON // JSON
implementation("com.fasterxml.jackson.core:jackson-databind:2.20.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.20.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.20.0")
implementation("com.fasterxml.jackson.core:jackson-core:2.20.0")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.20")
// Hashing
implementation("de.mkammerer:argon2-jvm:2.12")
} }
java { java {
toolchain { toolchain {
+1 -1
View File
@@ -8,7 +8,7 @@ org.gradle.warning.mode = all
org.gradle.configuration-cache = false org.gradle.configuration-cache = false
########################################################################## ##########################################################################
# Mod Properties # Mod Properties
mod_version = 1.0.0 mod_version = 0.1.1
mod_group = ciomek mod_group = ciomek
mod_name = loon mod_name = loon
########################################################################## ##########################################################################
+2
View File
@@ -2,6 +2,7 @@ package ciomek.loon;
import ciomek.loon.commands.ChangePasswordCommand; import ciomek.loon.commands.ChangePasswordCommand;
import ciomek.loon.commands.RegisterCommand; import ciomek.loon.commands.RegisterCommand;
import ciomek.loon.commands.WebUsernameCommand;
import ciomek.loon.mqtt.MQTTClient; import ciomek.loon.mqtt.MQTTClient;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.minecraft.core.net.command.CommandManager; import net.minecraft.core.net.command.CommandManager;
@@ -25,6 +26,7 @@ public class Loon implements ModInitializer {
CommandManager.registerCommand(new RegisterCommand()); CommandManager.registerCommand(new RegisterCommand());
CommandManager.registerCommand(new ChangePasswordCommand()); CommandManager.registerCommand(new ChangePasswordCommand());
CommandManager.registerCommand(new WebUsernameCommand());
MQTTClient.getInstance(); MQTTClient.getInstance();
} }
@@ -3,6 +3,7 @@ package ciomek.loon.commands;
import ciomek.loon.mqtt.MQTTClient; import ciomek.loon.mqtt.MQTTClient;
import ciomek.loon.mqtt.payload.ChangePasswordPayload; import ciomek.loon.mqtt.payload.ChangePasswordPayload;
import ciomek.loon.mqtt.payload.RegisterResponse; import ciomek.loon.mqtt.payload.RegisterResponse;
import ciomek.loon.utils.PasswordHasher;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentTypeString; import com.mojang.brigadier.arguments.ArgumentTypeString;
@@ -32,18 +33,11 @@ public class ChangePasswordCommand implements CommandManager.CommandRegistry {
return 0; return 0;
} }
String password = ArgumentTypeString.getString(context, "password"); String uuid = player.uuid.toString();
String responseTopic = "loon/auth/change_password/" + uuid + "/response";
ChangePasswordPayload payload = new ChangePasswordPayload(player.uuid.toString(), password); MQTTClient.getInstance().subscribe(responseTopic, (topic, message) -> {
MQTTClient.getInstance().unsubscribe(responseTopic);
MQTTClient.getInstance().publish(payload.topics().get(0), payload.toJson());
source.sendMessage("Sent request to the server! Waiting for the response. ");
MQTTClient.getInstance().subscribe("loon/register/+/response", (topic, message) -> {
String[] parts = topic.split("/");
String playerUuid = parts[2];
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
RegisterResponse response = objectMapper.readValue( RegisterResponse response = objectMapper.readValue(
@@ -64,6 +58,16 @@ public class ChangePasswordCommand implements CommandManager.CommandRegistry {
} }
}); });
String password = ArgumentTypeString.getString(context, "password");
String hashedPassword = PasswordHasher.hash(password);
ChangePasswordPayload payload = new ChangePasswordPayload(uuid, hashedPassword);
MQTTClient.getInstance().publish(payload.topics().get(0), payload.toJson());
source.sendMessage("Sent request to the server! Waiting for the response. ");
return 1; return 1;
}) })
) )
@@ -3,6 +3,7 @@ package ciomek.loon.commands;
import ciomek.loon.mqtt.MQTTClient; import ciomek.loon.mqtt.MQTTClient;
import ciomek.loon.mqtt.payload.RegisterPayload; import ciomek.loon.mqtt.payload.RegisterPayload;
import ciomek.loon.mqtt.payload.RegisterResponse; import ciomek.loon.mqtt.payload.RegisterResponse;
import ciomek.loon.utils.PasswordHasher;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentTypeString; import com.mojang.brigadier.arguments.ArgumentTypeString;
@@ -34,19 +35,11 @@ public class RegisterCommand implements CommandManager.CommandRegistry {
return 0; return 0;
} }
String internalUsername = ArgumentTypeString.getString(context, "internalUsername"); String uuid = player.uuid.toString();
String password = ArgumentTypeString.getString(context, "password"); String responseTopic = "loon/auth/register/" + uuid + "/response";
RegisterPayload payload = new RegisterPayload(player.uuid.toString(), password, internalUsername); MQTTClient.getInstance().subscribe(responseTopic, (topic, message) -> {
MQTTClient.getInstance().unsubscribe(responseTopic);
MQTTClient.getInstance().publish(payload.topics().get(0), payload.toJson());
source.sendMessage("Sent request to the server! Waiting for the response. ");
MQTTClient.getInstance().subscribe("loon/register/+/response", (topic, message) -> {
String[] parts = topic.split("/");
String playerUuid = parts[2];
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
RegisterResponse response = objectMapper.readValue( RegisterResponse response = objectMapper.readValue(
@@ -67,6 +60,17 @@ public class RegisterCommand implements CommandManager.CommandRegistry {
} }
}); });
String internalUsername = ArgumentTypeString.getString(context, "internalUsername");
String password = ArgumentTypeString.getString(context, "password");
String hashedPassword = PasswordHasher.hash(password);
RegisterPayload payload = new RegisterPayload(uuid, hashedPassword, internalUsername);
MQTTClient.getInstance().publish(payload.topics().get(0), payload.toJson());
source.sendMessage("Sent request to the server! Waiting for the response. ");
return 1; return 1;
}) })
) )
@@ -0,0 +1,57 @@
package ciomek.loon.commands;
import ciomek.loon.mqtt.MQTTClient;
import ciomek.loon.mqtt.payload.ShowUsernameResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilderLiteral;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.net.command.CommandManager;
import net.minecraft.core.net.command.CommandSource;
import static com.mojang.brigadier.builder.ArgumentBuilderLiteral.literal;
public class WebUsernameCommand implements CommandManager.CommandRegistry {
@SuppressWarnings("unchecked")
@Override
public void register(CommandDispatcher<CommandSource> commandDispatcher) {
commandDispatcher
.register(
(ArgumentBuilderLiteral<CommandSource>) (Object) literal("web-username")
.executes(context -> {
CommandSource source = (CommandSource) context.getSource();
Player player = source.getSender();
if (player == null) {
source.sendMessage("This command can only be run by a player!");
return 0;
}
String uuid = player.uuid.toString();
String responseTopic = "loon/auth/show_username/" + uuid + "/response";
MQTTClient.getInstance().subscribe(responseTopic, (topic, message) -> {
MQTTClient.getInstance().unsubscribe(responseTopic);
ObjectMapper objectMapper = new ObjectMapper();
ShowUsernameResponse response = objectMapper.readValue(
message.getPayload(),
ShowUsernameResponse.class
);
if (response == null)
return;
if (response.success())
source.sendMessage(response.internalUsername());
else
source.sendMessage("You are not registered. Use /register command. ");
});
MQTTClient.getInstance().publish("loon/auth/show_username/" + uuid, "{}");
return 1;
})
);
}
}
@@ -20,7 +20,7 @@ public class PacketHandlerServerMQTTMixin {
@Inject(method = "handleSlashCommand", at = @At("TAIL")) @Inject(method = "handleSlashCommand", at = @At("TAIL"))
private void onSlashCommand(String command, CallbackInfo ci) { private void onSlashCommand(String command, CallbackInfo ci) {
CommandPayload payload = new CommandPayload(command, playerEntity.username); CommandPayload payload = new CommandPayload(sanitizeCommand(command), playerEntity.username);
payload.send(); payload.send();
} }
@@ -59,4 +59,25 @@ public class PacketHandlerServerMQTTMixin {
payload.send(); payload.send();
} }
private String sanitizeCommand(String command) {
String normalized = command.startsWith("/") ? command.substring(1) : command;
String[] parts = normalized.split(" ");
if (parts.length == 0) {
return command;
}
if ("register".equals(parts[0]) && parts.length >= 3) {
parts[2] = "[REDACTED]";
return String.join(" ", parts);
}
if ("change-password".equals(parts[0]) && parts.length >= 2) {
parts[1] = "[REDACTED]";
return String.join(" ", parts);
}
return command;
}
} }
@@ -1,5 +1,6 @@
package ciomek.loon.mixin; package ciomek.loon.mixin;
import ciomek.loon.Loon;
import ciomek.loon.TPSTracker; import ciomek.loon.TPSTracker;
import ciomek.loon.mqtt.payload.data.PlayerIdentity; import ciomek.loon.mqtt.payload.data.PlayerIdentity;
import ciomek.loon.mqtt.payload.data.PositionData; import ciomek.loon.mqtt.payload.data.PositionData;
@@ -51,31 +52,9 @@ public class WorldServerMixin {
@Inject(method = "tick", at = @At("TAIL")) @Inject(method = "tick", at = @At("TAIL"))
private void onTick(CallbackInfo ci) { private void onTick(CallbackInfo ci) {
WorldServer self = (WorldServer) (Object) this; countTicks();
if (self.dimension == Dimension.OVERWORLD) { positionCountdown();
long now = System.nanoTime(); handleRequests();
if (lastTickNanos != 0) {
gapSumNanos += now - lastTickNanos;
gapTicks++;
if (gapSumNanos >= 1_000_000_000L) {
TPSTracker.setTPS(Math.min(20.0, gapTicks / (gapSumNanos / 1e9)));
gapSumNanos = 0;
gapTicks = 0;
}
}
lastTickNanos = now;
if (++positionCheckTicks >= POSITION_CHECK_INTERVAL_TICKS) {
positionCheckTicks = 0;
broadcastMovedPlayerPositions();
}
}
IRequest request;
while ((request = RequestManager.pollRequest()) != null) {
IRequest current = request;
EXECUTOR.submit(() -> current.handle(mcServer));
}
} }
@Unique @Unique
@@ -94,4 +73,52 @@ public class WorldServerMixin {
} }
} }
} }
@Unique private void countTicks()
{
WorldServer self = (WorldServer) (Object) this;
if (self.dimension == Dimension.OVERWORLD) {
long now = System.nanoTime();
if (lastTickNanos != 0) {
gapSumNanos += now - lastTickNanos;
gapTicks++;
if (gapSumNanos >= 1_000_000_000L) {
TPSTracker.setTPS(Math.min(20.0, gapTicks / (gapSumNanos / 1e9)));
gapSumNanos = 0;
gapTicks = 0;
}
}
lastTickNanos = now;
}
}
@Unique private void positionCountdown()
{
if (++positionCheckTicks >= POSITION_CHECK_INTERVAL_TICKS) {
positionCheckTicks = 0;
broadcastMovedPlayerPositions();
}
}
@Unique private void handleRequests()
{
IRequest request;
while ((request = RequestManager.pollRequest()) != null) {
IRequest current = request;
if (current.requireTickThread()) {
runRequest(current);
} else {
EXECUTOR.submit(() -> runRequest(current));
}
}
}
@Unique
private void runRequest(IRequest request) {
try {
request.handle(mcServer);
} catch (Exception e) {
Loon.LOGGER.error("Failed to handle request: {}, {}", request.getClass().getSimpleName(), e);
}
}
} }
+58 -19
View File
@@ -33,6 +33,14 @@ public class MQTTClient {
} }
} }
public void unsubscribe(String topicFilter) {
try {
client.unsubscribe(topicFilter);
} catch (MqttException e) {
throw new RuntimeException(e);
}
}
public void publish(String topic, String message, int qos) { public void publish(String topic, String message, int qos) {
MqttMessage mqttMessage = new MqttMessage(message.getBytes()); MqttMessage mqttMessage = new MqttMessage(message.getBytes());
mqttMessage.setQos(qos); mqttMessage.setQos(qos);
@@ -74,11 +82,31 @@ public class MQTTClient {
options.setUserName(username); options.setUserName(username);
options.setPassword(password.toCharArray()); options.setPassword(password.toCharArray());
options.setMaxInflight(10000); options.setMaxInflight(10000);
options.setAutomaticReconnect(true);
client.setCallback(new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
try {
client.subscribe("loon/player/+/inventory/full/request");
client.subscribe("loon/player/+/online/request");
client.subscribe("loon/player/+/position/request");
client.subscribe("loon/world/chunks/+/+/request");
client.subscribe("loon/server/info/request");
if (reconnect)
Loon.LOGGER.info("MQTT reconnected");
else
Loon.LOGGER.info("MQTT connected");
} catch (MqttException e) {
throw new RuntimeException(e);
}
}
client.setCallback(new MqttCallback() {
@Override @Override
public void connectionLost(Throwable cause) { public void connectionLost(Throwable cause) {
Loon.LOGGER.warn("MQTT connection lost, reconnecting...", cause);
} }
@Override @Override
@@ -88,11 +116,11 @@ public class MQTTClient {
Matcher matcher = pattern.matcher(topic); Matcher matcher = pattern.matcher(topic);
if (matcher.matches()) { if (matcher.matches()) {
UUID playerId = UUID.fromString(matcher.group(1)); UUID playerId = parsePlayerUuid(matcher.group(1));
if (playerId == null)
return;
InventoryRequest inventoryRequest = new InventoryRequest(playerId); RequestManager.addRequest(new InventoryRequest(playerId));
RequestManager.addRequest(inventoryRequest);
} }
} }
@@ -101,11 +129,11 @@ public class MQTTClient {
Matcher matcher = pattern.matcher(topic); Matcher matcher = pattern.matcher(topic);
if (matcher.matches()) { if (matcher.matches()) {
UUID playerId = UUID.fromString(matcher.group(1)); UUID playerId = parsePlayerUuid(matcher.group(1));
if (playerId == null)
return;
PlayerOnlineRequest playerOnlineRequest = new PlayerOnlineRequest(playerId); RequestManager.addRequest(new PlayerOnlineRequest(playerId));
RequestManager.addRequest(playerOnlineRequest);
} }
} }
@@ -114,11 +142,11 @@ public class MQTTClient {
Matcher matcher = pattern.matcher(topic); Matcher matcher = pattern.matcher(topic);
if (matcher.matches()) { if (matcher.matches()) {
UUID playerId = UUID.fromString(matcher.group(1)); UUID playerId = parsePlayerUuid(matcher.group(1));
if (playerId == null)
return;
PlayerPositionRequest request = new PlayerPositionRequest(playerId); RequestManager.addRequest(new PlayerPositionRequest(playerId));
RequestManager.addRequest(request);
} }
} }
@@ -126,6 +154,8 @@ public class MQTTClient {
Matcher matcher = pattern.matcher(topic); Matcher matcher = pattern.matcher(topic);
if (matcher.matches()) if (matcher.matches())
{
try
{ {
int xStart = Integer.parseInt(matcher.group(1)); int xStart = Integer.parseInt(matcher.group(1));
int xEnd = Integer.parseInt(matcher.group(2)); int xEnd = Integer.parseInt(matcher.group(2));
@@ -136,6 +166,11 @@ public class MQTTClient {
RequestManager.addRequest(request); RequestManager.addRequest(request);
} }
catch (NumberFormatException e)
{
}
}
if (topic.equals("loon/server/info/request")) { if (topic.equals("loon/server/info/request")) {
RequestManager.addRequest(new ServerInfoRequest()); RequestManager.addRequest(new ServerInfoRequest());
@@ -149,13 +184,17 @@ public class MQTTClient {
}); });
client.connect(options); client.connect(options);
client.subscribe("loon/player/+/inventory/full/request");
client.subscribe("loon/player/+/online/request");
client.subscribe("loon/player/+/position/request");
client.subscribe("loon/world/chunks/+/+/request");
client.subscribe("loon/server/info/request");
} catch (MqttException e) { } catch (MqttException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private static UUID parsePlayerUuid(String raw) {
try {
return UUID.fromString(raw);
} catch (IllegalArgumentException e) {
Loon.LOGGER.warn("Ignoring MQTT request with invalid player UUID: {}", raw);
return null;
}
}
} }
@@ -1,13 +1,15 @@
package ciomek.loon.mqtt.payload; package ciomek.loon.mqtt.payload;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.List; import java.util.List;
public record ChangePasswordPayload ( public record ChangePasswordPayload (
String uuid, @JsonIgnore String uuid,
String password String password
) implements Payload { ) implements Payload {
@Override @Override
public List<String> topics() { public List<String> topics() {
return List.of("loon/register/change_password"); return List.of("loon/auth/register/" + uuid + "/change_password");
} }
} }
@@ -1,14 +1,16 @@
package ciomek.loon.mqtt.payload; package ciomek.loon.mqtt.payload;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.List; import java.util.List;
public record RegisterPayload ( public record RegisterPayload (
String uuid, @JsonIgnore String uuid,
String password, String password,
String internalUsername String internalUsername
) implements Payload { ) implements Payload {
@Override @Override
public List<String> topics() { public List<String> topics() {
return List.of("loon/register/request"); return List.of("loon/auth/register/" + uuid + "/request");
} }
} }
@@ -6,6 +6,7 @@ import java.util.List;
public record ServerInfoPayload( public record ServerInfoPayload(
double tps, double tps,
String version, String version,
String domain,
String motd, String motd,
boolean onlineMode, boolean onlineMode,
int sleepPercentage, int sleepPercentage,
@@ -0,0 +1,7 @@
package ciomek.loon.mqtt.payload;
public record ShowUsernameResponse(
boolean success,
String error,
String internalUsername
) {}
@@ -19,6 +19,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public record ChunkMapRequest(int xStart, int xEnd, int zStart, int zEnd) implements IRequest { public record ChunkMapRequest(int xStart, int xEnd, int zStart, int zEnd) implements IRequest {
private static final boolean REQUIRE_TICK_THREAD = false;
@Override @Override
public void handle(MinecraftServer server) { public void handle(MinecraftServer server) {
World world = server.getDimensionWorld(0); World world = server.getDimensionWorld(0);
@@ -72,10 +74,11 @@ public record ChunkMapRequest(int xStart, int xEnd, int zStart, int zEnd) implem
Chunk resolveChunk(World world, ChunkProvider provider, ChunkLoader loader, int x, int z) Chunk resolveChunk(World world, ChunkProvider provider, ChunkLoader loader, int x, int z)
{ {
if (REQUIRE_TICK_THREAD) {
ChunkPos chunkPos = new ChunkPos(x, z); ChunkPos chunkPos = new ChunkPos(x, z);
if (provider.isChunkLoaded(chunkPos)) if (provider.isChunkLoaded(chunkPos))
return world.getChunk(chunkPos); return world.getChunk(chunkPos);
}
if (loader == null || !loader.chunkExists(world, x, z)) if (loader == null || !loader.chunkExists(world, x, z))
return null; return null;
@@ -86,4 +89,10 @@ public record ChunkMapRequest(int xStart, int xEnd, int zStart, int zEnd) implem
return null; return null;
} }
} }
@Override
public boolean requireTickThread()
{
return REQUIRE_TICK_THREAD;
}
} }
@@ -6,4 +6,8 @@ import net.minecraft.server.entity.player.PlayerServer;
public interface IRequest public interface IRequest
{ {
void handle(MinecraftServer server); void handle(MinecraftServer server);
default boolean requireTickThread()
{
return true;
}
} }
@@ -1,6 +1,7 @@
package ciomek.loon.mqtt.request; package ciomek.loon.mqtt.request;
import ciomek.loon.TPSTracker; import ciomek.loon.TPSTracker;
import ciomek.loon.Loon;
import ciomek.loon.mixin.WorldServerMixin; import ciomek.loon.mixin.WorldServerMixin;
import ciomek.loon.mqtt.payload.ServerInfoPayload; import ciomek.loon.mqtt.payload.ServerInfoPayload;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
@@ -8,9 +9,15 @@ import net.minecraft.server.MinecraftServer;
public record ServerInfoRequest() implements IRequest { public record ServerInfoRequest() implements IRequest {
@Override @Override
public void handle(MinecraftServer server) { public void handle(MinecraftServer server) {
String domain = System.getenv(Loon.EnvVarPrefix + "DOMAIN");
if (domain == null) {
domain = "";
}
ServerInfoPayload payload = new ServerInfoPayload( ServerInfoPayload payload = new ServerInfoPayload(
TPSTracker.getTPS(), TPSTracker.getTPS(),
server.getMinecraftVersion(), server.getMinecraftVersion(),
domain,
server.motd, server.motd,
server.onlineMode, server.onlineMode,
server.sleepPercentage, server.sleepPercentage,
@@ -0,0 +1,10 @@
package ciomek.loon.utils;
import de.mkammerer.argon2.Argon2Factory;
public class PasswordHasher {
public static String hash(String password) {
var argon2 = Argon2Factory.create();
return argon2.hash(3, 65536, 1, password.toCharArray());
}
}
+3 -3
View File
@@ -2,10 +2,10 @@
"schemaVersion": 1, "schemaVersion": 1,
"id": "loon", "id": "loon",
"version": "${version}", "version": "${version}",
"name": "Example Mod", "name": "loonmc",
"description": "This mod aims to help new BTA modders.", "description": "Mod that sends data to MQTT server.",
"authors": [ "authors": [
"Turnip Labs" "ciomek"
], ],
"contact": { "contact": {
"homepage": "", "homepage": "",