Merge branch 'main' into pojav

This commit is contained in:
ShirosakiMio 2024-08-24 18:19:22 +08:00
commit b2508d1702
47 changed files with 400 additions and 220 deletions

View File

@ -1,74 +1,110 @@
name: Bug Feedback
name: Bug Feedback / 错误反馈
description:
Report a bug.
Report a bug. 报告错误。
labels: bug
body:
- type: markdown
attributes:
value: |
Please ensure you are using the **latest bata version** before you submit, you can get the latest beta version by checking update in launcher setting page.
请确认在你提交之前你使用的是最新版本,你可以在启动器的设置页内通过检测更新获取最新版本。
Please confirm that the question you are asking is not related to modpacks.If you ask about modpacks, we will directly delete it.
请确认你询问的不是整合包相关的问题。如果你询问整合包相关问题,我们会直接删除。
To improve communication efficiency, we suggest you join our community to report bugs.
为了提高交流效率,我们建议你加入我们的社区报告错误。
You can find our community in community page of the launcher.
你可以在启动器的社区页内找到我们的社区。
- type: checkboxes
id: checks
attributes:
label: Before making issue / 在发布提问之前...
options:
- label: I have read the content above.我已经阅读了上方的内容。
required: true
- label: I understand what I'm asking is not about modpack.我明白我询问的不是整合包相关的问题。
required: true
- label: I have searched for existing issues.我已经搜索了现有的 issues。
required: true
- label: I have tried switching renderer.我已经尝试过切换渲染器。
- type: input
id: fcl-version
attributes:
label: Launcher Version
description: Please enter the Fold Craft Launcher version you are using.
placeholder: e.g. 1.0.1
label: Launcher Version / 启动器版本
description: |
Please enter the Fold Craft Launcher version you are using.
请输入你正在使用的Fold Craft Launcher版本。
placeholder: e.g. 1.0.1 / 例如1.0.1
validations:
required: true
- type: input
id: device
attributes:
label: Device Information
description: Please enter the information of the device on which you encountered the bug.
label: Device Information / 设备信息
description: |
Please enter the information of the device on which you encountered the bug.
请输入你遇到错误的设备的信息。
placeholder: e.g. OPPO Find X3 Pro, Android 13, arm64
validations:
required: true
- type: textarea
id: bug-report
attributes:
label: Problem Description
description: Please describe the problem in as much detail as possible, especially how you encountered this bug.
label: Problem Description / 问题描述
description: |
Please describe the problem in as much detail as possible, especially how you encountered this bug.
请尽可能详细地描述问题特别是你是如何遇到这个bug的。
validations:
required: true
- type: input
id: java-version
attributes:
label: Java
description: Please enter the version of Java you are using.
description: |
Please enter the version of Java you are using.
请输入你正在使用的Java版本。
placeholder: e.g. Java 17
- type: input
id: renderer
attributes:
label: Renderer
description: Please enter the renderer you are using.
label: Renderer / 渲染器
description: |
Please enter the renderer you are using.
请输入你正在使用的渲染器。
placeholder: e.g. Holy-GL4ES
- type: input
id: game-version
attributes:
label: Game Version
description: If you are having trouble launching a game that was automatically downloaded through the Fold Craft Launcher, please enter the version of the game you are launching.
label: Game Version / 游戏版本
description: |
If you are having trouble launching a game that was automatically downloaded through the Fold Craft Launcher, please enter the version of the game you are launching.
如果你在启动通过Fold Craft Launcher自动下载的游戏时遇到问题请输入你要启动的游戏版本。
placeholder: e.g. 1.19
- type: input
id: game-modify
attributes:
label: Modifications Made to the Game
description: If you have OptiFine, Forge, or other mods installed for the game, please enter your modifications below.
label: Modifications Made to the Game / 对游戏所做的修改
description: |
If you have OptiFine, Forge, or other mods installed for the game, please enter your modifications below.
如果你为游戏安装了OptiFine、Forge或其他模组请在下面输入你的修改。
placeholder: e.g. OptiFine HD_U_G9, Rift
- type: textarea
id: game-crash-report
attributes:
label: Game Crash Report
description: If there is a game crash report, please upload it below.
label: Game Crash Report / 游戏崩溃报告
description: |
If there is a game crash report, please upload it(file) below.
如果有游戏崩溃报告,请在下面上传(文件)。
- type: textarea
id: fcl-crash-report
attributes:
label: Launcher Crash Report
description: If there is a launcher crash report, please upload it below.
label: Launcher Crash Report / 启动器崩溃报告
description: |
If there is a launcher crash report, please upload it(file) below.
如果有启动器崩溃报告,请在下面上传(文件)。
- type: textarea
id: fcl-logs
attributes:
label: Launcher Log File
description: After encountering a problem, please click "Export Launcher Log" in the launcher setting page, and upload the exported log to the input field below.
label: Launcher Log File / 启动器日志文件
description: |
After encountering a problem, please click "Export Launcher Log" in the launcher setting page, and upload the exported log to the input field below.
遇到问题后,请在启动器设置页面中单击“导出启动器日志”,并将导出的日志上传到下面的输入字段。

View File

@ -1,21 +1,36 @@
name: "Feature Request"
description: "Propose new features for Fold Craft Launcher"
name: Feature Request / 功能请求
description: |
Propose new features for Fold Craft Launcher.
为Fold Craft Launcher提出新功能。
labels: feature request
body:
- type: markdown
attributes:
value: "Make sure to check the issues list for duplicates before submitting!"
value: |
Make sure to check the issues list for duplicates before submitting!
提交前,请务必检查问题列表中的重复项!
- type: checkboxes
id: checks
attributes:
label: Before making issue / 在发布提问之前...
options:
- label: I understand this is a request, not a wish.我明白这是在提出请求而不是在许愿。
required: true
- type: textarea
id: "feature"
attributes:
label: Description
description: "Describe in detail the new features you would like to add."
label: Description / 描述
description: |
Describe in detail the new features you would like to add.
详细描述你要添加的新功能。
validations:
required: true
- type: textarea
id: "reason"
attributes:
label: Why
description: "Describe the benefits of this feature and why."
label: Why / 原因
description: |
Describe the benefits of this feature and why.
描述此功能的好处及其原因。
validations:
required: true

View File

@ -44,8 +44,8 @@ android {
applicationId "com.tungsten.fcl"
minSdk 26
targetSdk 34
versionCode 1175
versionName "1.1.7.5"
versionCode 1176
versionName "1.1.7.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@ -4,7 +4,7 @@ fov:0.0
gamma:0.5070422
saturation:0.0
renderDistance:2
guiScale:0
guiScale:4
particles:1
bobView:false
anaglyph3d:false

View File

@ -11,6 +11,8 @@ import android.util.Log;
import com.tungsten.fcl.activity.JVMActivity;
import com.tungsten.fcl.control.MenuType;
import com.tungsten.fcl.setting.Profile;
import com.tungsten.fcl.setting.Profiles;
import com.tungsten.fcl.util.AndroidUtils;
import com.tungsten.fcl.util.RequestCodes;
import com.tungsten.fclauncher.FCLConfig;
@ -59,6 +61,13 @@ public class JarExecutorHelper {
private static void launchJarExecutor(Context context, File file) {
int version = getJavaVersion(file);
int javaVersion = getNearestJavaVersion(version);
Profile profile = Profiles.getSelectedProfile();
if (profile != null) {
String java = profile.getGlobal().getJava();
if (!java.equals(JavaVersion.JAVA_AUTO.getVersionName())) {
javaVersion = JavaVersion.getJavaFromVersionName(java).getVersion();
}
}
exec(context, file, javaVersion, null);
}

View File

@ -207,7 +207,7 @@ public final class LauncherHelper {
launchingStepsPane.dismiss();
if (!success) {
Exception ex = executor.getException();
if (!(ex instanceof CancellationException)) {
if (ex != null && !(ex instanceof CancellationException)) {
Schedulers.androidUIThread().execute(() -> {
String message;
if (ex instanceof ModpackCompletionException) {

View File

@ -17,6 +17,8 @@
*/
package com.tungsten.fcl.game;
import android.os.Environment;
import com.tungsten.fcl.setting.Profile;
import com.tungsten.fclcore.task.Task;
import com.tungsten.fclcore.util.io.CompressingUtils;
@ -48,7 +50,7 @@ public class ManuallyCreatedModpackInstallTask extends Task<Path> {
subdirectory = ModpackHelper.findMinecraftDirectoryInManuallyCreatedModpack(zipFile.toString(), fs);
}
Path dest = Paths.get("externalgames").resolve(name);
Path dest = Paths.get(Environment.getExternalStorageDirectory().getAbsolutePath() + "/FCL").resolve(name);
setResult(dest);

View File

@ -21,12 +21,13 @@ import static com.tungsten.fclcore.util.Lang.mapOf;
import static com.tungsten.fclcore.util.Lang.toIterable;
import static com.tungsten.fclcore.util.Pair.pair;
import android.os.Environment;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.tungsten.fcl.setting.Profile;
import com.tungsten.fcl.setting.Profiles;
import com.tungsten.fcl.setting.VersionSetting;
import com.tungsten.fclcore.game.GameDirectoryType;
import com.tungsten.fclcore.mod.MismatchedModpackTypeException;
import com.tungsten.fclcore.mod.Modpack;
import com.tungsten.fclcore.mod.ModpackCompletionException;
@ -181,7 +182,7 @@ public final class ModpackHelper {
}
public static boolean isExternalGameNameConflicts(String name) {
return Files.exists(Paths.get("externalgames").resolve(name));
return Files.exists(Paths.get(Environment.getExternalStorageDirectory().getAbsolutePath() + "/FCL").resolve(name));
}
public static Task<?> getInstallManuallyCreatedModpackTask(Profile profile, File zipFile, String name, Charset charset) {

View File

@ -143,9 +143,9 @@ public class GameOption {
String str = get("guiScale");
int guiScale;
try {
guiScale = (str == null ? 0 : Integer.parseInt(str));
guiScale = (str == null ? 4 : Integer.parseInt(str));
} catch (NumberFormatException ignore) {
guiScale = 0;
guiScale = 4;
}
int scale = Math.max(Math.min(width / 320, height / 240), 1);
if (scale < guiScale || guiScale == 0) {

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
</shape>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/white"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/white"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.FoldCraftLauncher" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/default_theme_color</item>
<item name="colorPrimaryVariant">@color/default_theme_color</item>
<item name="colorOnPrimary">@color/default_theme_color</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/default_theme_color</item>
<item name="colorSecondaryVariant">@color/default_theme_color</item>
<item name="colorOnSecondary">@color/default_theme_color</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">@color/default_theme_color</item>
<!-- Customize your theme here. -->
<item name="android:windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
</style>
</resources>

View File

@ -270,7 +270,7 @@
<string name="download_provider_mcbbs">我的世界中文论坛 (https://www.mcbbs.net/)</string>
<string name="download_provider_bmclapi">BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)</string>
<string name="download_provider_mojang">官方 (OptiFine 自动安装使用 BMCLAPI 下载源)</string>
<string name="download_provider_mojang">官方 (OptiFine 自动安装使用 OF-302 下载源)</string>
<string name="download_provider_official">尽量使用官方源</string>
<string name="download_provider_balanced">选择加载速度快的下载源</string>
<string name="download_provider_mirror">尽量使用镜像源</string>

View File

@ -293,7 +293,7 @@
<string name="download_provider_mcbbs">MCBBS (https://www.mcbbs.net/)</string>
<string name="download_provider_bmclapi">BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)</string>
<string name="download_provider_mojang">Mojang (OptiFine is provided by BMCLAPI)</string>
<string name="download_provider_mojang">Mojang (OptiFine is provided by OF-302)</string>
<string name="download_provider_official">From Official Sources</string>
<string name="download_provider_balanced">From Fastest Available</string>
<string name="download_provider_mirror">From Mirror</string>

View File

@ -38,7 +38,6 @@ import com.tungsten.fclcore.auth.authlibinjector.AuthlibInjectorArtifactInfo;
import com.tungsten.fclcore.auth.authlibinjector.AuthlibInjectorArtifactProvider;
import com.tungsten.fclcore.auth.authlibinjector.AuthlibInjectorDownloadException;
import com.tungsten.fclcore.auth.yggdrasil.Texture;
import com.tungsten.fclcore.auth.yggdrasil.TextureModel;
import com.tungsten.fclcore.auth.yggdrasil.TextureType;
import com.tungsten.fclcore.fakefx.beans.binding.Bindings;
import com.tungsten.fclcore.fakefx.beans.binding.ObjectBinding;
@ -100,14 +99,7 @@ public class OfflineAccount extends Account {
}
protected boolean loadAuthlibInjector(Skin skin) {
if (skin == null) return false;
if (skin.getType() == Skin.Type.DEFAULT) return false;
TextureModel defaultModel = TextureModel.detectUUID(getUUID());
if (skin.getType() == Skin.Type.ALEX && defaultModel == TextureModel.ALEX ||
skin.getType() == Skin.Type.STEVE && defaultModel == TextureModel.STEVE) {
return false;
}
return true;
return skin != null && skin.getType() != Skin.Type.DEFAULT;
}
@Override

View File

@ -19,11 +19,11 @@ package com.tungsten.fclcore.download;
import com.tungsten.fclcore.download.fabric.FabricAPIVersionList;
import com.tungsten.fclcore.download.fabric.FabricVersionList;
import com.tungsten.fclcore.download.forge.ForgeBMCLVersionList;
import com.tungsten.fclcore.download.forge.ForgeVersionList;
import com.tungsten.fclcore.download.game.GameVersionList;
import com.tungsten.fclcore.download.liteloader.LiteLoaderVersionList;
import com.tungsten.fclcore.download.neoforge.NeoForgeOfficialVersionList;
import com.tungsten.fclcore.download.optifine.OptiFineBMCLVersionList;
import com.tungsten.fclcore.download.optifine.OptiFine302VersionList;
import com.tungsten.fclcore.download.quilt.QuiltAPIVersionList;
import com.tungsten.fclcore.download.quilt.QuiltVersionList;
@ -34,10 +34,10 @@ public class MojangDownloadProvider implements DownloadProvider {
private final GameVersionList game;
private final FabricVersionList fabric;
private final FabricAPIVersionList fabricApi;
private final ForgeBMCLVersionList forge;
private final ForgeVersionList forge;
private final NeoForgeOfficialVersionList neoforge;
private final LiteLoaderVersionList liteLoader;
private final OptiFineBMCLVersionList optifine;
private final OptiFine302VersionList optifine;
private final QuiltVersionList quilt;
private final QuiltAPIVersionList quiltApi;
@ -47,10 +47,10 @@ public class MojangDownloadProvider implements DownloadProvider {
this.game = new GameVersionList(this);
this.fabric = new FabricVersionList(this);
this.fabricApi = new FabricAPIVersionList(this);
this.forge = new ForgeBMCLVersionList(apiRoot);
this.forge = new ForgeVersionList(this);
this.neoforge = new NeoForgeOfficialVersionList(this);
this.liteLoader = new LiteLoaderVersionList(this);
this.optifine = new OptiFineBMCLVersionList(apiRoot);
this.optifine = new OptiFine302VersionList("https://zkitefly.github.io/optifine-download-list/index.json");
this.quilt = new QuiltVersionList(this);
this.quiltApi = new QuiltAPIVersionList(this);
}

View File

@ -129,9 +129,14 @@ public abstract class VersionList<T extends RemoteVersion> {
lock.readLock().lock();
try {
T result = null;
for (T it : versions.get(gameVersion))
TreeSet<T> remoteVersions = versions.get(gameVersion);
for (T it : remoteVersions)
if (remoteVersion.equals(it.getSelfVersion()))
result = it;
if (result == null)
for (T it : remoteVersions)
if (remoteVersion.equals(it.getFullVersion()))
result = it;
return Optional.ofNullable(result);
} finally {
lock.readLock().unlock();

View File

@ -25,6 +25,7 @@ import static com.tungsten.fclcore.util.Pair.pair;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.tungsten.fclcore.download.VersionList;
import com.tungsten.fclcore.util.Lang;
import com.tungsten.fclcore.util.StringUtils;
import com.tungsten.fclcore.util.gson.Validation;
import com.tungsten.fclcore.util.io.HttpRequest;
@ -35,7 +36,10 @@ import org.jetbrains.annotations.Nullable;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
@ -64,10 +68,27 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
}
private static String toLookupVersion(String gameVersion) {
return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion;
}
private static String fromLookupVersion(String lookupVersion) {
return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion;
}
private static String toLookupBranch(String gameVersion, String branch) {
if ("1.7.10-pre4".equals(gameVersion)) {
return "prerelease";
}
return Lang.requireNonNullElse(branch, "");
}
@Override
public CompletableFuture<?> refreshAsync(String gameVersion) {
String lookupVersion = toLookupVersion(gameVersion);
return CompletableFuture.completedFuture(null)
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + gameVersion).<List<ForgeVersion>>getJson(new TypeToken<List<ForgeVersion>>() {
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + lookupVersion).<List<ForgeVersion>>getJson(new TypeToken<List<ForgeVersion>>() {
}.getType())))
.thenAcceptAsync(forgeVersions -> {
lock.writeLock().lock();
@ -81,16 +102,17 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
List<String> urls = new ArrayList<>();
for (ForgeVersion.File file : version.getFiles())
if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) {
String classifier = gameVersion + "-" + version.getVersion()
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
String branch = toLookupBranch(gameVersion, version.getBranch());
String classifier = lookupVersion + "-" + version.getVersion() + (branch.isEmpty() ? "" : '-' + branch);
String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat();
String fileName2 = "forge-" + classifier + "-" + gameVersion + "-" + file.getCategory() + "." + file.getFormat();
String fileName2 = "forge-" + classifier + "-" + lookupVersion + "-" + file.getCategory() + "." + file.getFormat();
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1);
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + lookupVersion + "/" + fileName2);
urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf(
pair("mcversion", version.getGameVersion()),
pair("version", version.getVersion()),
pair("branch", version.getBranch()),
pair("branch", branch),
pair("category", file.getCategory()),
pair("format", file.getFormat())
)));
@ -109,7 +131,7 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
}
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), releaseDate, urls));
fromLookupVersion(version.getGameVersion()), version.getVersion(), releaseDate, urls));
}
} finally {
lock.writeLock().unlock();

View File

@ -27,7 +27,7 @@ public final class ForgeVersion implements Validation {
private final String jobver;
private final String version;
private final int build;
private final double modified;
private final String modified;
private final String[][] files;
/**
@ -35,10 +35,10 @@ public final class ForgeVersion implements Validation {
*/
@SuppressWarnings("unused")
public ForgeVersion() {
this(null, null, null, null, 0, 0, null);
this(null, null, null, null, 0, null, null);
}
public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, double modified, String[][] files) {
public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, String modified, String[][] files) {
this.branch = branch;
this.mcversion = mcversion;
this.jobver = jobver;
@ -68,7 +68,7 @@ public final class ForgeVersion implements Validation {
return build;
}
public double getModified() {
public String getModified() {
return modified;
}

View File

@ -23,9 +23,13 @@ import com.tungsten.fclcore.util.StringUtils;
import com.tungsten.fclcore.util.io.HttpRequest;
import com.tungsten.fclcore.util.versioning.VersionNumber;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import static com.tungsten.fclcore.util.Logging.LOG;
public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
private final DownloadProvider downloadProvider;
@ -39,9 +43,17 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
return false;
}
private static String toLookupVersion(String gameVersion) {
return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion;
}
private static String fromLookupVersion(String lookupVersion) {
return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion;
}
@Override
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(downloadProvider.injectURL(FORGE_LIST)).getJsonAsync(ForgeVersionRoot.class)
return HttpRequest.GET(FORGE_LIST).getJsonAsync(ForgeVersionRoot.class)
.thenAcceptAsync(root -> {
lock.writeLock().lock();
@ -51,7 +63,7 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
versions.clear();
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
String gameVersion = VersionNumber.normalize(entry.getKey());
String gameVersion = fromLookupVersion(VersionNumber.normalize(entry.getKey()));
for (int v : entry.getValue()) {
ForgeVersion version = root.getNumber().get(v);
if (version == null)
@ -67,8 +79,19 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
if (jar == null)
continue;
Instant releaseDate = null;
if (version.getModified() != null) {
try {
long timestamp = Long.parseLong(version.getModified());
releaseDate = Instant.ofEpochSecond(timestamp);
} catch (NumberFormatException e) {
LOG.log(Level.WARNING, "Failed to parse instant " + version.getModified(), e);
}
}
versions.put(gameVersion, new ForgeRemoteVersion(
version.getGameVersion(), version.getVersion(), null, Collections.singletonList(jar)
toLookupVersion(version.getGameVersion()), version.getVersion(), releaseDate, Collections.singletonList(jar)
));
}
}
@ -78,5 +101,5 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
});
}
public static final String FORGE_LIST = "https://files.minecraftforge.net/maven/net/minecraftforge/forge/json";
}
public static final String FORGE_LIST = "https://zkitefly.github.io/forge-maven-metadata/list.json";
}

View File

@ -0,0 +1,94 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.tungsten.fclcore.download.optifine;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.tungsten.fclcore.download.VersionList;
import com.tungsten.fclcore.util.io.HttpRequest;
import com.tungsten.fclcore.util.versioning.VersionNumber;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
* @author huangyuhui
*/
public final class OptiFine302VersionList extends VersionList<OptiFineRemoteVersion> {
private final String versionListURL;
public OptiFine302VersionList(String versionListURL) {
this.versionListURL = versionListURL;
}
@Override
public boolean hasType() {
return true;
}
@Override
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(versionListURL).<OptiFine302VersionList.VersionList>getJsonAsync(new TypeToken<OptiFine302VersionList.VersionList>() {
}.getType()).thenAcceptAsync(root -> {
lock.writeLock().lock();
try {
versions.clear();
for (OptiFineVersion element : root.versions) {
String gameVersion = VersionNumber.normalize(element.gameVersion);
versions.put(gameVersion, new OptiFineRemoteVersion(
gameVersion, element.version,
root.downloadBases.stream().map(u -> u + element.fileName).collect(Collectors.toList()),
element.fileName.startsWith("pre")
));
}
} finally {
lock.writeLock().unlock();
}
});
}
private static final class VersionList {
@SerializedName("file")
private final List<OptiFineVersion> versions;
@SerializedName("download")
private final List<String> downloadBases;
public VersionList(List<OptiFineVersion> versions, List<String> downloadBases) {
this.versions = versions;
this.downloadBases = downloadBases;
}
}
private static final class OptiFineVersion {
@SerializedName("name")
private final String version;
@SerializedName("filename")
private final String fileName;
@SerializedName("mcversion")
private final String gameVersion;
public OptiFineVersion(String version, String fileName, String gameVersion) {
this.version = version;
this.fileName = fileName;
this.gameVersion = gameVersion;
}
}
}

View File

@ -17,6 +17,7 @@
*/
package com.tungsten.fclcore.download.optifine;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.tungsten.fclcore.download.VersionList;
import com.tungsten.fclcore.util.StringUtils;
@ -47,31 +48,46 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
@Override
public CompletableFuture<?> refreshAsync() {
return HttpRequest.GET(apiRoot + "/optifine/versionlist").<List<OptiFineVersion>>getJsonAsync(new TypeToken<List<OptiFineVersion>>() {
}.getType())
.thenAcceptAsync(root -> {
lock.writeLock().lock();
}.getType()).thenAcceptAsync(root -> {
lock.writeLock().lock();
try {
versions.clear();
Set<String> duplicates = new HashSet<>();
for (OptiFineVersion element : root) {
String version = element.getType() + "_" + element.getPatch();
String mirror = "https://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch();
if (!duplicates.add(mirror))
continue;
try {
versions.clear();
Set<String> duplicates = new HashSet<>();
for (OptiFineVersion element : root) {
String version = element.type + "_" + element.patch;
String mirror = apiRoot + "/optifine/" + element.gameVersion + "/" + element.type + "/" + element.patch;
if (!duplicates.add(mirror))
continue;
boolean isPre = element.getPatch() != null && (element.getPatch().startsWith("pre") || element.getPatch().startsWith("alpha"));
boolean isPre = element.patch != null && (element.patch.startsWith("pre") || element.patch.startsWith("alpha"));
if (StringUtils.isBlank(element.getGameVersion()))
continue;
if (StringUtils.isBlank(element.gameVersion))
continue;
String gameVersion = VersionNumber.normalize(element.getGameVersion());
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre));
}
} finally {
lock.writeLock().unlock();
}
});
String gameVersion = VersionNumber.normalize(element.gameVersion);
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre));
}
} finally {
lock.writeLock().unlock();
}
});
}
}
private static final class OptiFineVersion {
@SerializedName("type")
private final String type;
@SerializedName("patch")
private final String patch;
@SerializedName("mcversion")
private final String gameVersion;
public OptiFineVersion(String type, String patch, String gameVersion) {
this.type = type;
this.patch = patch;
this.gameVersion = gameVersion;
}
}
}

View File

@ -31,6 +31,11 @@ public class OptiFineRemoteVersion extends RemoteVersion {
super(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), gameVersion, selfVersion, null, snapshot ? Type.SNAPSHOT : Type.RELEASE, urls);
}
@Override
public String getFullVersion() {
return getGameVersion() + "_" + getSelfVersion();
}
@Override
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
return new OptiFineInstallTask(dependencyManager, baseVersion, this);

View File

@ -1,86 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.tungsten.fclcore.download.optifine;
import com.google.gson.annotations.SerializedName;
public final class OptiFineVersion {
@SerializedName("dl")
private final String downloadLink;
@SerializedName("ver")
private final String version;
@SerializedName("date")
private final String date;
@SerializedName("type")
private final String type;
@SerializedName("patch")
private final String patch;
@SerializedName("mirror")
private final String mirror;
@SerializedName("mcversion")
private final String gameVersion;
public OptiFineVersion() {
this(null, null, null, null, null, null, null);
}
public OptiFineVersion(String downloadLink, String version, String date, String type, String patch, String mirror, String gameVersion) {
this.downloadLink = downloadLink;
this.version = version;
this.date = date;
this.type = type;
this.patch = patch;
this.mirror = mirror;
this.gameVersion = gameVersion;
}
public String getDownloadLink() {
return downloadLink;
}
public String getVersion() {
return version;
}
public String getDate() {
return date;
}
public String getType() {
return type;
}
public String getPatch() {
return patch;
}
public String getMirror() {
return mirror;
}
public String getGameVersion() {
return gameVersion;
}
}

View File

@ -48,6 +48,10 @@ public final class AssetIndex {
return virtual || mapToResources;
}
public boolean needMapToResources() {
return mapToResources;
}
public Map<String, AssetObject> getObjects() {
return Collections.unmodifiableMap(objects);
}

View File

@ -23,14 +23,7 @@ import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.tungsten.fclcore.download.MaintainTask;
import com.tungsten.fclcore.download.game.VersionJsonSaveTask;
import com.tungsten.fclcore.event.Event;
import com.tungsten.fclcore.event.EventBus;
import com.tungsten.fclcore.event.GameJsonParseFailedEvent;
import com.tungsten.fclcore.event.LoadedOneVersionEvent;
import com.tungsten.fclcore.event.RefreshedVersionsEvent;
import com.tungsten.fclcore.event.RefreshingVersionsEvent;
import com.tungsten.fclcore.event.RemoveVersionEvent;
import com.tungsten.fclcore.event.RenameVersionEvent;
import com.tungsten.fclcore.event.*;
import com.tungsten.fclcore.game.tlauncher.TLauncherVersion;
import com.tungsten.fclcore.mod.ModManager;
import com.tungsten.fclcore.mod.ModpackConfiguration;
@ -47,13 +40,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.stream.Stream;
@ -125,9 +112,12 @@ public class DefaultGameRepository implements GameRepository {
@Override
public File getRunDirectory(String id) {
switch (getGameDirectoryType(id)) {
case VERSION_FOLDER: return getVersionRoot(id);
case ROOT_FOLDER: return getBaseDirectory();
default: throw new IllegalStateException();
case VERSION_FOLDER:
return getVersionRoot(id);
case ROOT_FOLDER:
return getBaseDirectory();
default:
throw new IllegalStateException();
}
}
@ -448,6 +438,8 @@ public class DefaultGameRepository implements GameRepository {
return assetsDir;
if (index.isVirtual()) {
Path resourcesDir = getRunDirectory(version).toPath().resolve("resources");
int cnt = 0;
int tot = index.getObjects().entrySet().size();
for (Map.Entry<String, AssetObject> entry : index.getObjects().entrySet()) {
@ -457,6 +449,12 @@ public class DefaultGameRepository implements GameRepository {
cnt++;
if (!Files.isRegularFile(target))
FileUtils.copyFile(original, target);
if (index.needMapToResources()) {
target = resourcesDir.resolve(entry.getKey());
if (!Files.isRegularFile(target))
FileUtils.copyFile(original, target);
}
}
}
@ -489,18 +487,20 @@ public class DefaultGameRepository implements GameRepository {
/**
* read modpack configuration for a version.
*
* @param version version installed as modpack
* @param <M> manifest type of ModpackConfiguration
* @param <M> manifest type of ModpackConfiguration
* @return modpack configuration object, or null if this version is not a modpack.
* @throws VersionNotFoundException if version does not exist.
* @throws IOException if an i/o error occurs.
* @throws IOException if an i/o error occurs.
*/
@Nullable
public <M> ModpackConfiguration<M> readModpackConfiguration(String version) throws IOException, VersionNotFoundException {
if (!hasVersion(version)) throw new VersionNotFoundException(version);
File file = getModpackConfiguration(version);
if (!file.exists()) return null;
return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<M>>(){}.getType());
return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<M>>() {
}.getType());
}
public boolean isModpack(String version) {

View File

@ -17,7 +17,6 @@
*/
package com.tungsten.fclcore.game;
import static com.tungsten.fclcore.util.Lang.tryCast;
import static com.tungsten.fclcore.util.Logging.LOG;
import com.google.gson.JsonParseException;
@ -47,8 +46,11 @@ public final class GameVersion {
private static Optional<String> getVersionFromJson(InputStream versionJson) {
try {
Map<?, ?> version = JsonUtils.fromNonNullJsonFully(versionJson, Map.class);
return tryCast(version.get("id"), String.class);
} catch (IOException | JsonParseException e) {
String id = (String) version.get("id");
if (id != null && id.contains(" / "))
id = id.substring(0, id.indexOf(" / "));
return Optional.ofNullable(id);
} catch (IOException | JsonParseException | ClassCastException e) {
LOG.log(Level.WARNING, "Failed to parse version.json", e);
return Optional.empty();
}

View File

@ -94,6 +94,7 @@ public abstract class FetchTask<T> extends Task<T> {
break download;
}
List<String> redirects = null;
try {
beforeDownload(url);
@ -103,7 +104,9 @@ public abstract class FetchTask<T> extends Task<T> {
if (checkETag) repository.injectConnection(conn);
if (conn instanceof HttpURLConnection) {
conn = NetworkUtils.resolveConnection((HttpURLConnection) conn);
redirects = new ArrayList<>();
conn = NetworkUtils.resolveConnection((HttpURLConnection) conn, redirects);
int responseCode = ((HttpURLConnection) conn).getResponseCode();
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
@ -164,13 +167,13 @@ public abstract class FetchTask<T> extends Task<T> {
} catch (FileNotFoundException ex) {
failedURL = url;
exception = ex;
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", not found", ex);
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", not found" + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
break; // we will not try this URL again
} catch (IOException ex) {
failedURL = url;
exception = ex;
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (++repeat), ex);
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (++repeat) + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
}
}
}

View File

@ -294,8 +294,12 @@ public class CacheRepository {
ETagIndex indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), ETagIndex.class);
Map<String, ETagItem> newIndex = joinETagIndexes(indexOnDisk == null ? null : indexOnDisk.eTag, index.values());
channel.truncate(0);
ETagIndex writeTo = new ETagIndex(newIndex.values());
channel.write(ByteBuffer.wrap(JsonUtils.GSON.toJson(writeTo).getBytes(UTF_8)));
ByteBuffer writeTo = ByteBuffer.wrap(JsonUtils.GSON.toJson(new ETagIndex(newIndex.values())).getBytes(UTF_8));
while (writeTo.hasRemaining()) {
if (channel.write(writeTo) == 0) {
throw new IOException("No value is written");
}
}
this.index = newIndex;
} finally {
lock.release();

View File

@ -144,6 +144,10 @@ public final class NetworkUtils {
return sb.toString();
}
public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws IOException {
return resolveConnection(conn, null);
}
/**
* This method is a work-around that aims to solve problem when "Location" in
* stupid server's response is not encoded.
@ -153,7 +157,7 @@ public final class NetworkUtils {
* @return manually redirected http connection.
* @throws IOException if an I/O error occurs.
*/
public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws IOException {
public static HttpURLConnection resolveConnection(HttpURLConnection conn, List<String> redirects) throws IOException {
int redirect = 0;
while (true) {
@ -168,6 +172,9 @@ public final class NetworkUtils {
String newURL = conn.getHeaderField("Location");
conn.disconnect();
if (redirects != null) {
redirects.add(newURL);
}
if (redirect > 20) {
throw new IOException("Too much redirects");
}

View File

@ -1,9 +1,9 @@
[
{
"type": "release",
"versionCode": 1175,
"versionName": "1.1.7.5",
"date": "2024.08.16",
"versionCode": 1176,
"versionName": "1.1.7.6",
"date": "2024.08.23",
"description": [
{
"lang": "en",
@ -11,10 +11,10 @@
},
{
"lang": "zh_CN",
"text": "FCL 1.1.7.5更新内容(若启动器内下载缓慢请使用网盘下载,64位的请下载arm64不知道该下哪个的请下载all\n1.更新赞助链接\n2.更新turnip驱动\n3.允许使用外部应用选择文件\n4.移除xhook改为bytehook\n5.修复少量bug"
"text": "FCL 1.1.7.6更新内容(若启动器内下载缓慢请使用网盘下载,64位的请下载arm64不知道该下哪个的请下载all\n1.修复微软登录时的错误提示\n2.修复neoforge整合包版本错误\n3.修复手动选择整合包时的整合包为非正常格式时无法安装的bug\n4.可手动选择执行jar文件使用的java\n5.同步部分HMCL Core修复些许bug"
}
],
"netdiskUrl": "https://pan.quark.cn/s/b65dd99d5555",
"url": "https://github.com/FCL-Team/FoldCraftLauncher/releases/download/1.1.7.5/FCL-release-1.1.7.5-all.apk"
"netdiskUrl": "https://pan.quark.cn/s/04bc07022a7d",
"url": "https://github.com/FCL-Team/FoldCraftLauncher/releases/download/1.1.7.6/FCL-release-1.1.7.6-all.apk"
}
]