Implementing #23
This commit is contained in:
parent
e92fc4c9a8
commit
82537de884
|
@ -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) -> {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"server": [
|
"server": [
|
||||||
"MixinPlayerEntity",
|
"MixinPlayerEntity",
|
||||||
"MixinPlayerManager",
|
"MixinPlayerManager",
|
||||||
|
"MixinServerLoginNetworkHandler",
|
||||||
"MixinServerPlayNetworkHandler",
|
"MixinServerPlayNetworkHandler",
|
||||||
"MixinSlot"
|
"MixinSlot"
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue