diff --git a/.github/workflows/main.sh b/.github/workflows/main.sh index 0a145b7..277b6ca 100644 --- a/.github/workflows/main.sh +++ b/.github/workflows/main.sh @@ -21,7 +21,7 @@ echo "MC_VERSION=$mcVersion" >> $GITHUB_ENV # Checks if build is stable (I always bump version when I release stable, uploadable version) -latestRelease=$(curl -s "https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest" | grep -oP '"tag_name": "\K(.*)(?=")') +latestRelease=$(curl -s "https://api.github.com/repos/$GITHUB_REPOSITORY/releases" | grep -oP '(?<="tag_name": ")[^"]*' | head -n 1) echo "Latest release is: $latestRelease" diff --git a/gradle.properties b/gradle.properties index a678e85..a446c27 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,15 +2,15 @@ org.gradle.jvmargs=-Xmx1G # Fabric properties -minecraft_version=1.16.3 -yarn_mappings=1.16.3+build.1 -loader_version=0.9.3+build.207 +minecraft_version=1.16.4 +yarn_mappings=1.16.4+build.1 +loader_version=0.10.6+build.214 #Fabric api -fabric_version=0.20.2+build.402-1.16 +fabric_version=0.25.1+build.416-1.16 # Mod Properties -mod_version = 1.6.0 +mod_version = 1.6.5 maven_group = org.samo_lego archives_base_name = simpleauth diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda8..62d4c05 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 804ccdc..99fd715 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Thu Jun 04 11:39:28 CEST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java b/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java index 4b8f910..ce0e8cd 100644 --- a/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java +++ b/src/main/java/org/samo_lego/simpleauth/SimpleAuth.java @@ -41,15 +41,23 @@ public class SimpleAuth implements DedicatedServerModInitializer { * It's cleared on server stop in order to save some interactions with database during runtime. * Stores their data as {@link org.samo_lego.simpleauth.storage.PlayerCache PlayerCache} object. */ - public static HashMap playerCacheMap = new HashMap<>(); + public static final HashMap playerCacheMap = new HashMap<>(); + + /** + * HashSet of player names that have Mojang accounts. + * If player is saved in here, they will be treated as online-mode ones. + */ + public static final HashSet mojangAccountNamesCache = new HashSet<>(); // Getting game directory public static final Path gameDirectory = FabricLoader.getInstance().getGameDir(); // Server properties - public static Properties serverProp = new Properties(); + public static final Properties serverProp = new Properties(); - // Mod config + /** + * Config of the SimpleAuth mod. + */ public static AuthConfig config; @Override @@ -59,6 +67,12 @@ public class SimpleAuth implements DedicatedServerModInitializer { // The support on discord was great! I really appreciate your help. logInfo("This mod wouldn't exist without the awesome Fabric Community. TYSM guys!"); + try { + serverProp.load(new FileReader(gameDirectory + "/server.properties")); + } catch (IOException e) { + logError("Error while reading server properties: " + e.getMessage()); + } + // Creating data directory (database and config files are stored there) File file = new File(gameDirectory + "/mods/SimpleAuth/leveldbStore"); if (!file.exists() && !file.mkdirs()) @@ -68,12 +82,6 @@ public class SimpleAuth implements DedicatedServerModInitializer { // Connecting to db DB.openConnection(); - try { - serverProp.load(new FileReader(gameDirectory + "/server.properties")); - } catch (IOException e) { - logError("Error while reading server properties: " + e.getMessage()); - } - // Registering the commands CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { @@ -113,7 +121,7 @@ public class SimpleAuth implements DedicatedServerModInitializer { // Closing threads try { THREADPOOL.shutdownNow(); - if (!THREADPOOL.awaitTermination(100, TimeUnit.MICROSECONDS)) { + if (!THREADPOOL.awaitTermination(500, TimeUnit.MILLISECONDS)) { Thread.currentThread().interrupt(); } } catch (InterruptedException e) { diff --git a/src/main/java/org/samo_lego/simpleauth/commands/LogoutCommand.java b/src/main/java/org/samo_lego/simpleauth/commands/LogoutCommand.java index 4266829..d9dac8c 100644 --- a/src/main/java/org/samo_lego/simpleauth/commands/LogoutCommand.java +++ b/src/main/java/org/samo_lego/simpleauth/commands/LogoutCommand.java @@ -9,6 +9,7 @@ import org.samo_lego.simpleauth.utils.PlayerAuth; import static net.minecraft.server.command.CommandManager.literal; import static org.samo_lego.simpleauth.SimpleAuth.config; +import static org.samo_lego.simpleauth.SimpleAuth.mojangAccountNamesCache; public class LogoutCommand { @@ -22,8 +23,12 @@ public class LogoutCommand { private static int logout(ServerCommandSource serverCommandSource) throws CommandSyntaxException { ServerPlayerEntity player = serverCommandSource.getPlayer(); - ((PlayerAuth) player).setAuthenticated(false); - player.sendMessage(new LiteralText(config.lang.successfulLogout), false); + if(!mojangAccountNamesCache.contains(player.getGameProfile().getName().toLowerCase())) { + ((PlayerAuth) player).setAuthenticated(false); + player.sendMessage(new LiteralText(config.lang.successfulLogout), false); + } + else + player.sendMessage(new LiteralText(config.lang.cannotLogout), false); return 1; } } diff --git a/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java b/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java index a20be4e..580999f 100644 --- a/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java +++ b/src/main/java/org/samo_lego/simpleauth/event/AuthEventHandler.java @@ -63,7 +63,6 @@ public class AuthEventHandler { // Player joining the server public static void onPlayerJoin(ServerPlayerEntity player) { - // If player is fake auth is not needed if (((PlayerAuth) player).canSkipAuth()) return; // Checking if session is still valid @@ -76,22 +75,12 @@ public class AuthEventHandler { playerCache.validUntil >= System.currentTimeMillis() && player.getIp().equals(playerCache.lastIp) ) { + // Valid session ((PlayerAuth) player).setAuthenticated(true); return; } - player.setInvulnerable(config.experimental.playerInvulnerable); - player.setInvisible(config.experimental.playerInvisible); - - // Invalidating session - playerCache.isAuthenticated = false; - if(config.main.spawnOnJoin) - ((PlayerAuth) player).hidePosition(true); - } - else { - ((PlayerAuth) player).setAuthenticated(false); - playerCache = playerCacheMap.get(uuid); - playerCache.wasOnFire = false; } + ((PlayerAuth) player).setAuthenticated(false); // Tries to rescue player from nether portal @@ -116,10 +105,8 @@ public class AuthEventHandler { String uuid = ((PlayerAuth) player).getFakeUuid(); PlayerCache playerCache = playerCacheMap.get(uuid); - if(((PlayerAuth) player).isAuthenticated()) { + if(playerCache.isAuthenticated) { playerCache.lastIp = player.getIp(); - playerCache.lastAir = player.getAir(); - playerCache.wasOnFire = player.isOnFire(); playerCache.wasInPortal = player.getBlockState().getBlock().equals(Blocks.NETHER_PORTAL); // Setting the session expire time @@ -151,13 +138,8 @@ public class AuthEventHandler { public static ActionResult onPlayerMove(PlayerEntity player) { // Player will fall if enabled (prevent fly kick) boolean auth = ((PlayerAuth) player).isAuthenticated(); - if(!auth && config.main.allowFalling && !player.isOnGround() && !player.isInsideWaterOrBubbleColumn()) { - if(player.isInvulnerable()) - player.setInvulnerable(false); - return ActionResult.PASS; - } // Otherwise movement should be disabled - else if(!auth && !config.experimental.allowMovement) { + if(!auth && !config.experimental.allowMovement) { if(!player.isInvulnerable()) player.setInvulnerable(true); return ActionResult.FAIL; diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerAdvancementTracker.java b/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerAdvancementTracker.java new file mode 100644 index 0000000..61a7581 --- /dev/null +++ b/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerAdvancementTracker.java @@ -0,0 +1,46 @@ +package org.samo_lego.simpleauth.mixin; + +import net.minecraft.advancement.PlayerAdvancementTracker; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.ServerAdvancementLoader; +import net.minecraft.server.network.ServerPlayerEntity; +import org.samo_lego.simpleauth.utils.PlayerAuth; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.io.File; + +import static org.samo_lego.simpleauth.SimpleAuth.config; + +@Mixin(PlayerAdvancementTracker.class) +public class MixinPlayerAdvancementTracker { + + @Mutable + @Shadow + @Final + private File advancementFile; + + @Shadow + private ServerPlayerEntity owner; + + @Inject(method = "load(Lnet/minecraft/server/ServerAdvancementLoader;)V", at = @At("HEAD")) + private void startMigratingOfflineAdvancements(ServerAdvancementLoader advancementLoader, CallbackInfo ci) { + if(config.experimental.premiumAutologin && !config.experimental.forceoOfflineUuids && ((PlayerAuth) this.owner).isUsingMojangAccount() && !this.advancementFile.isFile()) { + // Migrate + String playername = owner.getGameProfile().getName(); + this.advancementFile = new File(this.advancementFile.getParent(), PlayerEntity.getOfflinePlayerUuid(playername).toString() + ".json"); + } + } + + @Inject(method = "load(Lnet/minecraft/server/ServerAdvancementLoader;)V", at = @At("TAIL")) + private void endMigratingOfflineAdvancements(ServerAdvancementLoader advancementLoader, CallbackInfo ci) { + if(config.experimental.premiumAutologin && !config.experimental.forceoOfflineUuids && ((PlayerAuth) this.owner).isUsingMojangAccount()) { + this.advancementFile = new File(this.advancementFile.getParent(), owner.getUuid() + ".json"); + } + } +} diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.java b/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.java index 66edfc9..73655f2 100644 --- a/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.java +++ b/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.java @@ -1,220 +1,17 @@ package org.samo_lego.simpleauth.mixin; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.LiteralText; -import net.minecraft.text.Text; import net.minecraft.util.ActionResult; -import net.minecraft.util.Identifier; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; -import net.minecraft.world.World; -import org.samo_lego.simpleauth.SimpleAuth; import org.samo_lego.simpleauth.event.item.DropItemCallback; -import org.samo_lego.simpleauth.storage.PlayerCache; -import org.samo_lego.simpleauth.utils.PlayerAuth; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import static org.samo_lego.simpleauth.SimpleAuth.*; -import static org.samo_lego.simpleauth.utils.CarpetHelper.isPlayerCarpetFake; - @Mixin(PlayerEntity.class) -public abstract class MixinPlayerEntity implements PlayerAuth { +public class MixinPlayerEntity { - private final ServerPlayerEntity player = (ServerPlayerEntity) (Object) this; - - // * 20 for 20 ticks in second - private int kickTimer = config.main.kickTime * 20; - - private final boolean isRunningCarpet = FabricLoader.getInstance().isModLoaded("carpet"); - - private final MinecraftServer server = player.getServer(); - - /** - * Teleports player to spawn or last location that is recorded. - * Last location means the location before de-authentication. - * - * @param hide whether to teleport player to spawn (provided in config) or last recorded position - */ - @Override - public void hidePosition(boolean hide) { - assert server != null; - - PlayerCache cache = playerCacheMap.get(this.getFakeUuid()); - System.out.println("Teleporting Player. hide:" + hide); - if (hide) { - // Saving position - cache.lastLocation.dimension = player.getServerWorld(); - cache.lastLocation.position = player.getPos(); - cache.lastLocation.yaw = player.yaw; - cache.lastLocation.pitch = player.pitch; - - // Teleports player to spawn - player.teleport( - server.getWorld(RegistryKey.of(Registry.DIMENSION, new Identifier(config.worldSpawn.dimension))), - config.worldSpawn.x, - config.worldSpawn.y, - config.worldSpawn.z, - config.worldSpawn.yaw, - config.worldSpawn.pitch - ); - return; - } - // Puts player to last cached position - player.teleport( - cache.lastLocation.dimension, - cache.lastLocation.position.getX(), - cache.lastLocation.position.getY(), - cache.lastLocation.position.getZ(), - cache.lastLocation.yaw, - cache.lastLocation.pitch - ); - } - - /** - * Converts player uuid, to ensure player with "nAmE" and "NamE" get same uuid. - * Both players are not allowed to play, since mod mimics Mojang behaviour. - * of not allowing accounts with same names but different capitalization. - * - * @return converted UUID as string - */ - @Override - public String getFakeUuid() { - // If server is in online mode online-mode UUIDs should be used - assert server != null; - if(server.isOnlineMode()) - return player.getUuidAsString(); - /* - Lower case is used for Player and PlAyEr to get same UUID (for password storing) - Mimicking Mojang behaviour, where players cannot set their name to - ExAmple if Example is already taken. - */ - String playername = player.getName().asString().toLowerCase(); - return PlayerEntity.getOfflinePlayerUuid(playername).toString(); - - } - - /** - * Sets the authentication status of the player. - * - * @param authenticated whether player should be authenticated - */ - @Override - public void setAuthenticated(boolean authenticated) { - PlayerCache playerCache; - - if(!playerCacheMap.containsKey(this.getFakeUuid())) { - // First join - String jsonString = DB.getUserData(this.getFakeUuid()); - if(jsonString != null && !jsonString.isEmpty()) - playerCache = PlayerCache.fromJson(player, jsonString); - else - playerCache = new PlayerCache(player); - - // Saving to hashmap of player caches - playerCacheMap.put(this.getFakeUuid(), playerCache); - } - else { - playerCache = playerCacheMap.get(this.getFakeUuid()); - if(this.isAuthenticated() == authenticated) - return; - playerCache.isAuthenticated = authenticated; - } - - player.setInvulnerable(!authenticated && config.experimental.playerInvulnerable); - player.setInvisible(!authenticated && config.experimental.playerInvisible); - - // Teleporting player (hiding / restoring position) - if(config.main.spawnOnJoin) - this.hidePosition(!authenticated); - - if(authenticated) { - kickTimer = config.main.kickTime * 20; - // Updating blocks if needed (if portal rescue action happened) - if(playerCache.wasInPortal) { - World world = player.getEntityWorld(); - BlockPos pos = player.getBlockPos(); - - // Sending updates to portal blocks - // This is technically not needed, but it cleans the "messed portal" on the client - world.updateListeners(pos, world.getBlockState(pos), world.getBlockState(pos), 3); - world.updateListeners(pos.up(), world.getBlockState(pos.up()), world.getBlockState(pos.up()), 3); - } - - // Setting last air to player - if(player.isSubmergedInWater()) - player.setAir(playerCache.lastAir); - - // In case player is in lava during authentication proccess - if(!playerCache.wasOnFire) - player.setFireTicks(0); - } - } - - /** - * Gets the text which tells the player - * to login or register, depending on account status. - * - * @return LiteralText with appropriate string (login or register) - */ - @Override - public Text getAuthMessage() { - final PlayerCache cache = playerCacheMap.get(((PlayerAuth) player).getFakeUuid()); - if(SimpleAuth.config.main.enableGlobalPassword || cache.isRegistered) - return new LiteralText( - SimpleAuth.config.lang.notAuthenticated + "\n" + SimpleAuth.config.lang.loginRequired - ); - return new LiteralText( - SimpleAuth.config.lang.notAuthenticated+ "\n" + SimpleAuth.config.lang.registerRequired - ); - } - - /** - * Checks whether player is a fake player (from CarpetMod). - * - * @return true if player is fake (can skip authentication process), otherwise false - */ - @Override - public boolean canSkipAuth() { - // We ask CarpetHelper class since it has the imports needed - return this.isRunningCarpet && isPlayerCarpetFake(this.player); - } - - /** - * Checks whether player is authenticated. - * - * @return false if player is not authenticated, otherwise true. - */ - @Override - public boolean isAuthenticated() { - String uuid = ((PlayerAuth) player).getFakeUuid(); - return playerCacheMap.containsKey(uuid) && playerCacheMap.get(uuid).isAuthenticated; - } - - @Inject(method = "tick()V", at = @At("HEAD"), cancellable = true) - private void tick(CallbackInfo ci) { - if(!this.isAuthenticated()) { - // Checking player timer - if(kickTimer <= 0 && player.networkHandler.getConnection().isOpen()) { - player.networkHandler.disconnect(new LiteralText(config.lang.timeExpired)); - } - else { - // Sending authentication prompt every 10 seconds - if(kickTimer % 200 == 0) - player.sendMessage(this.getAuthMessage(), false); - kickTimer--; - } - ci.cancel(); - } - } // Player item dropping @Inject(method = "dropSelectedItem(Z)Z", at = @At("HEAD"), cancellable = true) @@ -223,14 +20,6 @@ public abstract class MixinPlayerEntity implements PlayerAuth { ActionResult result = DropItemCallback.EVENT.invoker().onDropItem(player); if (result == ActionResult.FAIL) { - // Canceling the item drop, as well as giving the items back to player (and updating inv with packet) - player.networkHandler.sendPacket( - new ScreenHandlerSlotUpdateS2CPacket( - -2, - player.inventory.selectedSlot, - player.inventory.getStack(player.inventory.selectedSlot)) - ); - player.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-1, -1, player.inventory.getCursorStack())); cir.setReturnValue(false); } } diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerManager.java b/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerManager.java index d0ae290..784d5c1 100644 --- a/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerManager.java +++ b/src/main/java/org/samo_lego/simpleauth/mixin/MixinPlayerManager.java @@ -1,25 +1,39 @@ package org.samo_lego.simpleauth.mixin; import com.mojang.authlib.GameProfile; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.ClientConnection; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.PlayerManager; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.stat.ServerStatHandler; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import org.samo_lego.simpleauth.event.entity.player.PlayerJoinServerCallback; import org.samo_lego.simpleauth.event.entity.player.PlayerLeaveServerCallback; import org.samo_lego.simpleauth.event.entity.player.PrePlayerJoinCallback; +import org.samo_lego.simpleauth.utils.PlayerAuth; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import java.io.File; import java.net.SocketAddress; +import java.util.UUID; + +import static org.samo_lego.simpleauth.SimpleAuth.config; @Mixin(PlayerManager.class) public abstract class MixinPlayerManager { + @Shadow @Final private MinecraftServer server; + @Inject(method = "onPlayerConnect(Lnet/minecraft/network/ClientConnection;Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At("RETURN")) private void onPlayerConnect(ClientConnection clientConnection, ServerPlayerEntity serverPlayerEntity, CallbackInfo ci) { PlayerJoinServerCallback.EVENT.invoker().onPlayerJoin(serverPlayerEntity); @@ -30,7 +44,6 @@ public abstract class MixinPlayerManager { PlayerLeaveServerCallback.EVENT.invoker().onPlayerLeave(serverPlayerEntity); } - // Method for kicking player for @Inject(method = "checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;", at = @At("HEAD"), cancellable = true) private void checkCanJoin(SocketAddress socketAddress, GameProfile profile, CallbackInfoReturnable cir) { // Getting the player that is trying to join the server @@ -43,4 +56,35 @@ public abstract class MixinPlayerManager { cir.setReturnValue(returnText); } } + + @ModifyVariable( + method = "createStatHandler(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/stat/ServerStatHandler;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/player/PlayerEntity;getName()Lnet/minecraft/text/Text;" + ), + ordinal = 1 + ) + private File migrateOfflineStats(File file, PlayerEntity player) { + if(config.experimental.premiumAutologin && !config.experimental.forceoOfflineUuids && ((PlayerAuth) player).isUsingMojangAccount()) { + String playername = player.getGameProfile().getName(); + file = new File(file.getParent(), PlayerEntity.getOfflinePlayerUuid(playername) + ".json"); + } + return file; + } + + @Inject( + method = "createStatHandler(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/stat/ServerStatHandler;", + at = @At( + value = "INVOKE", + target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void migrateOfflineStats(PlayerEntity player, CallbackInfoReturnable cir, UUID uUID, ServerStatHandler serverStatHandler, File serverStatsDir, File playerStatFile) { + File onlineFile = new File(serverStatsDir, uUID + ".json"); + if(config.experimental.premiumAutologin && !config.experimental.forceoOfflineUuids && ((PlayerAuth) player).isUsingMojangAccount() && !onlineFile.exists()) { + ((ServerStatHandlerAccessor) serverStatHandler).setFile(onlineFile); + } + } } diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerLoginNetworkHandler.java b/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerLoginNetworkHandler.java new file mode 100644 index 0000000..6fa1ac6 --- /dev/null +++ b/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerLoginNetworkHandler.java @@ -0,0 +1,123 @@ +package org.samo_lego.simpleauth.mixin; + +import com.mojang.authlib.GameProfile; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import net.minecraft.text.TranslatableText; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.net.ssl.HttpsURLConnection; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.samo_lego.simpleauth.SimpleAuth.*; +import static org.samo_lego.simpleauth.utils.SimpleLogger.logError; + +@Mixin(ServerLoginNetworkHandler.class) +public abstract class MixinServerLoginNetworkHandler { + + @Shadow + private GameProfile profile; + @Shadow + private int loginTicks; + + @Shadow protected abstract GameProfile toOfflineProfile(GameProfile profile); + + /** + * Fake state of current player. + */ + @Unique + private boolean acceptCrackedPlayer = false; + + /** + * Mimicks the ticking if autologin is enabled. + * @param ci + */ + @Inject(method = "tick()V", at = @At("HEAD"), cancellable = true) + private void preTick(CallbackInfo ci) { + if (this.acceptCrackedPlayer && config.experimental.premiumAutologin) { + ((ServerLoginNetworkHandler) (Object) this).acceptPlayer(); + + if (this.loginTicks++ == 600) + ((ServerLoginNetworkHandler) (Object) this).disconnect(new TranslatableText("multiplayer.disconnect.slow_login")); + ci.cancel(); + } + } + + @Inject(method = "acceptPlayer()V", at = @At("HEAD")) + private void acceptPlayer(CallbackInfo ci) { + if(config.experimental.forceoOfflineUuids) { + this.profile = this.toOfflineProfile(this.profile); + } + } + + /** + * Checks whether the player has purchased an account. + * If so, server is presented as online, and continues as in normal-online mode. + * Otherwise, player is marked as ready to be accepted into the game. + * @param packet + * @param ci + */ + @Inject( + method = "onHello(Lnet/minecraft/network/packet/c2s/login/LoginHelloC2SPacket;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/network/packet/c2s/login/LoginHelloC2SPacket;getProfile()Lcom/mojang/authlib/GameProfile;", + shift = At.Shift.AFTER + ), + cancellable = true + ) + private void checkPremium(LoginHelloC2SPacket packet, CallbackInfo ci) { + if(config.experimental.premiumAutologin) { + try { + String playername = packet.getProfile().getName().toLowerCase(); + Pattern pattern = Pattern.compile("^[a-z0-9_]{3,16}$"); + Matcher matcher = pattern.matcher(playername); + if(playerCacheMap.containsKey(PlayerEntity.getOfflinePlayerUuid(playername).toString()) || !matcher.matches()) { + // Player definitely doesn't have a mojang account + this.acceptCrackedPlayer = true; + + this.profile = packet.getProfile(); + ci.cancel(); + } + else if(!mojangAccountNamesCache.contains(playername)) { + // Checking account status from API + HttpsURLConnection httpsURLConnection = (HttpsURLConnection) new URL("https://api.mojang.com/users/profiles/minecraft/" + playername).openConnection(); + httpsURLConnection.setRequestMethod("GET"); + httpsURLConnection.setConnectTimeout(5000); + httpsURLConnection.setReadTimeout(5000); + + int response = httpsURLConnection.getResponseCode(); + if (response == HttpURLConnection.HTTP_OK) { + // Player has a Mojang account + httpsURLConnection.disconnect(); + + + // Caches the request + mojangAccountNamesCache.add(playername); + // Authentication continues in original method + } + else if(response == HttpURLConnection.HTTP_NO_CONTENT) { + // Player doesn't have a Mojang account + httpsURLConnection.disconnect(); + this.acceptCrackedPlayer = true; + + this.profile = packet.getProfile(); + ci.cancel(); + } + } + } catch (IOException e) { + logError(e.getMessage()); + } + } + } +} diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerPlayerEntity.java b/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerPlayerEntity.java new file mode 100644 index 0000000..753db51 --- /dev/null +++ b/src/main/java/org/samo_lego/simpleauth/mixin/MixinServerPlayerEntity.java @@ -0,0 +1,216 @@ +package org.samo_lego.simpleauth.mixin; + +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.World; +import org.samo_lego.simpleauth.SimpleAuth; +import org.samo_lego.simpleauth.storage.PlayerCache; +import org.samo_lego.simpleauth.utils.PlayerAuth; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import static org.samo_lego.simpleauth.SimpleAuth.*; +import static org.samo_lego.simpleauth.utils.CarpetHelper.isPlayerCarpetFake; +import static org.samo_lego.simpleauth.utils.SimpleLogger.logInfo; + +@Mixin(ServerPlayerEntity.class) +public class MixinServerPlayerEntity implements PlayerAuth { + + private final ServerPlayerEntity player = (ServerPlayerEntity) (Object) this; + + // * 20 for 20 ticks in second + private int kickTimer = config.main.kickTime * 20; + + private final boolean isRunningCarpet = FabricLoader.getInstance().isModLoaded("carpet"); + + @Final + @Shadow + public MinecraftServer server; + + /** + * Teleports player to spawn or last location that is recorded. + * Last location means the location before de-authentication. + * + * @param hide whether to teleport player to spawn (provided in config) or last recorded position + */ + @Override + public void hidePosition(boolean hide) { + assert server != null; + + PlayerCache cache = playerCacheMap.get(this.getFakeUuid()); + if(config.main.spawnOnJoin) + logInfo("Teleporting " + player.getName().asString() + (hide ? " to spawn." : " to original position.")); + if (hide) { + // Saving position + cache.lastLocation.dimension = player.getServerWorld(); + cache.lastLocation.position = player.getPos(); + cache.lastLocation.yaw = player.yaw; + cache.lastLocation.pitch = player.pitch; + + // Teleports player to spawn + player.teleport( + server.getWorld(RegistryKey.of(Registry.DIMENSION, new Identifier(config.worldSpawn.dimension))), + config.worldSpawn.x, + config.worldSpawn.y, + config.worldSpawn.z, + config.worldSpawn.yaw, + config.worldSpawn.pitch + ); + return; + } + // Puts player to last cached position + player.teleport( + cache.lastLocation.dimension, + cache.lastLocation.position.getX(), + cache.lastLocation.position.getY(), + cache.lastLocation.position.getZ(), + cache.lastLocation.yaw, + cache.lastLocation.pitch + ); + } + + /** + * Converts player uuid, to ensure player with "nAmE" and "NamE" get same uuid. + * Both players are not allowed to play, since mod mimics Mojang behaviour. + * of not allowing accounts with same names but different capitalization. + * + * @return converted UUID as string + */ + @Override + public String getFakeUuid() { + // If server is in online mode online-mode UUIDs should be used + assert server != null; + if(server.isOnlineMode() && this.isUsingMojangAccount()) + return player.getUuidAsString(); + /* + Lower case is used for Player and PlAyEr to get same UUID (for password storing) + Mimicking Mojang behaviour, where players cannot set their name to + ExAmple if Example is already taken. + */ + // Getting player+s name via GameProfile, in order to be compatible with Drogtor mod + String playername = player.getGameProfile().getName().toLowerCase(); + return PlayerEntity.getOfflinePlayerUuid(playername).toString(); + + } + + /** + * Sets the authentication status of the player. + * + * @param authenticated whether player should be authenticated + */ + @Override + public void setAuthenticated(boolean authenticated) { + PlayerCache playerCache; + + if(!playerCacheMap.containsKey(this.getFakeUuid())) { + // First join + playerCache = new PlayerCache(this.getFakeUuid(), player); + playerCacheMap.put(this.getFakeUuid(), playerCache); + } + else { + playerCache = playerCacheMap.get(this.getFakeUuid()); + if(this.isAuthenticated() == authenticated) + return; + playerCache.isAuthenticated = authenticated; + } + + player.setInvulnerable(!authenticated && config.experimental.playerInvulnerable); + player.setInvisible(!authenticated && config.experimental.playerInvisible); + + // Teleporting player (hiding / restoring position) + if(config.main.spawnOnJoin) + this.hidePosition(!authenticated); + + if(authenticated) { + kickTimer = config.main.kickTime * 20; + // Updating blocks if needed (if portal rescue action happened) + if(playerCache.wasInPortal) { + World world = player.getEntityWorld(); + BlockPos pos = player.getBlockPos(); + + // Sending updates to portal blocks + // This is technically not needed, but it cleans the "messed portal" on the client + world.updateListeners(pos, world.getBlockState(pos), world.getBlockState(pos), 3); + world.updateListeners(pos.up(), world.getBlockState(pos.up()), world.getBlockState(pos.up()), 3); + } + } + } + + /** + * Gets the text which tells the player + * to login or register, depending on account status. + * + * @return LiteralText with appropriate string (login or register) + */ + @Override + public Text getAuthMessage() { + final PlayerCache cache = playerCacheMap.get(((PlayerAuth) player).getFakeUuid()); + if(SimpleAuth.config.main.enableGlobalPassword || cache.isRegistered) + return new LiteralText( + SimpleAuth.config.lang.notAuthenticated + "\n" + SimpleAuth.config.lang.loginRequired + ); + return new LiteralText( + SimpleAuth.config.lang.notAuthenticated+ "\n" + SimpleAuth.config.lang.registerRequired + ); + } + + /** + * Checks whether player can skip authentication process. + * + * @return true if can skip authentication process, otherwise false + */ + @Override + public boolean canSkipAuth() { + // We ask CarpetHelper class since it has the imports needed + return (this.isRunningCarpet && isPlayerCarpetFake(this.player)) || (isUsingMojangAccount() && config.experimental.premiumAutologin); + } + + /** + * Whether the player is using the mojang account. + * @return true if paid, otherwise false + */ + @Override + public boolean isUsingMojangAccount() { + return mojangAccountNamesCache.contains(player.getGameProfile().getName().toLowerCase()); + } + + /** + * Checks whether player is authenticated. + * + * @return false if player is not authenticated, otherwise true. + */ + @Override + public boolean isAuthenticated() { + String uuid = ((PlayerAuth) player).getFakeUuid(); + return this.canSkipAuth() || (playerCacheMap.containsKey(uuid) && playerCacheMap.get(uuid).isAuthenticated); + } + + @Inject(method = "playerTick()V", at = @At("HEAD"), cancellable = true) + private void playerTick(CallbackInfo ci) { + if(!this.isAuthenticated()) { + // Checking player timer + if(kickTimer <= 0 && player.networkHandler.getConnection().isOpen()) { + player.networkHandler.disconnect(new LiteralText(config.lang.timeExpired)); + } + else { + // Sending authentication prompt every 10 seconds + if(kickTimer % 200 == 0) + player.sendMessage(this.getAuthMessage(), false); + --kickTimer; + } + ci.cancel(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/MixinWorldSaveHandler.java b/src/main/java/org/samo_lego/simpleauth/mixin/MixinWorldSaveHandler.java new file mode 100644 index 0000000..3651d48 --- /dev/null +++ b/src/main/java/org/samo_lego/simpleauth/mixin/MixinWorldSaveHandler.java @@ -0,0 +1,100 @@ +package org.samo_lego.simpleauth.mixin; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.world.WorldSaveHandler; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import static org.samo_lego.simpleauth.SimpleAuth.config; +import static org.samo_lego.simpleauth.SimpleAuth.mojangAccountNamesCache; +import static org.samo_lego.simpleauth.utils.SimpleLogger.logInfo; + +@Mixin(WorldSaveHandler.class) +public class MixinWorldSaveHandler { + + @Final + @Shadow + private File playerDataDir; + + @Unique + private boolean fileExists; + + @Final + @Shadow + private static Logger LOGGER; + + /** + * Saves whether player save file exists. + * + * @param playerEntity + * @param cir + * @param compoundTag + * @param file + */ + @Inject( + method = "loadPlayerData(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/nbt/CompoundTag;", + at = @At( + value = "INVOKE", + target = "Ljava/io/File;exists()Z" + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void fileExists(PlayerEntity playerEntity, CallbackInfoReturnable cir, CompoundTag compoundTag, File file) { + // @ModifyVariable cannot capture locals + this.fileExists = file.exists(); + } + + /** + * Loads offline-uuid player data to compoundTag in order to migrate from offline to online. + * + * @param compoundTag null compound tag. + * @param player player who might need migration of datd. + * @return compoundTag containing migrated data. + */ + @ModifyVariable( + method = "loadPlayerData(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/nbt/CompoundTag;", + at = @At( + value = "INVOKE", + target = "Ljava/io/File;exists()Z" + ) + ) + private CompoundTag migratePlayerData(CompoundTag compoundTag, PlayerEntity player) { + // Checking for offline player data only if online doesn't exist yet + String playername = player.getGameProfile().getName().toLowerCase(); + if(config.experimental.premiumAutologin && mojangAccountNamesCache.contains(playername) && !this.fileExists) { + if(config.experimental.debugMode) + logInfo("Migrating data for " + playername); + File file = new File(this.playerDataDir, PlayerEntity.getOfflinePlayerUuid(player.getGameProfile().getName()) + ".dat"); + if (file.exists() && file.isFile()) + try { + compoundTag = NbtIo.readCompressed(new FileInputStream(file)); + } + catch (IOException e) { + LOGGER.warn("Failed to load player data for {}", playername); + } + } + else if(config.experimental.debugMode) + logInfo("Not migrating " + + playername + + ", as premium status is: " + + mojangAccountNamesCache.contains(playername) + + " and data file is " + (this.fileExists ? "" : "not") + + " present." + ); + return compoundTag; + } +} diff --git a/src/main/java/org/samo_lego/simpleauth/mixin/ServerStatHandlerAccessor.java b/src/main/java/org/samo_lego/simpleauth/mixin/ServerStatHandlerAccessor.java new file mode 100644 index 0000000..f329057 --- /dev/null +++ b/src/main/java/org/samo_lego/simpleauth/mixin/ServerStatHandlerAccessor.java @@ -0,0 +1,17 @@ +package org.samo_lego.simpleauth.mixin; + +import net.minecraft.stat.ServerStatHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.io.File; + +@Mixin(ServerStatHandler.class) +public interface ServerStatHandlerAccessor { + + @Accessor("file") + File getFile(); + + @Accessor("file") + void setFile(File file); +} diff --git a/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java b/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java index 31d236f..ed73a5e 100644 --- a/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java +++ b/src/main/java/org/samo_lego/simpleauth/storage/AuthConfig.java @@ -23,7 +23,9 @@ import com.google.gson.GsonBuilder; import java.io.*; import java.nio.charset.StandardCharsets; +import static org.samo_lego.simpleauth.SimpleAuth.serverProp; import static org.samo_lego.simpleauth.utils.SimpleLogger.logError; +import static org.samo_lego.simpleauth.utils.SimpleLogger.logInfo; public class AuthConfig { private static final Gson gson = new GsonBuilder() @@ -80,11 +82,6 @@ public class AuthConfig { */ public int sessionTimeoutTime = 60; - /** - * Should deauthenticated players fall if the login mid-air? - */ - public boolean allowFalling = false; - /** * Whether to tp player to spawn when joining (to hide original player coordinates). */ @@ -145,7 +142,7 @@ public class AuthConfig { public String timeExpired = "§cTime for authentication has expired."; public String registerRequired = "§6Type /register to claim this account."; public String alreadyRegistered = "§6This account name is already registered!"; - public String registerSuccess = "§aYou are now authenticated."; + public String registerSuccess = "§aAccount was created."; public String userdataDeleted = "§aUserdata deleted."; public String userdataUpdated = "§aUserdata updated."; public String accountDeleted = "§aYour account was successfully deleted!"; @@ -157,6 +154,7 @@ public class AuthConfig { public String worldSpawnSet = "§aSpawn for logging in was set successfully."; public String corruptedPlayerData = "§cYour data is probably corrupted. Please contact admin."; public String userNotRegistered = "§cThis player is not registered!"; + public String cannotLogout = "§cYou cannot logout!"; } public static class ExperimentalConfig { /** @@ -212,6 +210,27 @@ public class AuthConfig { * @see wiki */ public boolean useBCryptLibrary = false; + /** + * Whether players who have a valid session should skip the authentication process. + * You have to set online-mode to true in server.properties! + * (cracked players will still be able to enter, but they'll need to login) + * + * This protects premium usernames from being stolen, since cracked players + * with name that is found in Mojang database, are kicked. + */ + public boolean premiumAutologin = false; + /** + * Whether to modify player uuids to offline style. + * Note: this should be used only if you had your server + * running in offline mode and you made the switch to use + * AuthConfig#premiumAutoLogin AND your players already + * have e.g. villager discounts, which are based on uuid. + * Other things (advancements, playerdata) are migrated + * automatically, so think before enabling this. In case + * an online-mode player changes username, they'll loose all + * their stuff, unless you migrate it manually. + */ + public boolean forceoOfflineUuids = false; } public MainConfig main = new MainConfig(); @@ -234,6 +253,16 @@ public class AuthConfig { new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8) )) { config = gson.fromJson(fileReader, AuthConfig.class); + if(!Boolean.parseBoolean(serverProp.getProperty("online-mode"))) { + if(config.experimental.forceoOfflineUuids) { + logInfo("Server is in offline mode, forceoOfflineUuids option is irrelevant. Setting it to false."); + config.experimental.forceoOfflineUuids = false; + } + if(config.experimental.premiumAutologin) { + logError("You cannot use server in offline mode and premiumAutologin! Disabling the latter."); + config.experimental.premiumAutologin = false; + } + } } catch (IOException e) { throw new RuntimeException("[SimpleAuth] Problem occurred when trying to load config: ", e); } diff --git a/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java b/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java index ee5edd9..ec2613a 100644 --- a/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java +++ b/src/main/java/org/samo_lego/simpleauth/storage/PlayerCache.java @@ -1,11 +1,14 @@ package org.samo_lego.simpleauth.storage; -import com.google.gson.Gson; -import com.google.gson.JsonObject; +import com.google.gson.*; import net.minecraft.block.Blocks; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Identifier; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; import java.util.Objects; @@ -46,8 +49,6 @@ public class PlayerCache { /** * Player stats before de-authentication. */ - public int lastAir; - public boolean wasOnFire; public boolean wasInPortal; /** @@ -60,7 +61,7 @@ public class PlayerCache { public float pitch; } - public PlayerCache.LastLocation lastLocation = new PlayerCache.LastLocation(); + public final PlayerCache.LastLocation lastLocation = new PlayerCache.LastLocation(); private static final Gson gson = new Gson(); @@ -79,10 +80,6 @@ public class PlayerCache { if(player != null) { this.lastIp = player.getIp(); - this.wasOnFire = player.isOnFire(); - this.wasInPortal = player.getBlockState().getBlock().equals(Blocks.NETHER_PORTAL); - this.lastAir = player.getAir(); - // Setting position cache this.lastLocation.dimension = player.getServerWorld(); this.lastLocation.position = player.getPos(); @@ -92,9 +89,7 @@ public class PlayerCache { this.wasInPortal = player.getBlockState().getBlock().equals(Blocks.NETHER_PORTAL); } else { - this.wasOnFire = false; this.wasInPortal = false; - this.lastAir = 300; } this.isRegistered = false; diff --git a/src/main/java/org/samo_lego/simpleauth/storage/database/LevelDB.java b/src/main/java/org/samo_lego/simpleauth/storage/database/LevelDB.java index cc4befc..30c5388 100644 --- a/src/main/java/org/samo_lego/simpleauth/storage/database/LevelDB.java +++ b/src/main/java/org/samo_lego/simpleauth/storage/database/LevelDB.java @@ -63,7 +63,8 @@ public class LevelDB { * @param data data to put inside database * @return true if operation was successful, otherwise false */ - public static boolean registerUser(String uuid, String data) { + @Deprecated + public boolean registerUser(String uuid, String data) { try { if(!isUserRegistered(uuid)) { levelDBStore.put(bytes("UUID:" + uuid), bytes("data:" + data)); @@ -110,7 +111,8 @@ public class LevelDB { * @param uuid uuid of the player to update data for * @param data data to put inside database */ - public static void updateUserData(String uuid, String data) { + @Deprecated + public void updateUserData(String uuid, String data) { try { levelDBStore.put(bytes("UUID:" + uuid), bytes("data:" + data)); } catch (Error e) { diff --git a/src/main/java/org/samo_lego/simpleauth/utils/AuthHelper.java b/src/main/java/org/samo_lego/simpleauth/utils/AuthHelper.java index cd39534..1eaa013 100644 --- a/src/main/java/org/samo_lego/simpleauth/utils/AuthHelper.java +++ b/src/main/java/org/samo_lego/simpleauth/utils/AuthHelper.java @@ -1,10 +1,5 @@ package org.samo_lego.simpleauth.utils; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import org.samo_lego.simpleauth.SimpleAuth; import org.samo_lego.simpleauth.utils.hashing.HasherArgon2; import org.samo_lego.simpleauth.utils.hashing.HasherBCrypt; @@ -12,9 +7,6 @@ import static org.samo_lego.simpleauth.SimpleAuth.config; import static org.samo_lego.simpleauth.SimpleAuth.playerCacheMap; public class AuthHelper { - // Json parser - private static final JsonParser parser = new JsonParser(); - /** * Checks password of user * diff --git a/src/main/java/org/samo_lego/simpleauth/utils/PlayerAuth.java b/src/main/java/org/samo_lego/simpleauth/utils/PlayerAuth.java index 6aebecc..35abc37 100644 --- a/src/main/java/org/samo_lego/simpleauth/utils/PlayerAuth.java +++ b/src/main/java/org/samo_lego/simpleauth/utils/PlayerAuth.java @@ -58,4 +58,10 @@ public interface PlayerAuth { * @see See implementation */ boolean canSkipAuth(); + + /** + * Whether the player is using the mojang account + * @return true if paid, false if cracked + */ + boolean isUsingMojangAccount(); } diff --git a/src/main/resources/mixins.simpleauth.json b/src/main/resources/mixins.simpleauth.json index 4da9d2d..0713648 100644 --- a/src/main/resources/mixins.simpleauth.json +++ b/src/main/resources/mixins.simpleauth.json @@ -4,10 +4,15 @@ "compatibilityLevel": "JAVA_8", "mixins": [], "server": [ + "MixinPlayerAdvancementTracker", "MixinPlayerEntity", "MixinPlayerManager", + "MixinServerLoginNetworkHandler", + "MixinServerPlayerEntity", "MixinServerPlayNetworkHandler", - "MixinSlot" + "MixinSlot", + "MixinWorldSaveHandler", + "ServerStatHandlerAccessor" ], "injectors": { "defaultRequire": 1