Implementing #23

This commit is contained in:
samo_lego 2020-10-21 20:47:04 +02:00
parent e92fc4c9a8
commit 82537de884
6 changed files with 164 additions and 12 deletions

View File

@ -22,6 +22,7 @@ import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -46,6 +47,14 @@ public class SimpleAuth implements DedicatedServerModInitializer {
*/ */
public static HashMap<String, PlayerCache> playerCacheMap = new HashMap<>(); public static HashMap<String, PlayerCache> 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 HashSet<String> mojangAccountNamesCache = new HashSet<>();
public static HashSet<String> accountStatusCache = new HashSet<>();
// Getting game directory // Getting game directory
public static final Path gameDirectory = FabricLoader.getInstance().getGameDir(); public static final Path gameDirectory = FabricLoader.getInstance().getGameDir();
@ -62,6 +71,12 @@ public class SimpleAuth implements DedicatedServerModInitializer {
// The support on discord was great! I really appreciate your help. // The support on discord was great! I really appreciate your help.
logInfo("This mod wouldn't exist without the awesome Fabric Community. TYSM guys!"); 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) // Creating data directory (database and config files are stored there)
File file = new File(gameDirectory + "/mods/SimpleAuth/leveldbStore"); File file = new File(gameDirectory + "/mods/SimpleAuth/leveldbStore");
if (!file.exists() && !file.mkdirs()) if (!file.exists() && !file.mkdirs())
@ -71,12 +86,6 @@ public class SimpleAuth implements DedicatedServerModInitializer {
// Connecting to db // Connecting to db
DB.openConnection(); DB.openConnection();
try {
serverProp.load(new FileReader(gameDirectory + "/server.properties"));
} catch (IOException e) {
logError("Error while reading server properties: " + e.getMessage());
}
// Registering the commands // Registering the commands
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {

View File

@ -1,5 +1,6 @@
package org.samo_lego.simpleauth.mixin; package org.samo_lego.simpleauth.mixin;
import com.mojang.authlib.GameProfile;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
@ -17,7 +18,9 @@ import org.samo_lego.simpleauth.SimpleAuth;
import org.samo_lego.simpleauth.event.item.DropItemCallback; import org.samo_lego.simpleauth.event.item.DropItemCallback;
import org.samo_lego.simpleauth.storage.PlayerCache; import org.samo_lego.simpleauth.storage.PlayerCache;
import org.samo_lego.simpleauth.utils.PlayerAuth; import org.samo_lego.simpleauth.utils.PlayerAuth;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; 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.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@ -26,10 +29,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static org.samo_lego.simpleauth.SimpleAuth.config; import static org.samo_lego.simpleauth.SimpleAuth.config;
import static org.samo_lego.simpleauth.SimpleAuth.playerCacheMap; import static org.samo_lego.simpleauth.SimpleAuth.playerCacheMap;
import static org.samo_lego.simpleauth.utils.CarpetHelper.isPlayerCarpetFake; import static org.samo_lego.simpleauth.utils.CarpetHelper.isPlayerCarpetFake;
import static org.samo_lego.simpleauth.utils.SimpleLogger.logInfo;
@Mixin(PlayerEntity.class) @Mixin(PlayerEntity.class)
public abstract class MixinPlayerEntity implements PlayerAuth { public abstract class MixinPlayerEntity implements PlayerAuth {
@Shadow @Final private GameProfile gameProfile;
private final ServerPlayerEntity player = (ServerPlayerEntity) (Object) this; private final ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
// * 20 for 20 ticks in second // * 20 for 20 ticks in second
@ -50,6 +55,8 @@ public abstract class MixinPlayerEntity implements PlayerAuth {
assert server != null; assert server != null;
PlayerCache cache = playerCacheMap.get(this.getFakeUuid()); PlayerCache cache = playerCacheMap.get(this.getFakeUuid());
if(config.main.spawnOnJoin)
logInfo("Teleporting " + player.getName().asString() + (hide ? " to spawn." : " to original position."));
if (hide) { if (hide) {
// Saving position // Saving position
cache.lastLocation.dimension = player.getServerWorld(); cache.lastLocation.dimension = player.getServerWorld();
@ -90,14 +97,15 @@ public abstract class MixinPlayerEntity implements PlayerAuth {
public String getFakeUuid() { public String getFakeUuid() {
// If server is in online mode online-mode UUIDs should be used // If server is in online mode online-mode UUIDs should be used
assert server != null; assert server != null;
if(server.isOnlineMode()) if(server.isOnlineMode() && this.isUsingMojangAccount())
return player.getUuidAsString(); return player.getUuidAsString();
/* /*
Lower case is used for Player and PlAyEr to get same UUID (for password storing) 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 Mimicking Mojang behaviour, where players cannot set their name to
ExAmple if Example is already taken. ExAmple if Example is already taken.
*/ */
String playername = player.getName().asString().toLowerCase(); // 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(); return PlayerEntity.getOfflinePlayerUuid(playername).toString();
} }
@ -172,14 +180,23 @@ public abstract class MixinPlayerEntity implements PlayerAuth {
} }
/** /**
* Checks whether player is a fake player (from CarpetMod). * Checks whether player can skip authentication process.
* *
* @return true if player is fake (can skip authentication process), otherwise false * @return true if can skip authentication process, otherwise false
*/ */
@Override @Override
public boolean canSkipAuth() { public boolean canSkipAuth() {
// We ask CarpetHelper class since it has the imports needed // We ask CarpetHelper class since it has the imports needed
return this.isRunningCarpet && isPlayerCarpetFake(this.player); 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 !gameProfile.getId().equals(PlayerEntity.getOfflinePlayerUuid(gameProfile.getName()));
} }
/** /**
@ -190,7 +207,7 @@ public abstract class MixinPlayerEntity implements PlayerAuth {
@Override @Override
public boolean isAuthenticated() { public boolean isAuthenticated() {
String uuid = ((PlayerAuth) player).getFakeUuid(); String uuid = ((PlayerAuth) player).getFakeUuid();
return playerCacheMap.containsKey(uuid) && playerCacheMap.get(uuid).isAuthenticated; return this.canSkipAuth() || (playerCacheMap.containsKey(uuid) && playerCacheMap.get(uuid).isAuthenticated);
} }
@Inject(method = "tick()V", at = @At("HEAD"), cancellable = true) @Inject(method = "tick()V", at = @At("HEAD"), cancellable = true)

View File

@ -0,0 +1,108 @@
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.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 static org.samo_lego.simpleauth.SimpleAuth.*;
import static org.samo_lego.simpleauth.utils.SimpleLogger.logError;
@Mixin(ServerLoginNetworkHandler.class)
public class MixinServerLoginNetworkHandler {
@Shadow
private GameProfile profile;
@Shadow
private int loginTicks;
/**
* Fake state of current player.
*/
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();
}
}
/**
* 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();
if(playerCacheMap.containsKey(PlayerEntity.getOfflinePlayerUuid(playername).toString())) {
// 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());
}
}
}
}

View File

@ -23,6 +23,7 @@ import com.google.gson.GsonBuilder;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; 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.logError;
public class AuthConfig { public class AuthConfig {
@ -191,6 +192,12 @@ public class AuthConfig {
* @see <a href="https://github.com/samolego/SimpleAuth/wiki/GLIBC-problems" target="_blank">wiki</a> * @see <a href="https://github.com/samolego/SimpleAuth/wiki/GLIBC-problems" target="_blank">wiki</a>
*/ */
public boolean useBCryptLibrary = false; 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)
*/
public boolean premiumAutologin = false;
} }
public MainConfig main = new MainConfig(); public MainConfig main = new MainConfig();
@ -212,6 +219,10 @@ public class AuthConfig {
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8) new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)
)) { )) {
config = gson.fromJson(fileReader, AuthConfig.class); config = gson.fromJson(fileReader, AuthConfig.class);
if(config.experimental.premiumAutologin && !Boolean.parseBoolean(serverProp.getProperty("online-mode"))) {
logError("You cannot use server in offline mode and premiumAutologin! Disabling the latter.");
config.experimental.premiumAutologin = false;
}
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("[SimpleAuth] Problem occurred when trying to load config: ", e); throw new RuntimeException("[SimpleAuth] Problem occurred when trying to load config: ", e);
} }

View File

@ -58,4 +58,10 @@ public interface PlayerAuth {
* @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a> * @see <a href="https://samolego.github.io/SimpleAuth/org/samo_lego/simpleauth/mixin/MixinPlayerEntity.html">See implementation</a>
*/ */
boolean canSkipAuth(); boolean canSkipAuth();
/**
* Whether the player is using the mojang account
* @return true if paid, false if cracked
*/
boolean isUsingMojangAccount();
} }

View File

@ -6,6 +6,7 @@
"server": [ "server": [
"MixinPlayerEntity", "MixinPlayerEntity",
"MixinPlayerManager", "MixinPlayerManager",
"MixinServerLoginNetworkHandler",
"MixinServerPlayNetworkHandler", "MixinServerPlayNetworkHandler",
"MixinSlot" "MixinSlot"
], ],