Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.

Commit b7e6eb3

Browse files
authored
Add Skin & Cape Manager (#176)
Adds a skin & cape manager. Skins may be downloaded to the `skins` directory in the current game folder. Additionally, skin files may be imported to said folder by dragging files into the screen or placing them in there manually. Capes can not be added, they are supplied by Mojang. Notes: - This increases the dependency of the 1.8.9 module on legacy-lwjgl3 to >1.2.8 for file drop support. TODO: - [x] Improve drag'n'drop functionality. It should only copy valid image files. (Current: Any files) - [x] Add screen to "steal" skins (see `// TODO`) - [x] Update Changelog
2 parents d7ee6e1 + dd4207c commit b7e6eb3

File tree

89 files changed

+8322
-633
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+8322
-633
lines changed

1.16_combat-6/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ tasks.processResources {
7373

7474
tasks.runClient {
7575
classpath(sourceSets.getByName("test").runtimeClasspath)
76+
jvmArgs("-XX:+AllowEnhancedClassRedefinition", "-XX:+IgnoreUnrecognizedVMOptions")
7677
}
7778

7879
tasks.withType(JavaCompile::class).configureEach {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright © 2025 moehreag <[email protected]> & Contributors
3+
*
4+
* This file is part of AxolotlClient.
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*
20+
* For more information, see the LICENSE file.
21+
*/
22+
23+
package io.github.axolotlclient.mixin;
24+
25+
import io.github.axolotlclient.AxolotlClientConfig.impl.ui.vanilla.widgets.VanillaButtonWidget;
26+
import io.github.axolotlclient.modules.hud.util.DrawUtil;
27+
import io.github.axolotlclient.util.ButtonWidgetTextures;
28+
import net.minecraft.client.MinecraftClient;
29+
import net.minecraft.client.gui.widget.ButtonWidget;
30+
import net.minecraft.client.util.math.MatrixStack;
31+
import net.minecraft.text.Text;
32+
import net.minecraft.util.Identifier;
33+
import org.spongepowered.asm.mixin.Mixin;
34+
import org.spongepowered.asm.mixin.Shadow;
35+
import org.spongepowered.asm.mixin.injection.At;
36+
import org.spongepowered.asm.mixin.injection.Redirect;
37+
38+
@Mixin(VanillaButtonWidget.class)
39+
public abstract class ConfigVanillaButtonWidgetMixin extends ButtonWidget {
40+
@Shadow(remap = false)
41+
public abstract int getX();
42+
43+
@Shadow(remap = false)
44+
public abstract int getY();
45+
46+
private ConfigVanillaButtonWidgetMixin(int x, int y, int width, int height, Text message, PressAction action) {
47+
super(x, y, width, height, message, action);
48+
}
49+
50+
@Redirect(method = "renderButton", at = @At(value = "INVOKE", target = "Lio/github/axolotlclient/AxolotlClientConfig/impl/ui/vanilla/widgets/VanillaButtonWidget;drawTexture(Lnet/minecraft/client/util/math/MatrixStack;IIIIII)V", ordinal = 0))
51+
private void drawTexture$1(VanillaButtonWidget instance, MatrixStack stack, int x, int y, int u, int v, int width, int height) {
52+
53+
}
54+
55+
@Redirect(method = "renderButton", at = @At(value = "INVOKE", target = "Lio/github/axolotlclient/AxolotlClientConfig/impl/ui/vanilla/widgets/VanillaButtonWidget;drawTexture(Lnet/minecraft/client/util/math/MatrixStack;IIIIII)V", ordinal = 1))
56+
private void drawTexture$2$replaceWithNineSlice(VanillaButtonWidget instance, MatrixStack stack, int x, int y, int u, int v, int width, int height) {
57+
Identifier tex = ButtonWidgetTextures.get(getYImage(hovered));
58+
DrawUtil.blitSprite(tex, getX(), getY(), getWidth(), getHeight(), new DrawUtil.NineSlice(200, 20, 3));
59+
MinecraftClient.getInstance().getTextureManager().bindTexture(WIDGETS_LOCATION);
60+
}
61+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright © 2025 moehreag <[email protected]> & Contributors
3+
*
4+
* This file is part of AxolotlClient.
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*
20+
* For more information, see the LICENSE file.
21+
*/
22+
23+
package io.github.axolotlclient.mixin.skins;
24+
25+
import net.minecraft.client.texture.NativeImage;
26+
import net.minecraft.client.texture.PlayerSkinTexture;
27+
import org.spongepowered.asm.mixin.Mixin;
28+
import org.spongepowered.asm.mixin.gen.Invoker;
29+
30+
@Mixin(PlayerSkinTexture.class)
31+
public interface PlayerSkinTextureAccessor {
32+
33+
@Invoker("remapTexture")
34+
static NativeImage invokeRemapTexture(NativeImage img) {
35+
throw new UnsupportedOperationException();
36+
}
37+
}

1.16_combat-6/src/main/java/io/github/axolotlclient/modules/auth/AccountsScreen.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
package io.github.axolotlclient.modules.auth;
2424

25+
import io.github.axolotlclient.modules.auth.skin.SkinManagementScreen;
2526
import net.minecraft.client.gui.screen.ConfirmScreen;
2627
import net.minecraft.client.gui.screen.Screen;
2728
import net.minecraft.client.gui.screen.ScreenTexts;
@@ -36,6 +37,7 @@ public class AccountsScreen extends Screen {
3637
private ButtonWidget loginButton;
3738
private ButtonWidget deleteButton;
3839
private ButtonWidget refreshButton;
40+
private ButtonWidget skinsButton;
3941

4042
public AccountsScreen(Screen currentScreen) {
4143
super(new TranslatableText("accounts"));
@@ -77,10 +79,14 @@ public void init() {
7779

7880
accountsListWidget.setAccounts(Auth.getInstance().getAccounts());
7981

80-
addButton(loginButton = new ButtonWidget(this.width / 2 - 154, this.height - 52, 150, 20, new TranslatableText("auth.login"),
82+
addButton(loginButton = new ButtonWidget(this.width / 2 - 154, this.height - 52, 100, 20, new TranslatableText("auth.login"),
8183
buttonWidget -> login()));
8284

83-
this.addButton(new ButtonWidget(this.width / 2 + 4, this.height - 52, 150, 20, new TranslatableText("auth.add"),
85+
addButton(skinsButton = new ButtonWidget(this.width / 2 - 50, this.height - 52, 100, 20, new TranslatableText("skins.manage"),
86+
btn -> client.openScreen(new SkinManagementScreen(
87+
this, accountsListWidget.getSelected().getAccount()))));
88+
89+
this.addButton(new ButtonWidget(this.width / 2 + 4 + 50, this.height - 52, 100, 20, new TranslatableText("auth.add"),
8490
button -> {
8591
if (!Auth.getInstance().allowOfflineAccounts()) {
8692
initMSAuth();
@@ -120,27 +126,25 @@ public void removed() {
120126
}
121127

122128
private void initMSAuth() {
123-
Auth.getInstance().getAuth().startDeviceAuth().thenRun(() -> client.execute(this::refresh));
129+
Auth.getInstance().getMsApi().startDeviceAuth().thenRun(() -> client.execute(this::refresh));
124130
}
125131

126132
private void refreshAccount() {
127133
refreshButton.active = false;
128134
AccountsListWidget.Entry entry = accountsListWidget.getSelected();
129135
if (entry != null) {
130-
entry.getAccount().refresh(Auth.getInstance().getAuth()).thenRun(() -> client.execute(() -> {
131-
Auth.getInstance().save();
132-
refresh();
133-
}));
136+
entry.getAccount().refresh(Auth.getInstance().getMsApi());
134137
}
135138
}
136139

137140
private void updateButtonActivationStates() {
138141
AccountsListWidget.Entry entry = accountsListWidget.getSelected();
139142
if (client.world == null && entry != null) {
140143
loginButton.active = entry.getAccount().isExpired() || !entry.getAccount().equals(Auth.getInstance().getCurrent());
141-
deleteButton.active = refreshButton.active = true;
144+
refreshButton.active = skinsButton.active = !entry.getAccount().isOffline();
145+
deleteButton.active = true;
142146
} else {
143-
loginButton.active = deleteButton.active = refreshButton.active = false;
147+
loginButton.active = deleteButton.active = refreshButton.active = skinsButton.active = false;
144148
}
145149
}
146150

1.16_combat-6/src/main/java/io/github/axolotlclient/modules/auth/Auth.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,24 @@
2323
package io.github.axolotlclient.modules.auth;
2424

2525
import java.util.*;
26+
import java.util.concurrent.CompletableFuture;
2627

2728
import com.mojang.authlib.GameProfile;
2829
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
2930
import io.github.axolotlclient.AxolotlClient;
30-
import io.github.axolotlclient.AxolotlClientConfig.api.options.OptionCategory;
3131
import io.github.axolotlclient.AxolotlClientConfig.impl.options.BooleanOption;
3232
import io.github.axolotlclient.api.API;
3333
import io.github.axolotlclient.api.types.User;
3434
import io.github.axolotlclient.api.util.UUIDHelper;
3535
import io.github.axolotlclient.mixin.MinecraftClientAccessor;
3636
import io.github.axolotlclient.modules.Module;
37+
import io.github.axolotlclient.modules.auth.skin.SkinManager;
3738
import io.github.axolotlclient.util.ThreadExecuter;
3839
import io.github.axolotlclient.util.notifications.Notifications;
3940
import io.github.axolotlclient.util.options.GenericOption;
4041
import lombok.Getter;
4142
import net.minecraft.client.MinecraftClient;
4243
import net.minecraft.client.gui.screen.ConfirmScreen;
43-
import net.minecraft.client.gui.screen.Screen;
4444
import net.minecraft.client.util.DefaultSkinHelper;
4545
import net.minecraft.client.util.Session;
4646
import net.minecraft.text.TranslatableText;
@@ -51,17 +51,20 @@ public class Auth extends Accounts implements Module {
5151
@Getter
5252
private final static Auth Instance = new Auth();
5353
public final BooleanOption showButton = new BooleanOption("auth.showButton", false);
54+
public final BooleanOption skinManagerAnimations = new BooleanOption("skins.manage.animations", true);
5455
private final MinecraftClient client = MinecraftClient.getInstance();
5556
private final GenericOption viewAccounts = new GenericOption("viewAccounts", "clickToOpen", () -> client.openScreen(new AccountsScreen(client.currentScreen)));
5657

5758
private final Map<String, Identifier> textures = new HashMap<>();
5859
private final Set<String> loadingTexture = new HashSet<>();
5960
private final Map<String, GameProfile> profileCache = new WeakHashMap<>();
61+
@Getter
62+
private final SkinManager skinManager = new SkinManager();
6063

6164
@Override
6265
public void init() {
6366
load();
64-
this.auth = new MSAuth(AxolotlClient.LOGGER, this, () -> client.options.language);
67+
this.msApi = new MSApi(this, () -> client.options.language);
6568
if (isContained(client.getSession().getUuid())) {
6669
current = getAccounts().stream().filter(account -> account.getUuid().equals(client.getSession().getUuid())).toList().get(0);
6770
current.setAuthToken(client.getSession().getAccessToken());
@@ -73,7 +76,6 @@ public void init() {
7376
current = new Account(client.getSession().getUsername(), client.getSession().getUuid(), client.getSession().getAccessToken());
7477
}
7578

76-
OptionCategory category = OptionCategory.create("auth");
7779
category.add(showButton, viewAccounts);
7880
AxolotlClient.config().general.add(category);
7981
}
@@ -88,12 +90,10 @@ protected void login(Account account) {
8890
if (account.isExpired()) {
8991
Notifications.getInstance().addStatus(new TranslatableText("auth.notif.title"), new TranslatableText("auth.notif.refreshing", account.getName()));
9092
}
91-
account.refresh(auth).thenAccept(res -> {
92-
res.ifPresent(a -> {
93-
if (!a.isExpired()) {
94-
login(a);
95-
}
96-
});
93+
account.refresh(msApi).thenAccept(a -> {
94+
if (!a.isExpired()) {
95+
login(a);
96+
}
9797
}).thenRun(this::save);
9898
} else {
9999
try {
@@ -138,14 +138,18 @@ public void loadTextures(String uuid, String name) {
138138
}
139139

140140
@Override
141-
void showAccountsExpiredScreen(Account account) {
142-
Screen current = client.currentScreen;
141+
CompletableFuture<Account> showAccountsExpiredScreen(Account account) {
142+
var screen = client.currentScreen;
143+
var fut = new CompletableFuture<Account>();
143144
client.execute(() -> client.openScreen(new ConfirmScreen((bl) -> {
144-
client.openScreen(current);
145145
if (bl) {
146-
auth.startDeviceAuth();
146+
msApi.startDeviceAuth().thenRun(() -> fut.complete(account));
147+
} else {
148+
fut.cancel(true);
147149
}
150+
client.openScreen(screen);
148151
}, new TranslatableText("auth"), new TranslatableText("auth.accountExpiredNotice", account.getName()))));
152+
return fut;
149153
}
150154

151155
@Override

0 commit comments

Comments
 (0)