[Chore] auth 有关的部分类转换为记录类 (#6268)

This commit is contained in:
ToobLac
2026-06-30 02:08:22 +08:00
committed by GitHub
parent c0f699b90d
commit d8938e1fcb
17 changed files with 95 additions and 286 deletions
@@ -87,7 +87,7 @@ public final class TexturesLoader {
private static final Path TEXTURES_DIR = Metadata.HMCL_USER_HOME.resolve("skins");
private static Path getTexturePath(Texture texture) {
String url = texture.getUrl();
String url = texture.url();
int slash = url.lastIndexOf('/');
int dot = url.lastIndexOf('.');
if (dot < slash) {
@@ -99,7 +99,7 @@ public final class TexturesLoader {
}
public static LoadedTexture loadTexture(Texture texture) throws Throwable {
if (StringUtils.isBlank(texture.getUrl())) {
if (StringUtils.isBlank(texture.url())) {
throw new IOException("Texture url is empty");
}
@@ -107,14 +107,14 @@ public final class TexturesLoader {
if (!Files.isRegularFile(file)) {
// download it
try {
new FileDownloadTask(texture.getUrl(), file).run();
LOG.info("Texture downloaded: " + texture.getUrl());
new FileDownloadTask(texture.url(), file).run();
LOG.info("Texture downloaded: " + texture.url());
} catch (Exception e) {
if (Files.isRegularFile(file)) {
// concurrency conflict?
LOG.warning("Failed to download texture " + texture.getUrl() + ", but the file is available", e);
LOG.warning("Failed to download texture " + texture.url() + ", but the file is available", e);
} else {
throw new IOException("Failed to download texture " + texture.getUrl());
throw new IOException("Failed to download texture " + texture.url());
}
}
}
@@ -127,7 +127,7 @@ public final class TexturesLoader {
if (img.isError())
throw img.getException();
Map<String, String> metadata = texture.getMetadata();
Map<String, String> metadata = texture.metadata();
if (metadata == null) {
metadata = emptyMap();
}
@@ -176,7 +176,7 @@ public final class TexturesLoader {
}
})
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))
.filter(it -> StringUtils.isNotBlank(it.getUrl())))
.filter(it -> StringUtils.isNotBlank(it.url())))
.asyncMap(it -> {
if (it.isPresent()) {
Texture texture = it.get();
@@ -184,7 +184,7 @@ public final class TexturesLoader {
try {
return loadTexture(texture);
} catch (Throwable e) {
LOG.warning("Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
LOG.warning("Failed to load texture " + texture.url() + ", using fallback texture", e);
return uuidFallback;
}
}, POOL);
@@ -209,15 +209,15 @@ public final class TexturesLoader {
skin.load(profileName).setExecutor(POOL).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception != null) {
LOG.warning("Failed to load texture", exception);
} else if (result != null && result.getSkin() != null && result.getSkin().getImage() != null) {
} else if (result != null && result.skin() != null && result.skin().image() != null) {
Map<String, String> metadata;
if (result.getModel() != null) {
metadata = singletonMap("model", result.getModel().modelName);
if (result.model() != null) {
metadata = singletonMap("model", result.model().modelName);
} else {
metadata = emptyMap();
}
binding.set(new LoadedTexture(result.getSkin().getImage(), metadata));
binding.set(new LoadedTexture(result.skin().image(), metadata));
}
}).start();
}
@@ -234,12 +234,12 @@ public final class TexturesLoader {
.asyncMap(textures -> {
if (textures.isPresent()) {
Texture texture = textures.get().get(TextureType.SKIN);
if (texture != null && StringUtils.isNotBlank(texture.getUrl())) {
if (texture != null && StringUtils.isNotBlank(texture.url())) {
return CompletableFuture.supplyAsync(() -> {
try {
return loadTexture(texture);
} catch (Throwable e) {
LOG.warning("Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
LOG.warning("Failed to load texture " + texture.url() + ", using fallback texture", e);
return uuidFallback;
}
}, POOL);
@@ -130,11 +130,11 @@ public class OfflineAccountSkinPane extends StackPane {
skinItem.setSelectedData(Skin.Type.DEFAULT);
modelCombobox.setValue(TextureModel.WIDE);
} else {
skinItem.setSelectedData(account.getSkin().getType());
cslApiField.setText(account.getSkin().getCslApi());
modelCombobox.setValue(account.getSkin().getTextureModel());
skinSelector.setValue(account.getSkin().getLocalSkinPath());
capeSelector.setValue(account.getSkin().getLocalCapePath());
skinItem.setSelectedData(account.getSkin().type());
cslApiField.setText(account.getSkin().cslApi());
modelCombobox.setValue(account.getSkin().textureModel());
skinSelector.setValue(account.getSkin().localSkinPath());
capeSelector.setValue(account.getSkin().localCapePath());
}
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(1));
@@ -147,7 +147,7 @@ public class OfflineAccountSkinPane extends StackPane {
Controllers.showToast(i18n("message.failed"));
} else {
UUID uuid = this.account.getProfileID();
if (result == null || result.getSkin() == null && result.getCape() == null) {
if (result == null || result.skin() == null && result.cape() == null) {
canvas.updateSkin(
TexturesLoader.getDefaultSkin(uuid).getImage(),
TexturesLoader.getDefaultModel(uuid) == TextureModel.SLIM,
@@ -156,9 +156,9 @@ public class OfflineAccountSkinPane extends StackPane {
return;
}
canvas.updateSkin(
result.getSkin() != null ? result.getSkin().getImage() : TexturesLoader.getDefaultSkin(uuid).getImage(),
result.getModel() == TextureModel.SLIM,
result.getCape() != null ? result.getCape().getImage() : null);
result.skin() != null ? result.skin().image() : TexturesLoader.getDefaultSkin(uuid).getImage(),
result.model() == TextureModel.SLIM,
result.cape() != null ? result.cape().image() : null);
}
}).start();
};
@@ -288,22 +288,7 @@ public class OAuth {
DEVICE,
}
public static final class Result {
private final String accessToken;
private final String refreshToken;
public Result(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
public String getAccessToken() {
return accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public record Result(String accessToken, String refreshToken) {
}
private final static class DeviceTokenResponse extends ErrorResponse {
@@ -136,7 +136,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
@Override
public Arguments getLaunchArguments(LaunchOptions options) {
return new Arguments().addJVMArguments(
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
"-javaagent:" + artifact.location().toString() + "=" + server.getUrl(),
"-Dauthlibinjector.side=client",
"-Dauthlibinjector.yggdrasil.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));
}
@@ -17,13 +17,15 @@
*/
package org.jackhuang.hmcl.auth.authlibinjector;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
public class AuthlibInjectorArtifactInfo {
public record AuthlibInjectorArtifactInfo(int buildNumber, String version, Path location) {
public static AuthlibInjectorArtifactInfo from(Path location) throws IOException {
try (JarFile jarFile = new JarFile(location.toFile())) {
@@ -50,30 +52,8 @@ public class AuthlibInjectorArtifactInfo {
}
}
private int buildNumber;
private String version;
private Path location;
public AuthlibInjectorArtifactInfo(int buildNumber, String version, Path location) {
this.buildNumber = buildNumber;
this.version = version;
this.location = location;
}
public int getBuildNumber() {
return buildNumber;
}
public String getVersion() {
return version;
}
public Path getLocation() {
return location;
}
@Override
public String toString() {
public @NotNull String toString() {
return "authlib-injector [buildNumber=" + buildNumber + ", version=" + version + "]";
}
}
@@ -91,7 +91,7 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
AuthlibInjectorVersionInfo latest = getLatestArtifactInfo();
Optional<AuthlibInjectorArtifactInfo> local = getLocalArtifact();
if (local.isPresent() && local.get().getBuildNumber() >= latest.buildNumber) {
if (local.isPresent() && local.get().buildNumber() >= latest.buildNumber) {
return;
}
@@ -46,7 +46,7 @@ public final class MicrosoftAccount extends OAuthAccount {
super(accountID);
this.service = requireNonNull(service);
this.session = requireNonNull(session);
this.profileID = requireNonNull(session.getProfile().id());
this.profileID = requireNonNull(session.profile().id());
}
protected MicrosoftAccount(MicrosoftService service, OAuth.GrantFlow flow) throws AuthenticationException {
@@ -54,35 +54,35 @@ public final class MicrosoftAccount extends OAuthAccount {
this.service = requireNonNull(service);
MicrosoftSession acquiredSession = service.authenticate(flow);
if (acquiredSession.getProfile() == null) {
if (acquiredSession.profile() == null) {
session = service.refresh(acquiredSession);
} else {
session = acquiredSession;
}
profileID = session.getProfile().id();
profileID = session.profile().id();
authenticated = true;
}
@Override
public String getProfileName() {
return session.getProfile().name();
return session.profile().name();
}
@Override
public UUID getProfileID() {
return session.getProfile().id();
return session.profile().id();
}
@Override
public AuthInfo logIn() throws AuthenticationException {
if (!authenticated || !session.hasProfileName() || System.currentTimeMillis() > session.getNotAfter()) {
if (!authenticated || !session.hasProfileName() || System.currentTimeMillis() > session.notAfter()) {
if (session.hasProfileName()
&& service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
&& service.validate(session.notAfter(), session.tokenType(), session.accessToken())) {
authenticated = true;
} else {
MicrosoftSession acquiredSession = service.refresh(session);
if (!Objects.equals(acquiredSession.getProfile().id(), session.getProfile().id())) {
if (!Objects.equals(acquiredSession.profile().id(), session.profile().id())) {
throw new ServerResponseMalformedException("Selected profile changed");
}
if (!acquiredSession.hasProfileName()) {
@@ -102,11 +102,11 @@ public final class MicrosoftAccount extends OAuthAccount {
@Override
public AuthInfo logInWhenCredentialsExpired() throws AuthenticationException {
MicrosoftSession acquiredSession = service.authenticate(OAuth.GrantFlow.DEVICE);
if (!Objects.equals(profileID, acquiredSession.getProfile().id())) {
throw new WrongAccountException(profileID, acquiredSession.getProfile().id());
if (!Objects.equals(profileID, acquiredSession.profile().id())) {
throw new WrongAccountException(profileID, acquiredSession.profile().id());
}
if (acquiredSession.getProfile() == null) {
if (acquiredSession.profile() == null) {
session = service.refresh(acquiredSession);
} else {
session = acquiredSession;
@@ -133,7 +133,7 @@ public final class MicrosoftAccount extends OAuthAccount {
@Override
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
service.uploadSkin(session.getAccessToken(), isSlim, file);
service.uploadSkin(session.accessToken(), isSlim, file);
}
@Override
@@ -77,7 +77,7 @@ public class MicrosoftService {
public MicrosoftSession authenticate(OAuth.GrantFlow flow) throws AuthenticationException {
try {
OAuth.Result result = OAuth.MICROSOFT.authenticate(flow, new OAuth.Options(SCOPE, callback));
return authenticateViaLiveAccessToken(result.getAccessToken(), result.getRefreshToken());
return authenticateViaLiveAccessToken(result.accessToken(), result.refreshToken());
} catch (IOException e) {
throw new ServerDisconnectException(e);
} catch (JsonParseException e) {
@@ -87,8 +87,8 @@ public class MicrosoftService {
public MicrosoftSession refresh(MicrosoftSession oldSession) throws AuthenticationException {
try {
OAuth.Result result = OAuth.MICROSOFT.refresh(oldSession.getRefreshToken(), new OAuth.Options(SCOPE, callback));
return authenticateViaLiveAccessToken(result.getAccessToken(), result.getRefreshToken());
OAuth.Result result = OAuth.MICROSOFT.refresh(oldSession.refreshToken(), new OAuth.Options(SCOPE, callback));
return authenticateViaLiveAccessToken(result.accessToken(), result.refreshToken());
} catch (IOException e) {
throw new ServerDisconnectException(e);
} catch (JsonParseException e) {
@@ -30,13 +30,7 @@ import java.util.UUID;
import static java.util.Objects.requireNonNull;
public class MicrosoftSession {
private final String tokenType;
private final long notAfter;
private final String accessToken;
private final String refreshToken;
private final User user;
private final GameProfile profile;
public record MicrosoftSession(String tokenType, String accessToken, long notAfter, String refreshToken, User user, GameProfile profile) {
public MicrosoftSession(String tokenType, String accessToken, long notAfter, String refreshToken, User user, GameProfile profile) {
this.tokenType = tokenType;
@@ -49,32 +43,8 @@ public class MicrosoftSession {
if (accessToken != null) Logger.registerAccessToken(accessToken);
}
public String getTokenType() {
return tokenType;
}
public String getAccessToken() {
return accessToken;
}
public long getNotAfter() {
return notAfter;
}
public String getRefreshToken() {
return refreshToken;
}
public String getAuthorization() {
return String.format("%s %s", getTokenType(), getAccessToken());
}
public User getUser() {
return user;
}
public GameProfile getProfile() {
return profile;
return String.format("%s %s", tokenType(), accessToken());
}
/// Returns whether the stored session contains a usable Minecraft profile name.
@@ -97,7 +97,7 @@ public class OfflineAccount extends Account {
}
protected boolean loadAuthlibInjector(Skin skin) {
return skin != null && skin.getType() != Skin.Type.DEFAULT;
return skin != null && skin.type() != Skin.Type.DEFAULT;
}
public AuthInfo logInWithoutSkin() throws AuthenticationException {
@@ -169,7 +169,7 @@ public class OfflineAccount extends Account {
}
return new Arguments().addJVMArguments(
"-javaagent:" + artifact.getLocation().toString() + "=" + "http://localhost:" + server.getListeningPort(),
"-javaagent:" + artifact.location().toString() + "=" + "http://localhost:" + server.getListeningPort(),
"-Dauthlibinjector.side=client"
);
}
@@ -38,9 +38,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public class Skin {
public record Skin(Type type, String cslApi, TextureModel textureModel, String localSkinPath, String localCapePath) {
public enum Type {
DEFAULT,
@@ -59,75 +61,31 @@ public class Skin {
YGGDRASIL_API;
public static Type fromStorage(String type) {
switch (type) {
case "default":
return DEFAULT;
case "alex":
return ALEX;
case "ari":
return ARI;
case "efe":
return EFE;
case "kai":
return KAI;
case "makena":
return MAKENA;
case "noor":
return NOOR;
case "steve":
return STEVE;
case "sunny":
return SUNNY;
case "zuri":
return ZURI;
case "local_file":
return LOCAL_FILE;
case "little_skin":
return LITTLE_SKIN;
case "custom_skin_loader_api":
return CUSTOM_SKIN_LOADER_API;
case "yggdrasil_api":
return YGGDRASIL_API;
default:
return null;
}
return switch (type) {
case "default" -> DEFAULT;
case "alex" -> ALEX;
case "ari" -> ARI;
case "efe" -> EFE;
case "kai" -> KAI;
case "makena" -> MAKENA;
case "noor" -> NOOR;
case "steve" -> STEVE;
case "sunny" -> SUNNY;
case "zuri" -> ZURI;
case "local_file" -> LOCAL_FILE;
case "little_skin" -> LITTLE_SKIN;
case "custom_skin_loader_api" -> CUSTOM_SKIN_LOADER_API;
case "yggdrasil_api" -> YGGDRASIL_API;
default -> null;
};
}
}
private final Type type;
private final String cslApi;
private final TextureModel textureModel;
private final String localSkinPath;
private final String localCapePath;
public Skin(Type type, String cslApi, TextureModel textureModel, String localSkinPath, String localCapePath) {
this.type = type;
this.cslApi = cslApi;
this.textureModel = textureModel;
this.localSkinPath = localSkinPath;
this.localCapePath = localCapePath;
}
public Type getType() {
return type;
}
public String getCslApi() {
return cslApi;
}
public TextureModel getTextureModel() {
@Override
public TextureModel textureModel() {
return textureModel == null ? TextureModel.WIDE : textureModel;
}
public String getLocalSkinPath() {
return localSkinPath;
}
public String getLocalCapePath() {
return localCapePath;
}
public Task<LoadedSkin> load(String username) {
switch (type) {
case DEFAULT:
@@ -156,7 +114,7 @@ public class Skin {
Optional<Path> capePath = FileUtils.tryGetPath(localCapePath);
if (skinPath.isPresent()) skin = Texture.loadTexture(Files.newInputStream(skinPath.get()));
if (capePath.isPresent()) cape = Texture.loadTexture(Files.newInputStream(capePath.get()));
return new LoadedSkin(getTextureModel(), skin, cape);
return new LoadedSkin(textureModel(), skin, cape);
});
case LITTLE_SKIN:
case CUSTOM_SKIN_LOADER_API:
@@ -204,7 +162,7 @@ public class Skin {
public void writeStorage(JsonObject storage) {
storage.addProperty("type", type.name().toLowerCase(Locale.ROOT));
storage.addProperty("cslApi", cslApi);
storage.addProperty("textureModel", getTextureModel().modelName);
storage.addProperty("textureModel", textureModel().modelName);
storage.addProperty("localSkinPath", localSkinPath);
storage.addProperty("localCapePath", localCapePath);
}
@@ -270,46 +228,11 @@ public class Skin {
}
}
public static class LoadedSkin {
private final TextureModel model;
private final Texture skin;
private final Texture cape;
public LoadedSkin(TextureModel model, Texture skin, Texture cape) {
this.model = model;
this.skin = skin;
this.cape = cape;
}
public TextureModel getModel() {
return model;
}
public Texture getSkin() {
return skin;
}
public Texture getCape() {
return cape;
}
public record LoadedSkin(TextureModel model, Texture skin, Texture cape) {
}
private static class SkinJson {
private final String username;
private final String skin;
private final String cape;
private final String elytra;
@SerializedName(value = "textures", alternate = { "skins" })
private final TextureJson textures;
public SkinJson(String username, String skin, String cape, String elytra, TextureJson textures) {
this.username = username;
this.skin = skin;
this.cape = cape;
this.elytra = elytra;
this.textures = textures;
}
private record SkinJson(String username, String skin, String cape, String elytra,
@SerializedName(value = "textures", alternate = {"skins"}) TextureJson textures) {
public boolean hasSkin() {
return StringUtils.isNotBlank(username);
@@ -356,20 +279,7 @@ public class Skin {
} else return cape;
}
public static class TextureJson {
@SerializedName("default")
private final String defaultSkin;
private final String slim;
private final String cape;
private final String elytra;
public TextureJson(String defaultSkin, String slim, String cape, String elytra) {
this.defaultSkin = defaultSkin;
this.slim = slim;
this.cape = cape;
this.elytra = elytra;
}
public record TextureJson(@SerializedName("default") String defaultSkin, String slim, String cape, String elytra) {
}
}
}
@@ -30,23 +30,12 @@ import java.util.Map;
import static java.util.Objects.requireNonNull;
public final class Texture {
private final String hash;
private final Image image;
public record Texture(String hash, Image image) {
public Texture(String hash, Image image) {
this.hash = requireNonNull(hash);
this.image = requireNonNull(image);
}
public String getHash() {
return hash;
}
public Image getImage() {
return image;
}
private static final Map<String, Texture> textures = new HashMap<>();
public static boolean hasTexture(String hash) {
@@ -129,7 +129,7 @@ public class YggdrasilServer extends HttpServer {
if (Texture.hasTexture(hash)) {
Texture texture = Texture.getTexture(hash);
byte[] data = PNGJavaFXUtils.writeImageToArray(texture.getImage());
byte[] data = PNGJavaFXUtils.writeImageToArray(texture.image());
Response response = newFixedLengthResponse(Response.Status.OK, "image/png", new ByteArrayInputStream(data), data.length);
response.addHeader("Etag", String.format("\"%s\"", hash));
response.addHeader("Cache-Control", "max-age=2592000, public");
@@ -177,19 +177,19 @@ public class YggdrasilServer extends HttpServer {
public Object toCompleteResponse(String rootUrl) {
Map<String, Object> realTextures = new HashMap<>();
if (skin != null && skin.getSkin() != null) {
if (skin.getModel() == TextureModel.SLIM) {
if (skin != null && skin.skin() != null) {
if (skin.model() == TextureModel.SLIM) {
realTextures.put("SKIN", mapOf(
pair("url", rootUrl + "/textures/" + skin.getSkin().getHash()),
pair("url", rootUrl + "/textures/" + skin.skin().hash()),
pair("metadata", mapOf(
pair("model", "slim")
))));
} else {
realTextures.put("SKIN", mapOf(pair("url", rootUrl + "/textures/" + skin.getSkin().getHash())));
realTextures.put("SKIN", mapOf(pair("url", rootUrl + "/textures/" + skin.skin().hash())));
}
}
if (skin != null && skin.getCape() != null) {
realTextures.put("CAPE", mapOf(pair("url", rootUrl + "/textures/" + skin.getCape().getHash())));
if (skin != null && skin.cape() != null) {
realTextures.put("CAPE", mapOf(pair("url", rootUrl + "/textures/" + skin.cape().hash())));
}
Map<String, Object> textureResponse = mapOf(
@@ -23,27 +23,21 @@ import org.jetbrains.annotations.Nullable;
import java.util.Map;
@Immutable
public final class Texture {
private final String url;
private final Map<String, String> metadata;
public record Texture(String url, Map<String, String> metadata) {
public Texture() {
this(null, null);
}
public Texture(String url, Map<String, String> metadata) {
this.url = url;
this.metadata = metadata;
}
@Override
@Nullable
public String getUrl() {
public String url() {
return url;
}
@Override
@Nullable
public Map<String, String> getMetadata() {
public Map<String, String> metadata() {
return metadata;
}
}
@@ -32,32 +32,12 @@ import org.jetbrains.annotations.Nullable;
* @author huang
*/
@Immutable
public final class User implements Validation {
private final String id;
@Nullable
@JsonAdapter(PropertyMapSerializer.class)
private final Map<String, String> properties;
public record User(String id, @JsonAdapter(PropertyMapSerializer.class) @Nullable Map<String, String> properties) implements Validation {
public User(String id) {
this(id, null);
}
public User(String id, @Nullable Map<String, String> properties) {
this.id = id;
this.properties = properties;
}
public String getId() {
return id;
}
@Nullable
public Map<String, String> getProperties() {
return properties;
}
@Override
public void validate() throws JsonParseException {
if (StringUtils.isBlank(id))
@@ -211,7 +211,7 @@ public class YggdrasilService {
response.accessToken,
response.selectedProfile,
response.availableProfiles == null ? null : unmodifiableList(response.availableProfiles),
response.user == null ? null : response.user.getProperties());
response.user == null ? null : response.user.properties());
}
private static void requireEmpty(String response) throws AuthenticationException {
@@ -37,6 +37,7 @@ public class YggdrasilSession {
private final String accessToken;
private final GameProfile selectedProfile;
private final List<GameProfile> availableProfiles;
@Nullable
private final Map<String, String> userProperties;