Compare commits

...
Author SHA1 Message Date
MythandGlavo 1605eb1775 fix: add locale to avoid wrong yggdrasil server name (#2521)
* fix: add locale to avoid wrong yggdrasil server name

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-03-23 14:19:16 +08:00
Glavo 94ccee0b76 优化 I18n (#2948)
* update

* update

* update

* update

* update
2024-03-23 14:11:52 +08:00
Glavo 8b94fe5b0b 更新模组和整合包信息 (#2947)
* update

* update
2024-03-23 11:31:09 +08:00
Glavo abec1abcff 更新默认皮肤 (#2943)
* 更新默认皮肤

* update

* update

* update

* update

* update

* update
2024-03-22 16:09:25 +08:00
Glavo 127938fa38 支持更多格式的版本图标 (#2942) 2024-03-21 20:01:21 +08:00
60 changed files with 14788 additions and 6880 deletions

No files matched your search

+3 -32
View File
@@ -27,7 +27,7 @@ S = ';'
MOD_SEPARATOR = ','
CURSEFORGE_PATTERN1 = re.compile(
r'^/minecraft/(mc-mods|modpacks|customization|mc-addons|customization/configuration)/+(?P<modid>[\w-]+)(/(.*?))?$')
r'^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons)/+(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN2 = re.compile(
r'^/projects/(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN3 = re.compile(
@@ -58,30 +58,6 @@ def parseMcmod(url):
return match.group('modid')
return ''
MCBBS_HTML_PATTERN = re.compile(r'/+thread-(?P<modid>\d+)-(\d+)-(\d+).html')
MCBBS_PHP_PATTERN = re.compile(r'')
def parseMcbbs(url):
res = urlparse(url)
if res.scheme not in ['http', 'https']:
return ''
if res.netloc != 'www.mcbbs.net':
return ''
match = MCBBS_HTML_PATTERN.match(res.path)
if match:
return match.group('modid')
query = parse_qs(res.query)
if res.path == '/forum.php':
if 'mod' in query and 'tid' in query and query['mod'] == ['viewthread']:
return query['tid']
return ''
skip = [
'Minecraft',
'The Building Game'
@@ -111,7 +87,7 @@ if __name__ == '__main__':
exit(1)
black_lists = [
'Master Chef'
'Wood Converter'
]
curseforge_id = ''
@@ -131,11 +107,6 @@ if __name__ == '__main__':
if mcmod_id == '':
print('Error mcmod ' + chinese_name)
exit(1)
if 'mcbbs' in links and links['mcbbs']:
mcbbs_id = parseMcbbs(links['mcbbs'][0]['url'])
if mcbbs_id == '':
print('Error mcbbs ' + chinese_name)
exit(1)
mod_id = []
if 'modid' in mod and 'list' in mod['modid']:
@@ -148,6 +119,6 @@ if __name__ == '__main__':
mod_ids = MOD_SEPARATOR.join([str(id) for id in mod_id])
outfile.write(
f'{curseforge_id}{S}{mcmod_id}{S}{mcbbs_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
f'{curseforge_id}{S}{mcmod_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
print('Success!')
@@ -12,14 +12,10 @@ import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jetbrains.annotations.Nullable;
public class SkinCanvas extends Group {
public static final Image ALEX = FXUtils.newBuiltinImage("/assets/img/skin/alex.png");
public static final Image STEVE = FXUtils.newBuiltinImage("/assets/img/skin/steve.png");
public static final SkinCube ALEX_LARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, true);
public static final SkinCube ALEX_RARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, true);
@@ -152,10 +148,6 @@ public class SkinCanvas extends Group {
capeGroup.setVisible(hasCape);
}
public SkinCanvas(double preW, double preH) {
this(STEVE, preW, preH, true);
}
public SkinCanvas(Image skin, double preW, double preH, boolean msaa) {
this.skin = skin;
this.preW = preW;
@@ -25,9 +25,6 @@ import javafx.scene.control.ButtonType;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.stage.Stage;
import org.jackhuang.hmcl.auth.offline.Skin;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.SambaException;
import org.jackhuang.hmcl.task.AsyncTaskExecutor;
@@ -53,10 +50,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.Logging.LOG;
@@ -71,8 +66,6 @@ public final class Launcher extends Application {
CookieHandler.setDefault(COOKIE_MANAGER);
register();
LOG.info("JavaFX Version: " + System.getProperty("javafx.runtime.version"));
try {
Object pipeline = Class.forName("com.sun.prism.GraphicsPipeline").getMethod("getPipeline").invoke(null);
@@ -128,45 +121,6 @@ public final class Launcher extends Application {
}
}
private static void register() {
Skin.registerDefaultSkinLoader((type) -> {
switch (type) {
case ALEX:
return Skin.class.getResourceAsStream("/assets/img/skin/alex.png");
case ARI:
return Skin.class.getResourceAsStream("/assets/img/skin/ari.png");
case EFE:
return Skin.class.getResourceAsStream("/assets/img/skin/efe.png");
case KAI:
return Skin.class.getResourceAsStream("/assets/img/skin/kai.png");
case MAKENA:
return Skin.class.getResourceAsStream("/assets/img/skin/makena.png");
case NOOR:
return Skin.class.getResourceAsStream("/assets/img/skin/noor.png");
case STEVE:
return Skin.class.getResourceAsStream("/assets/img/skin/steve.png");
case SUNNY:
return Skin.class.getResourceAsStream("/assets/img/skin/sunny.png");
case ZURI:
return Skin.class.getResourceAsStream("/assets/img/skin/zuri.png");
default:
return null;
}
});
RemoteMod.registerEmptyRemoteMod(new RemoteMod("", "", i18n("mods.broken_dependency.title"), i18n("mods.broken_dependency.desc"), new ArrayList<>(), "", "/assets/img/icon@8x.png", new RemoteMod.IMod() {
@Override
public List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException {
throw new IOException();
}
@Override
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository) throws IOException {
throw new IOException();
}
}));
}
private static ButtonType showAlert(AlertType alertType, String contentText, ButtonType... buttons) {
return new Alert(alertType, contentText, buttons).showAndWait().orElse(null);
}
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
@@ -72,7 +71,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
}
}
if (I18n.getCurrentLocale().getLocale() != Locale.CHINA) {
if (!I18n.isUseChinese()) {
return;
}
@@ -259,8 +259,50 @@ public class HMCLGameRepository extends DefaultGameRepository {
return vs;
}
public File getVersionIconFile(String id) {
return new File(getVersionRoot(id), "icon.png");
public Optional<File> getVersionIconFile(String id) {
File root = getVersionRoot(id);
File iconFile = new File(root, "icon.png");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.jpg");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.bmp");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.gif");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
return Optional.empty();
}
public void setVersionIconFile(String id, File iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
if (!ext.equals("png") && !ext.equals("jpg") && !ext.equals("bmp") && !ext.equals("gif")) {
throw new IllegalArgumentException("Unsupported icon file: " + ext);
}
deleteIconFile(id);
FileUtils.copyFile(iconFile, new File(getVersionRoot(id), "icon." + ext));
}
public void deleteIconFile(String id) {
File root = getVersionRoot(id);
new File(root, "icon.png").delete();
new File(root, "icon.jpg").delete();
new File(root, "icon.bmp").delete();
new File(root, "icon.gif").delete();
}
public Image getVersionIconImage(String id) {
@@ -272,9 +314,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
if (iconType == VersionIconType.DEFAULT) {
Version version = getVersion(id).resolve(this);
File iconFile = getVersionIconFile(id);
if (iconFile.exists()) {
try (InputStream inputStream = new FileInputStream(iconFile)) {
Optional<File> iconFile = getVersionIconFile(id);
if (iconFile.isPresent()) {
try (InputStream inputStream = new FileInputStream(iconFile.get())) {
return new Image(inputStream);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to load version icon of " + id, e);
@@ -140,23 +140,35 @@ public final class TexturesLoader {
// ====
// ==== Skins ====
private final static Map<TextureModel, LoadedTexture> DEFAULT_SKINS = new EnumMap<>(TextureModel.class);
private static final String[] DEFAULT_SKINS = {"alex", "ari", "efe", "kai", "makena", "noor", "steve", "sunny", "zuri"};
static {
loadDefaultSkin("/assets/img/skin/steve.png", TextureModel.STEVE);
loadDefaultSkin("/assets/img/skin/alex.png", TextureModel.ALEX);
public static Image getDefaultSkinImage() {
return FXUtils.newBuiltinImage("/assets/img/skin/wide/steve.png");
}
private static void loadDefaultSkin(String path, TextureModel model) {
DEFAULT_SKINS.put(model, new LoadedTexture(FXUtils.newBuiltinImage(path), singletonMap("model", model.modelName)));
public static LoadedTexture getDefaultSkin(UUID uuid) {
int idx = Math.floorMod(uuid.hashCode(), DEFAULT_SKINS.length * 2);
TextureModel model;
Image skin;
if (idx < DEFAULT_SKINS.length) {
model = TextureModel.SLIM;
skin = FXUtils.newBuiltinImage("/assets/img/skin/slim/" + DEFAULT_SKINS[idx] + ".png");
} else {
model = TextureModel.WIDE;
skin = FXUtils.newBuiltinImage("/assets/img/skin/wide/" + DEFAULT_SKINS[idx - DEFAULT_SKINS.length] + ".png");
}
return new LoadedTexture(skin, singletonMap("model", model.modelName));
}
public static LoadedTexture getDefaultSkin(TextureModel model) {
return DEFAULT_SKINS.get(model);
public static TextureModel getDefaultModel(UUID uuid) {
return TextureModel.WIDE.modelName.equals(getDefaultSkin(uuid).getMetadata().get("model"))
? TextureModel.WIDE
: TextureModel.SLIM;
}
public static ObjectBinding<LoadedTexture> skinBinding(YggdrasilService service, UUID uuid) {
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(uuid));
LoadedTexture uuidFallback = getDefaultSkin(uuid);
return BindingMapping.of(service.getProfileRepository().binding(uuid))
.map(profile -> profile
.flatMap(it -> {
@@ -187,7 +199,7 @@ public final class TexturesLoader {
}
public static ObservableValue<LoadedTexture> skinBinding(Account account) {
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(account.getUUID()));
LoadedTexture uuidFallback = getDefaultSkin(account.getUUID());
if (account instanceof OfflineAccount) {
OfflineAccount offlineAccount = (OfflineAccount) account;
@@ -348,7 +360,7 @@ public final class TexturesLoader {
fxAvatarBinding(canvas, skinBinding(account));
else {
unbindAvatar(canvas);
drawAvatar(canvas, getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image);
drawAvatar(canvas, getDefaultSkin(account.getUUID()).image);
}
}
@@ -27,7 +27,6 @@ import javafx.scene.control.Tooltip;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
@@ -58,7 +57,7 @@ public class AccountAdvancedListItem extends AdvancedListItem {
tooltip.setText(i18n("account.create"));
TexturesLoader.unbindAvatar(canvas);
TexturesLoader.drawAvatar(canvas, TexturesLoader.getDefaultSkin(TextureModel.STEVE).getImage());
TexturesLoader.drawAvatar(canvas, TexturesLoader.getDefaultSkinImage());
} else {
titleProperty().bind(BindingMapping.of(account, Account::getCharacter));
@@ -25,7 +25,6 @@ import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.*;
@@ -35,6 +34,7 @@ import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.Skin;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -42,6 +42,7 @@ import org.jackhuang.hmcl.ui.construct.*;
import java.io.File;
import java.util.Arrays;
import java.util.UUID;
import java.util.logging.Level;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
@@ -71,7 +72,7 @@ public class OfflineAccountSkinPane extends StackPane {
BorderPane pane = new BorderPane();
SkinCanvas canvas = new SkinCanvas(SkinCanvas.STEVE, 300, 300, true);
SkinCanvas canvas = new SkinCanvas(TexturesLoader.getDefaultSkinImage(), 300, 300, true);
StackPane canvasPane = new StackPane(canvas);
canvasPane.setPrefWidth(300);
canvasPane.setPrefHeight(300);
@@ -116,11 +117,11 @@ public class OfflineAccountSkinPane extends StackPane {
));
modelCombobox.setConverter(stringConverter(model -> i18n("account.skin.model." + model.modelName)));
modelCombobox.getItems().setAll(TextureModel.STEVE, TextureModel.ALEX);
modelCombobox.getItems().setAll(TextureModel.WIDE, TextureModel.SLIM);
if (account.getSkin() == null) {
skinItem.setSelectedData(Skin.Type.DEFAULT);
modelCombobox.setValue(TextureModel.STEVE);
modelCombobox.setValue(TextureModel.WIDE);
} else {
skinItem.setSelectedData(account.getSkin().getType());
cslApiField.setText(account.getSkin().getCslApi());
@@ -136,13 +137,18 @@ public class OfflineAccountSkinPane extends StackPane {
LOG.log(Level.WARNING, "Failed to load skin", exception);
Controllers.showToast(i18n("message.failed"));
} else {
UUID uuid = this.account.getUUID();
if (result == null || result.getSkin() == null && result.getCape() == null) {
canvas.updateSkin(getDefaultTexture(), isDefaultSlim(), null);
canvas.updateSkin(
TexturesLoader.getDefaultSkin(uuid).getImage(),
TexturesLoader.getDefaultModel(uuid) == TextureModel.SLIM,
null
);
return;
}
canvas.updateSkin(
result.getSkin() != null ? result.getSkin().getImage() : getDefaultTexture(),
result.getModel() == TextureModel.ALEX,
result.getSkin() != null ? result.getSkin().getImage() : TexturesLoader.getDefaultSkin(uuid).getImage(),
result.getModel() == TextureModel.SLIM,
result.getCape() != null ? result.getCape().getImage() : null);
}
}).start();
@@ -198,17 +204,4 @@ public class OfflineAccountSkinPane extends StackPane {
private Skin getSkin() {
return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
}
private boolean isDefaultSlim() {
return TextureModel.detectUUID(account.getUUID()) == TextureModel.ALEX;
}
private Image getDefaultTexture() {
if (isDefaultSlim()) {
return SkinCanvas.ALEX;
} else {
return SkinCanvas.STEVE;
}
}
}
@@ -177,7 +177,7 @@ public abstract class SettingsView extends StackPane {
languagePane.setLeft(left);
cboLanguage = new JFXComboBox<>();
cboLanguage.setConverter(stringConverter(locale -> locale.getName(I18n.getCurrentLocale().getResourceBundle())));
cboLanguage.setConverter(stringConverter(I18n::getName));
FXUtils.setLimitWidth(cboLanguage, 300);
languagePane.setRight(cboLanguage);
@@ -477,7 +477,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
protected void updateControl(RemoteMod dataItem, boolean empty) {
if (empty) return;
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(getSkinnable().repository.getType()).getModByCurseForgeId(dataItem.getSlug());
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : dataItem.getTitle());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : dataItem.getTitle());
content.setSubtitle(dataItem.getDescription());
content.getTags().setAll(dataItem.getCategories().stream()
.map(category -> getSkinnable().getLocalizedCategory(category))
@@ -240,7 +240,7 @@ public class DownloadPage extends Control implements DecoratorPage {
TwoLineListItem content = new TwoLineListItem();
HBox.setHgrow(content, Priority.ALWAYS);
ModTranslations.Mod mod = getSkinnable().translations.getModByCurseForgeId(getSkinnable().addon.getSlug());
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : getSkinnable().addon.getTitle());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : getSkinnable().addon.getTitle());
content.setSubtitle(getSkinnable().addon.getDescription());
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
.map(category -> getSkinnable().page.getLocalizedCategory(category))
@@ -352,15 +352,21 @@ public class DownloadPage extends Control implements DecoratorPage {
container.setOnMouseClicked(e -> Controllers.navigate(new DownloadPage(page, addon, version, callback)));
getChildren().setAll(container);
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : addon.getTitle());
content.setSubtitle(addon.getDescription());
content.getTags().setAll(addon.getCategories().stream()
.map(page::getLocalizedCategory)
.collect(Collectors.toList()));
if (addon != RemoteMod.BROKEN) {
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.getTitle());
content.setSubtitle(addon.getDescription());
content.getTags().setAll(addon.getCategories().stream()
.map(page::getLocalizedCategory)
.collect(Collectors.toList()));
if (StringUtils.isNotBlank(addon.getIconUrl())) {
imageView.setImage(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true, true));
if (StringUtils.isNotBlank(addon.getIconUrl())) {
imageView.setImage(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true, true));
}
} else {
content.setTitle(i18n("mods.broken_dependency.title"));
content.setSubtitle(i18n("mods.broken_dependency.desc"));
imageView.setImage(FXUtils.newBuiltinImage("/assets/img/icon@8x.png", 40, 40, true, true));
}
}
}
@@ -490,10 +490,8 @@ class ModListPageSkin extends SkinBase<ModListPage> {
content.getTags().add(i18n("install.installer.quilt"));
break;
}
if (dataItem.getMod() != null) {
if (I18n.getCurrentLocale().getLocale() == Locale.CHINA) {
content.getTags().add(dataItem.getMod().getDisplayName());
}
if (dataItem.getMod() != null && I18n.isUseChinese()) {
content.getTags().add(dataItem.getMod().getDisplayName());
}
content.setSubtitle(dataItem.getSubtitle());
if (booleanProperty != null) {
@@ -198,10 +198,9 @@ public enum ModTranslations {
return true;
}
public static class Mod {
public static final class Mod {
private final String curseforge;
private final String mcmod;
private final String mcbbs;
private final List<String> modIds;
private final String name;
private final String subname;
@@ -209,23 +208,21 @@ public enum ModTranslations {
public Mod(String line) {
String[] items = line.split(";", -1);
if (items.length != 7) {
throw new IllegalArgumentException("Illegal mod data line, 7 items expected " + line);
if (items.length != 6) {
throw new IllegalArgumentException("Illegal mod data line, 6 items expected " + line);
}
curseforge = items[0];
mcmod = items[1];
mcbbs = items[2];
modIds = Collections.unmodifiableList(Arrays.asList(items[3].split(",")));
name = items[4];
subname = items[5];
abbr = items[6];
modIds = Collections.unmodifiableList(Arrays.asList(items[2].split(",")));
name = items[3];
subname = items[4];
abbr = items[5];
}
public Mod(String curseforge, String mcmod, String mcbbs, List<String> modIds, String name, String subname, String abbr) {
public Mod(String curseforge, String mcmod, List<String> modIds, String name, String subname, String abbr) {
this.curseforge = curseforge;
this.mcmod = mcmod;
this.mcbbs = mcbbs;
this.modIds = modIds;
this.name = name;
this.subname = subname;
@@ -252,10 +249,6 @@ public enum ModTranslations {
return mcmod;
}
public String getMcbbs() {
return mcbbs;
}
public List<String> getModIds() {
return modIds;
}
@@ -32,7 +32,6 @@ import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.DialogPane;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
@@ -74,20 +73,19 @@ public class VersionIconDialog extends DialogPane {
private void exploreIcon() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png", "*.jpg", "*.bmp", "*.gif"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
File iconFile = profile.getRepository().getVersionIconFile(versionId);
try {
FileUtils.copyFile(selectedFile, iconFile);
profile.getRepository().setVersionIconFile(versionId, selectedFile);
if (vs != null) {
vs.setVersionIcon(VersionIconType.DEFAULT);
}
onAccept();
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e);
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.SEVERE, "Failed to set icon file: " + selectedFile, e);
}
}
}
@@ -48,7 +48,6 @@ import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -642,9 +641,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
if (versionId == null)
return;
File iconFile = profile.getRepository().getVersionIconFile(versionId);
if (iconFile.exists())
iconFile.delete();
profile.getRepository().deleteIconFile(versionId);
VersionSetting localVersionSetting = profile.getRepository().getLocalVersionSettingOrCreate(versionId);
if (localVersionSetting != null) {
localVersionSetting.setVersionIcon(VersionIconType.DEFAULT);
@@ -20,10 +20,9 @@ package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import java.time.Instant;
import java.util.Arrays;
import java.util.IllegalFormatException;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Logging.LOG;
@@ -34,17 +33,25 @@ public final class I18n {
}
private static volatile SupportedLocale locale = Locales.DEFAULT;
private static volatile ResourceBundle resourceBundle = locale.getResourceBundle();
private static volatile DateTimeFormatter dateTimeFormatter;
public static void setLocale(SupportedLocale locale) {
I18n.locale = locale;
resourceBundle = locale.getResourceBundle();
dateTimeFormatter = null;
}
public static SupportedLocale getCurrentLocale() {
return locale;
public static boolean isUseChinese() {
return locale.getLocale() == Locale.CHINA;
}
public static ResourceBundle getResourceBundle() {
return getCurrentLocale().getResourceBundle();
return resourceBundle;
}
public static String getName(SupportedLocale locale) {
return locale == Locales.DEFAULT ? resourceBundle.getString("lang.default") : locale.getResourceBundle().getString("lang");
}
public static String i18n(String key, Object... formatArgs) {
@@ -69,7 +76,12 @@ public final class I18n {
}
public static String formatDateTime(Instant instant) {
return getCurrentLocale().getDateTimeFormatter().format(instant);
DateTimeFormatter formatter = dateTimeFormatter;
if (formatter == null) {
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("world.time")).withZone(ZoneId.systemDefault());
}
return formatter.format(instant);
}
public static boolean hasKey(String key) {
@@ -24,8 +24,6 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import java.io.IOException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
@@ -34,7 +32,7 @@ public final class Locales {
private Locales() {
}
public static final SupportedLocale DEFAULT = new SupportedLocale(Locale.getDefault(), "lang.default");
public static final SupportedLocale DEFAULT = new SupportedLocale(Locale.getDefault());
/**
* English
@@ -102,23 +100,10 @@ public final class Locales {
@JsonAdapter(SupportedLocale.TypeAdapter.class)
public static final class SupportedLocale {
private final Locale locale;
private final String name;
private final ResourceBundle resourceBundle;
private DateTimeFormatter dateTimeFormatter;
private ResourceBundle resourceBundle;
SupportedLocale(Locale locale) {
this(locale, null);
}
SupportedLocale(Locale locale, String name) {
this.locale = locale;
this.name = name;
if (JavaVersion.CURRENT_JAVA.getParsedVersion() == JavaVersion.JAVA_8) {
resourceBundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control.INSTANCE);
} else {
// UTF-8 is supported in Java 9+
resourceBundle = ResourceBundle.getBundle("assets.lang.I18N", locale);
}
}
public Locale getLocale() {
@@ -126,20 +111,22 @@ public final class Locales {
}
public ResourceBundle getResourceBundle() {
return resourceBundle;
}
ResourceBundle bundle = resourceBundle;
public DateTimeFormatter getDateTimeFormatter() {
if (dateTimeFormatter == null) {
dateTimeFormatter = DateTimeFormatter.ofPattern(resourceBundle.getString("world.time")).withZone(ZoneId.systemDefault());
if (resourceBundle == null) {
if (this != DEFAULT && this.locale == DEFAULT.locale) {
bundle = DEFAULT.getResourceBundle();
} else if (JavaVersion.CURRENT_JAVA.getParsedVersion() < 9) {
bundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control.INSTANCE);
} else {
// Java 9+ uses UTF-8 as the default encoding for resource bundles
bundle = ResourceBundle.getBundle("assets.lang.I18N", locale);
}
resourceBundle = bundle;
}
return dateTimeFormatter;
}
public String getName(ResourceBundle newResourceBundle) {
if (name == null) return resourceBundle.getString("lang");
else return newResourceBundle.getString(name);
return bundle;
}
public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 B

@@ -143,8 +143,8 @@ account.portable=Portable Account
account.skin=Skin
account.skin.file=Skin File
account.skin.model=Model
account.skin.model.default=Classic (Steve)
account.skin.model.slim=Slim (Alex)
account.skin.model.default=Classic
account.skin.model.slim=Slim
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=Address
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
@@ -125,8 +125,8 @@ account.password=Contraseña
account.skin=Skin
account.skin.file=Archivo de skin
account.skin.model=Modelo
account.skin.model.default=Clásico (Steve)
account.skin.model.slim=Delgado (Alex)
account.skin.model.default=Clásico
account.skin.model.slim=Delgado
account.skin.type.csl_api=Blessing skin
account.skin.type.csl_api.location=URL
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
@@ -112,8 +112,8 @@ account.password=パスワード
account.skin=スキン
account.skin.file=スキンイメージファイル
account.skin.model=モデル
account.skin.model.default=Classic (Steve)
account.skin.model.slim=Slim (Alex)
account.skin.model.default=Classic
account.skin.model.slim=Slim
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=アドレス
account.skin.type.csl_api.location.hint=CustomSkinAPI
@@ -120,8 +120,8 @@ account.password=Пароль
account.skin=Скин
account.skin.file=Файл скина
account.skin.model=Модель
account.skin.model.default=Классическая (Стив)
account.skin.model.slim=Стройная (Алекс)
account.skin.model.default=Классическая
account.skin.model.slim=Стройная
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=Адрес
account.skin.type.csl_api.location.hint=URL-адрес CustomSkinAPI
@@ -152,8 +152,8 @@ account.portable=便攜帳戶
account.skin=皮膚
account.skin.file=皮膚圖片檔案
account.skin.model=模型
account.skin.model.default=經典(Steve)
account.skin.model.slim=苗條(Alex)
account.skin.model.default=經典
account.skin.model.slim=苗條
account.skin.type.csl_api=Blessing Skin 伺服器
account.skin.type.csl_api.location=伺服器位址
account.skin.type.csl_api.location.hint=CustomSkinAPI 位址
@@ -153,8 +153,8 @@ account.portable=便携账户
account.skin=皮肤
account.skin.file=皮肤图片文件
account.skin.model=模型
account.skin.model.default=经典(Steve)
account.skin.model.slim=苗条(Alex)
account.skin.model.default=经典
account.skin.model.slim=苗条
account.skin.type.csl_api=Blessing Skin 服务器
account.skin.type.csl_api.location=服务器地址
account.skin.type.csl_api.location.hint=CustomSkinAPI 地址
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -8,6 +8,7 @@ import moe.mickey.minecraft.skin.fx.SkinCanvas;
import moe.mickey.minecraft.skin.fx.SkinCanvasSupport;
import moe.mickey.minecraft.skin.fx.animation.SkinAniRunning;
import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms;
import org.jackhuang.hmcl.game.TexturesLoader;
import java.util.function.Consumer;
@@ -16,7 +17,7 @@ public class Test extends Application {
public static final String TITLE = "FX - Minecraft skin preview";
public static SkinCanvas createSkinCanvas() {
SkinCanvas canvas = new SkinCanvas(SkinCanvas.STEVE, 400, 400, true);
SkinCanvas canvas = new SkinCanvas(TexturesLoader.getDefaultSkinImage(), 400, 400, true);
canvas.getAnimationPlayer().addSkinAnimation(new SkinAniWavingArms(100, 2000, 7.5, canvas), new SkinAniRunning(100, 100, 30, canvas));
FunctionHelper.alwaysB(Consumer<SkinCanvas>::accept, canvas, new SkinCanvasSupport.Mouse(.5), new SkinCanvasSupport.Drag(TITLE));
return canvas;
@@ -60,6 +60,7 @@ public class AuthlibInjectorServer implements Observable {
try {
url = addHttpsIfMissing(url);
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
String ali = conn.getHeaderField("x-authlib-injector-api-location");
if (ali != null) {
@@ -68,6 +69,7 @@ public class AuthlibInjectorServer implements Observable {
conn.disconnect();
url = absoluteAli.toString();
conn = (HttpURLConnection) absoluteAli.openConnection();
conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
}
}
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.auth.offline;
import com.google.gson.annotations.SerializedName;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
import org.jackhuang.hmcl.task.FetchTask;
import org.jackhuang.hmcl.task.GetTask;
@@ -39,7 +40,6 @@ import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.tryCast;
@@ -99,12 +99,6 @@ public class Skin {
}
}
private static Function<Type, InputStream> defaultSkinLoader = null;
public static void registerDefaultSkinLoader(Function<Type, InputStream> defaultSkinLoader0) {
defaultSkinLoader = defaultSkinLoader0;
}
private final Type type;
private final String cslApi;
private final TextureModel textureModel;
@@ -128,7 +122,7 @@ public class Skin {
}
public TextureModel getTextureModel() {
return textureModel == null ? TextureModel.STEVE : textureModel;
return textureModel == null ? TextureModel.WIDE : textureModel;
}
public String getLocalSkinPath() {
@@ -152,11 +146,14 @@ public class Skin {
case STEVE:
case SUNNY:
case ZURI:
if (defaultSkinLoader == null) {
return Task.supplyAsync(() -> null);
}
TextureModel model = type == Type.ALEX ? TextureModel.ALEX : TextureModel.STEVE;
return Task.supplyAsync(() -> new LoadedSkin(model, Texture.loadTexture(defaultSkinLoader.apply(type)), null));
TextureModel model = this.textureModel != null ? this.textureModel : type == Type.ALEX ? TextureModel.SLIM : TextureModel.WIDE;
String resource = (model == TextureModel.SLIM ? "/assets/img/skin/slim/" : "/assets/img/skin/wide/") + type.name().toLowerCase(Locale.ROOT) + ".png";
return Task.supplyAsync(() -> new LoadedSkin(
model,
Texture.loadTexture(new Image(resource)),
null
));
case LOCAL_FILE:
return Task.supplyAsync(() -> {
Texture skin = null, cape = null;
@@ -227,16 +224,7 @@ public class Skin {
String localSkinPath = tryCast(storage.get("localSkinPath"), String.class).orElse(null);
String localCapePath = tryCast(storage.get("localCapePath"), String.class).orElse(null);
TextureModel model;
if ("default".equals(textureModel)) {
model = TextureModel.STEVE;
} else if ("slim".equals(textureModel)) {
model = TextureModel.ALEX;
} else {
model = TextureModel.STEVE;
}
return new Skin(type, cslApi, model, localSkinPath, localCapePath);
return new Skin(type, cslApi, "slim".equals(textureModel) ? TextureModel.SLIM : TextureModel.WIDE, localSkinPath, localCapePath);
}
private static class FetchBytesTask extends FetchTask<InputStream> {
@@ -327,9 +315,9 @@ public class Skin {
@Nullable
public TextureModel getModel() {
if (textures != null && textures.slim != null) {
return TextureModel.ALEX;
return TextureModel.SLIM;
} else if (textures != null && textures.defaultSkin != null) {
return TextureModel.STEVE;
return TextureModel.WIDE;
} else {
return null;
}
@@ -351,9 +339,9 @@ public class Skin {
public String getHash() {
TextureModel model = getModel();
if (model == TextureModel.ALEX)
if (model == TextureModel.SLIM)
return getAlexModelHash();
else if (model == TextureModel.STEVE)
else if (model == TextureModel.WIDE)
return getSteveModelHash();
else
return null;
@@ -178,7 +178,7 @@ 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.ALEX) {
if (skin.getModel() == TextureModel.SLIM) {
realTextures.put("SKIN", mapOf(
pair("url", rootUrl + "/textures/" + skin.getSkin().getHash()),
pair("metadata", mapOf(
@@ -17,27 +17,12 @@
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import java.util.Map;
import java.util.UUID;
public enum TextureModel {
STEVE("default"), ALEX("slim");
WIDE("default"), SLIM("slim");
public final String modelName;
TextureModel(String modelName) {
this.modelName = modelName;
}
public static TextureModel detectModelName(Map<String, String> metadata) {
if (metadata != null && "slim".equals(metadata.get("model"))) {
return ALEX;
} else {
return STEVE;
}
}
public static TextureModel detectUUID(UUID uuid) {
return (uuid.hashCode() & 1) == 1 ? ALEX : STEVE;
}
}
@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
@@ -30,18 +31,18 @@ import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.io.NetworkUtils.encodeLocation;
public class RemoteMod {
private static RemoteMod EMPTY = null;
public static void registerEmptyRemoteMod(RemoteMod empty) {
EMPTY = empty;
}
public static RemoteMod getEmptyRemoteMod() {
if (EMPTY == null) {
throw new NullPointerException();
public static final RemoteMod BROKEN = new RemoteMod("", "", "RemoteMod.BROKEN", "", Collections.emptyList(), "", "", new RemoteMod.IMod() {
@Override
public List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException {
throw new IOException();
}
return EMPTY;
}
@Override
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository) throws IOException {
throw new IOException();
}
});
private final String slug;
private final String author;
@@ -158,7 +159,7 @@ public class RemoteMod {
public RemoteMod load() throws IOException {
if (this.remoteMod == null) {
if (this.type == DependencyType.BROKEN) {
this.remoteMod = RemoteMod.getEmptyRemoteMod();
this.remoteMod = RemoteMod.BROKEN;
} else {
this.remoteMod = this.remoteModRepository.getModById(this.id);
}
@@ -90,7 +90,7 @@ public final class NetworkUtils {
connection.setUseCaches(false);
connection.setConnectTimeout(TIME_OUT);
connection.setReadTimeout(TIME_OUT);
connection.setRequestProperty("Accept-Language", Locale.getDefault().toString());
connection.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
return connection;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -9,22 +9,19 @@ import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.*;
public class NormalizedSkinTest {
private static NormalizedSkin getSkin(String name) throws InvalidSkinException {
String path = Paths.get("../HMCL/src/main/resources/assets/img/skin/" + name + ".png").normalize().toAbsolutePath().toUri().toString();
private static NormalizedSkin getSkin(String name, boolean slim) throws InvalidSkinException {
String path = Paths.get(String.format("../HMCLCore/src/main/resources/assets/img/skin/%s/%s.png", slim ? "slim" : "wide", name)).normalize().toAbsolutePath().toUri().toString();
return new NormalizedSkin(new Image(path));
}
@Test
@EnabledIf("org.jackhuang.hmcl.JavaFXLauncher#isStarted")
public void testIsSlim() throws Exception {
assertFalse(getSkin("steve").isSlim());
assertTrue(getSkin("alex").isSlim());
assertTrue(getSkin("noor").isSlim());
assertFalse(getSkin("sunny").isSlim());
assertFalse(getSkin("ari").isSlim());
assertFalse(getSkin("zuri").isSlim());
assertTrue(getSkin("makena").isSlim());
assertFalse(getSkin("kai").isSlim());
assertTrue(getSkin("efe").isSlim());
String[] names = {"alex", "ari", "efe", "kai", "makena", "noor", "steve", "sunny", "zuri"};
for (String skin : names) {
assertTrue(getSkin(skin, true).isSlim());
assertFalse(getSkin(skin, false).isSlim());
}
}
}