Support Microsoft authentication from debug mode
parent
b980606088
commit
db3c6d2848
|
@ -50,6 +50,7 @@ dependencies {
|
||||||
implementation("io.github.spair:imgui-java-natives-windows-ft:$imguiVersion")
|
implementation("io.github.spair:imgui-java-natives-windows-ft:$imguiVersion")
|
||||||
|
|
||||||
implementation(files("vendor/libs/lwjgl-util.jar"))
|
implementation(files("vendor/libs/lwjgl-util.jar"))
|
||||||
|
implementation(files("vendor/libs/minecraft_authenticator-2.0.1.jar"))
|
||||||
|
|
||||||
runtimeOnly("org.joml:joml:1.10.2")
|
runtimeOnly("org.joml:joml:1.10.2")
|
||||||
runtimeOnly("org.anarres:jcpp:1.4.14")
|
runtimeOnly("org.anarres:jcpp:1.4.14")
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
package site.hackery.unknit;
|
|
||||||
|
|
||||||
|
|
||||||
import com.mojang.authlib.Agent;
|
|
||||||
import com.mojang.authlib.GameProfile;
|
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
|
||||||
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
|
||||||
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
|
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
|
||||||
import net.minecraft.client.main.Main;
|
|
||||||
import net.minecraft.client.util.Session;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Mixin(Main.class)
|
|
||||||
public abstract class MixinMinecraftMain {
|
|
||||||
private static void replace(List<String> arguments, String tag, String value) {
|
|
||||||
int index = arguments.indexOf(tag);
|
|
||||||
if (index != -1) {
|
|
||||||
arguments.set(index + 1, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void replaceVersion(List<String> arguments) {
|
|
||||||
replace(arguments, "--version", "1.18.1");
|
|
||||||
replace(arguments, "--versionType", "release");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void replaceSession(List<String> arguments, Session session) {
|
|
||||||
replace(arguments, "--accessToken", session.getAccessToken());
|
|
||||||
|
|
||||||
arguments.add("--username");
|
|
||||||
arguments.add(session.getUsername());
|
|
||||||
|
|
||||||
arguments.add("--uuid");
|
|
||||||
arguments.add(session.getUuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ModifyVariable(method = "main", at = @At("HEAD"), argsOnly = true, remap = false)
|
|
||||||
private static String[] setArgs(String[] args) {
|
|
||||||
List<String> arguments = new ArrayList<>(Arrays.asList(args));
|
|
||||||
replaceVersion(arguments);
|
|
||||||
|
|
||||||
try {
|
|
||||||
String authFile = System.getenv("LOGIN_FILE");
|
|
||||||
authFile = authFile == null ? "lastlogin.txt" : authFile;
|
|
||||||
|
|
||||||
List<String> loginDetails = Files.readAllLines(Paths.get(authFile));
|
|
||||||
if (loginDetails.size() >= 2) {
|
|
||||||
Session session = getAuthenticatedSession(loginDetails.get(0), loginDetails.get(1));
|
|
||||||
replaceSession(arguments, session);
|
|
||||||
}
|
|
||||||
} catch (AuthenticationException | IOException ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return arguments.toArray(new String[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Session getAuthenticatedSession(String username, String password) throws AuthenticationException {
|
|
||||||
YggdrasilUserAuthentication yggdrasilUserAuthentication = constructUserAuthentication(username, password);
|
|
||||||
yggdrasilUserAuthentication.logIn();
|
|
||||||
|
|
||||||
GameProfile selectedProfile = yggdrasilUserAuthentication.getSelectedProfile();
|
|
||||||
|
|
||||||
return new Session(
|
|
||||||
selectedProfile.getName(),
|
|
||||||
UUIDTypeAdapter.fromUUID(selectedProfile.getId()),
|
|
||||||
yggdrasilUserAuthentication.getAuthenticatedToken(),
|
|
||||||
Optional.empty(),
|
|
||||||
Optional.empty(),
|
|
||||||
Session.AccountType.LEGACY // Why legacy?
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static YggdrasilUserAuthentication constructUserAuthentication(String username, String password) {
|
|
||||||
YggdrasilUserAuthentication yggdrasilUserAuthentication = (YggdrasilUserAuthentication) new YggdrasilAuthenticationService(Proxy.NO_PROXY, "my epic client token").createUserAuthentication(Agent.MINECRAFT);
|
|
||||||
yggdrasilUserAuthentication.setUsername(username);
|
|
||||||
yggdrasilUserAuthentication.setPassword(password);
|
|
||||||
|
|
||||||
return yggdrasilUserAuthentication;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
package site.hackery.unknit.auth;
|
||||||
|
|
||||||
|
import net.hycrafthd.minecraft_authenticator.login.AuthenticationException;
|
||||||
|
import net.hycrafthd.minecraft_authenticator.login.Authenticator;
|
||||||
|
import net.hycrafthd.minecraft_authenticator.login.User;
|
||||||
|
import net.hycrafthd.minecraft_authenticator.login.file.AuthenticationFile;
|
||||||
|
import net.minecraft.client.util.Session;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class GameAuthenticationFlow {
|
||||||
|
/**
|
||||||
|
* Creates an authenticated Minecraft session from given environment variables.
|
||||||
|
* AUTH_TYPE may be "mojang", "microsoft", or "refresh" (default).
|
||||||
|
* Under "mojang" authentication, you must supply the "AUTH_USERNAME" and "AUTH_PASSWORD" variables.
|
||||||
|
* Under "microsoft" authentication, you must supply the "AUTH_TOKEN" variable.
|
||||||
|
*
|
||||||
|
* @return a valid Session, or null
|
||||||
|
*/
|
||||||
|
public static Session createSessionFromEnvironment() {
|
||||||
|
try {
|
||||||
|
Authenticator.Builder readyAuth = createAuthenticator();
|
||||||
|
if (readyAuth != null) {
|
||||||
|
Authenticator authenticator = readyAuth.run();
|
||||||
|
Optional<User> userOpt = authenticator.getUser();
|
||||||
|
if (userOpt.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
saveAuthData(authenticator.getResultFile());
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
return new Session(
|
||||||
|
user.getName(),
|
||||||
|
user.getUuid(),
|
||||||
|
user.getAccessToken(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Session.AccountType.byName(user.getType())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (AuthenticationException | IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path getAuthDataFile() {
|
||||||
|
String authFilePath = System.getenv("AUTH_DATA_FILE");
|
||||||
|
authFilePath = authFilePath == null ? "mc_auth.dat" : authFilePath;
|
||||||
|
return Paths.get(authFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Authenticator.Builder createAuthenticator() throws IOException {
|
||||||
|
String authType = System.getenv("AUTH_TYPE");
|
||||||
|
authType = authType == null ? "refresh" : authType;
|
||||||
|
|
||||||
|
switch (authType) {
|
||||||
|
case "mojang":
|
||||||
|
return createMojangAuthenticator();
|
||||||
|
case "microsoft":
|
||||||
|
return createMicrosoftAuthenticator();
|
||||||
|
case "refresh":
|
||||||
|
return createRefreshAuthenticator();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Authenticator.Builder createMojangAuthenticator() {
|
||||||
|
String username = System.getenv("AUTH_USERNAME");
|
||||||
|
String password = System.getenv("AUTH_PASSWORD");
|
||||||
|
|
||||||
|
if (username == null || password == null) {
|
||||||
|
throw new RuntimeException("Attempted to perform Mojang authentication without AUTH_USERNAME or AUTH_PASSWORD set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Authenticator.ofYggdrasil("-", username, password).shouldAuthenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Authenticator.Builder createMicrosoftAuthenticator() {
|
||||||
|
String token = System.getenv("AUTH_TOKEN");
|
||||||
|
if (token == null) {
|
||||||
|
throw new RuntimeException("Attempted to perform Microsoft authentication without AUTH_TOKEN set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Authenticator.ofMicrosoft(token).shouldAuthenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Authenticator.Builder createRefreshAuthenticator() throws IOException {
|
||||||
|
Path authData = getAuthDataFile();
|
||||||
|
if (!Files.exists(authData)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream inputStream = Files.newInputStream(authData)) {
|
||||||
|
AuthenticationFile file = AuthenticationFile.read(inputStream);
|
||||||
|
return Authenticator.of(file).shouldAuthenticate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void saveAuthData(AuthenticationFile file) throws IOException {
|
||||||
|
try (OutputStream stream = Files.newOutputStream(getAuthDataFile())) {
|
||||||
|
file.write(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package site.hackery.unknit;
|
package site.hackery.unknit.mixin;
|
||||||
|
|
||||||
import net.minecraft.client.*;
|
import net.minecraft.client.ClientBrandRetriever;
|
||||||
import org.spongepowered.asm.mixin.*;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.*;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.*;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
@Mixin(value = ClientBrandRetriever.class, remap = false)
|
@Mixin(value = ClientBrandRetriever.class, remap = false)
|
||||||
public abstract class MixinClientBrandRetriever {
|
public abstract class MixinClientBrandRetriever {
|
|
@ -1,4 +1,4 @@
|
||||||
package site.hackery.unknit;
|
package site.hackery.unknit.mixin;
|
||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.util.ModStatus;
|
import net.minecraft.util.ModStatus;
|
|
@ -0,0 +1,51 @@
|
||||||
|
package site.hackery.unknit.mixin;
|
||||||
|
|
||||||
|
|
||||||
|
import net.minecraft.client.main.Main;
|
||||||
|
import net.minecraft.client.util.Session;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
import site.hackery.unknit.auth.GameAuthenticationFlow;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(Main.class)
|
||||||
|
public abstract class MixinMinecraftMain {
|
||||||
|
private static void replace(List<String> arguments, String tag, String value) {
|
||||||
|
int index = arguments.indexOf(tag);
|
||||||
|
if (index != -1) {
|
||||||
|
arguments.set(index + 1, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void replaceVersion(List<String> arguments) {
|
||||||
|
replace(arguments, "--version", "1.18.1");
|
||||||
|
replace(arguments, "--versionType", "release");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void replaceSession(List<String> arguments, Session session) {
|
||||||
|
replace(arguments, "--accessToken", session.getAccessToken());
|
||||||
|
|
||||||
|
arguments.add("--username");
|
||||||
|
arguments.add(session.getUsername());
|
||||||
|
|
||||||
|
arguments.add("--uuid");
|
||||||
|
arguments.add(session.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyVariable(method = "main", at = @At("HEAD"), argsOnly = true, remap = false)
|
||||||
|
private static String[] setArgs(String[] args) {
|
||||||
|
List<String> arguments = new ArrayList<>(Arrays.asList(args));
|
||||||
|
replaceVersion(arguments);
|
||||||
|
|
||||||
|
Session session = GameAuthenticationFlow.createSessionFromEnvironment();
|
||||||
|
if (session != null) {
|
||||||
|
replaceSession(arguments, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arguments.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"required": true,
|
"required": true,
|
||||||
"package": "site.hackery.unknit",
|
"package": "site.hackery.unknit.mixin",
|
||||||
"compatibilityLevel": "JAVA_17",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|
Loading…
Reference in New Issue