From ee3435d52af6e23fad40d68d865b837e67b9a30a Mon Sep 17 00:00:00 2001 From: artdeell Date: Tue, 4 Feb 2025 15:20:46 +0300 Subject: [PATCH] Feat[launcher]: add support for the "Better than Adventure!" mod --- .../main/java/net/kdt/pojavlaunch/Tools.java | 2 +- .../fragments/BTAInstallFragment.java | 57 +++++++++ .../fragments/ProfileTypeSelectFragment.java | 2 + .../modloaders/BTADownloadTask.java | 97 ++++++++++++++++ .../kdt/pojavlaunch/modloaders/BTAUtils.java | 109 ++++++++++++++++++ .../modloaders/BTAVersionListAdapter.java | 95 +++++++++++++++ .../main/res/layout/fragment_profile_type.xml | 8 ++ .../src/main/res/values/strings.xml | 4 + .../src/main/java/org/lwjgl/glfw/GLFW.java | 6 + 9 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/BTAInstallFragment.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTADownloadTask.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAUtils.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAVersionListAdapter.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index a66c61ed1b..ff41d45793 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -327,7 +327,7 @@ public static void launchMinecraft(final AppCompatActivity activity, MinecraftAc } javaArgList.addAll(Arrays.asList(getMinecraftJVMArgs(versionId, gamedir))); javaArgList.add("-cp"); - javaArgList.add(getLWJGL3ClassPath() + ":" + launchClassPath); + javaArgList.add(launchClassPath + ":" + getLWJGL3ClassPath()); javaArgList.add(versionInfo.mainClass); javaArgList.addAll(Arrays.asList(launchArgs)); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/BTAInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/BTAInstallFragment.java new file mode 100644 index 0000000000..8033e13c84 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/BTAInstallFragment.java @@ -0,0 +1,57 @@ +package net.kdt.pojavlaunch.fragments; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.ExpandableListAdapter; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.modloaders.BTADownloadTask; +import net.kdt.pojavlaunch.modloaders.BTAUtils; +import net.kdt.pojavlaunch.modloaders.BTAVersionListAdapter; +import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy; + +import java.io.File; +import java.io.IOException; + +public class BTAInstallFragment extends ModVersionListFragment { + private static ModloaderListenerProxy sTaskProxy; + @Override + public int getTitleText() { + return R.string.select_bta_version; + } + + @Override + public int getNoDataMsg() { + return R.string.modloader_dl_failed_to_load_list; + } + + @Override + public ModloaderListenerProxy getTaskProxy() { + return sTaskProxy; + } + + @Override + public BTAUtils.BTAVersionList loadVersionList() throws IOException { + return BTAUtils.downloadVersionList(); + } + + @Override + public void setTaskProxy(ModloaderListenerProxy proxy) { + sTaskProxy = proxy; + } + + @Override + public ExpandableListAdapter createAdapter(BTAUtils.BTAVersionList versionList, LayoutInflater layoutInflater) { + return new BTAVersionListAdapter(versionList, layoutInflater); + } + + @Override + public Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy) { + return new BTADownloadTask(listenerProxy, (BTAUtils.BTAVersion) selectedVersion); + } + + @Override + public void onDownloadFinished(Context context, File downloadedFile) { + // We don't have to do anything after the BTADownloadTask ends, so this is a stub + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java index 32be87bb46..9e409a41e1 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java @@ -35,5 +35,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Tools.swapFragment(requireActivity(), SearchModFragment.class, SearchModFragment.TAG, null)); view.findViewById(R.id.modded_profile_quilt).setOnClickListener((v)-> Tools.swapFragment(requireActivity(), QuiltInstallFragment.class, QuiltInstallFragment.TAG, null)); + view.findViewById(R.id.modded_profile_bta).setOnClickListener((v)-> + Tools.swapFragment(requireActivity(), BTAInstallFragment.class, BTAInstallFragment.TAG, null)); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTADownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTADownloadTask.java new file mode 100644 index 0000000000..bd49c71e9e --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTADownloadTask.java @@ -0,0 +1,97 @@ +package net.kdt.pojavlaunch.modloaders; + +import android.util.Base64; +import android.util.Base64OutputStream; +import android.util.Log; + +import com.kdt.mcgui.ProgressLayout; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; +import net.kdt.pojavlaunch.utils.DownloadUtils; +import net.kdt.pojavlaunch.utils.FileUtils; +import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; +import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class BTADownloadTask implements Runnable { + private static final String BASE_JSON = "{\"inheritsFrom\":\"b1.7.3\",\"mainClass\":\"net.minecraft.client.Minecraft\",\"libraries\":[{\"name\":\"bta-client:bta-client:%1$s\",\"downloads\":{\"artifact\":{\"path\":\"bta-client/bta-client-%1$s.jar\",\"url\":\"%2$s\"}}}],\"id\":\"%3$s\"}"; + private final ModloaderDownloadListener mListener; + private final BTAUtils.BTAVersion mBtaVersion; + + public BTADownloadTask(ModloaderDownloadListener mListener, BTAUtils.BTAVersion mBtaVersion) { + this.mListener = mListener; + this.mBtaVersion = mBtaVersion; + } + + @Override + public void run() { + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.fabric_dl_progress, "BTA"); + try { + runCatching() ; + mListener.onDownloadFinished(null); + }catch (IOException e) { + mListener.onDownloadError(e); + } + ProgressLayout.clearProgress(ProgressLayout.INSTALL_MODPACK); + } + + private String tryDownloadIcon() { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + try (Base64OutputStream base64OutputStream = new Base64OutputStream(byteArrayOutputStream, Base64.DEFAULT)){ + // Instead of appending and wasting memory with a StringBuilder, just write the prefix + // to the stream before the base64 icon data. + byteArrayOutputStream.write("data:image/png;base64,".getBytes(StandardCharsets.US_ASCII)); + DownloadUtils.download(mBtaVersion.iconUrl, base64OutputStream); + return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.US_ASCII); + }catch (IOException e) { + Log.w("BTADownloadTask", "Failed to download base64 icon", e); + }finally { + try { + byteArrayOutputStream.close(); + } catch (IOException e) { + Log.wtf("BTADownloadTask", "Failed to close a byte array stream??", e); + } + } + return null; + } + + private void createJson(String btaVersionId) throws IOException { + String btaJson = String.format(BASE_JSON, mBtaVersion.versionName, mBtaVersion.downloadUrl, btaVersionId); + File jsonDir = new File(Tools.DIR_HOME_VERSION, btaVersionId); + File jsonFile = new File(jsonDir, btaVersionId+".json"); + FileUtils.ensureDirectory(jsonDir); + Tools.write(jsonFile.getAbsolutePath(), btaJson); + } + + // BTA doesn't have SHA1 checksums in its repositories, so the user may try to reinstall it + // if it didn't work due to a broken download. So, for reinstalls like that to work, + // we need to delete the old client jar to force the download of a new one. + private void removeOldClient() throws IOException{ + File btaClientPath = new File(Tools.DIR_HOME_LIBRARY, String.format("bta-client/bta-client-%1$s.jar", mBtaVersion.versionName)); + if(btaClientPath.exists() && !btaClientPath.delete()) + throw new IOException("Failed to delete old client jar"); + } + + private void createProfile(String btaVersionId) throws IOException { + LauncherProfiles.load(); + MinecraftProfile btaProfile = new MinecraftProfile(); + btaProfile.lastVersionId = btaVersionId; + btaProfile.name = "Better than Adventure!"; + btaProfile.icon = tryDownloadIcon(); + LauncherProfiles.insertMinecraftProfile(btaProfile); + LauncherProfiles.write(); + } + + public void runCatching() throws IOException { + removeOldClient(); + String btaVersionId = "bta-"+mBtaVersion.versionName; + createJson(btaVersionId); + createProfile(btaVersionId); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAUtils.java new file mode 100644 index 0000000000..db567579de --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAUtils.java @@ -0,0 +1,109 @@ +package net.kdt.pojavlaunch.modloaders; + +import android.util.Log; + +import androidx.annotation.Keep; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; + +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.utils.DownloadUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +public class BTAUtils { + private static final String BTA_CLIENT_URL = "https://downloads.betterthanadventure.net/bta-client/release/%s/client.jar"; + private static final String BTA_ICON_URL = "https://downloads.betterthanadventure.net/bta-client/release/%s/auto/%s.png"; + private static final List BTA_TESTED_VERSIONS = new ArrayList<>(); + static { + BTA_TESTED_VERSIONS.add("v7.3"); + BTA_TESTED_VERSIONS.add("v7.2_01"); + BTA_TESTED_VERSIONS.add("v7.2"); + BTA_TESTED_VERSIONS.add("v7.1_01"); + BTA_TESTED_VERSIONS.add("v7.1"); + } + + private static String getIconUrl(String version) { + String versionUnderscore = version.replace('.','_'); + return String.format(BTA_ICON_URL, version, versionUnderscore); + } + + private static List createVersionList(List versionStrings) { + ListIterator iterator = versionStrings.listIterator(versionStrings.size()); + ArrayList btaVersions = new ArrayList<>(versionStrings.size()); + while(iterator.hasPrevious()) { + String version = iterator.previous(); + btaVersions.add(new BTAVersion( + version, + String.format(BTA_CLIENT_URL, version), + getIconUrl(version) + )); + } + btaVersions.trimToSize(); + return btaVersions; + } + + private static BTAVersionList processReleasesJson(String releasesInfo) throws JsonParseException { + BTAVersionsManifest manifest = Tools.GLOBAL_GSON.fromJson(releasesInfo, BTAVersionsManifest.class); + List stringVersions = manifest.versions; + List testedVersions = new ArrayList<>(); + List untestedVersions = new ArrayList<>(); + for(String version : stringVersions) { + if(version == null) break; + if(BTA_TESTED_VERSIONS.contains(version)) { + testedVersions.add(version); + }else { + untestedVersions.add(version); + } + } + + return new BTAVersionList( + createVersionList(testedVersions), + createVersionList(untestedVersions) + ); + } + + public static BTAVersionList downloadVersionList() throws IOException { + try { + return DownloadUtils.downloadStringCached( + "https://downloads.betterthanadventure.net/bta-client/release/versions.json", + "bta_releases", BTAUtils::processReleasesJson); + }catch (DownloadUtils.ParseException e) { + Log.e("BTAUtils", "Failed to process json", e); + return null; + } + } + + private static class BTAVersionsManifest { + @Keep + public List versions; + @Keep + @SerializedName("default") + public String defaultVersion; + } + + public static class BTAVersion { + public final String versionName; + public final String downloadUrl; + public final String iconUrl; + + public BTAVersion(String versionName, String downloadUrl, String iconUrl) { + this.versionName = versionName; + this.downloadUrl = downloadUrl; + this.iconUrl = iconUrl; + } + } + public static class BTAVersionList { + public final List testedVersions; + public final List untestedVersions; + + public BTAVersionList(List mTestedVersions, List mUntestedVersions) { + this.testedVersions = mTestedVersions; + this.untestedVersions = mUntestedVersions; + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAVersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAVersionListAdapter.java new file mode 100644 index 0000000000..84c3c23935 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/BTAVersionListAdapter.java @@ -0,0 +1,95 @@ +package net.kdt.pojavlaunch.modloaders; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListAdapter; +import android.widget.TextView; + +import net.kdt.pojavlaunch.R; + +import java.util.ArrayList; +import java.util.List; + +public class BTAVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter{ + private final LayoutInflater mLayoutInflater; + private final ArrayList mGroupNames; + private final ArrayList> mGroups; + + public BTAVersionListAdapter(BTAUtils.BTAVersionList versionList, LayoutInflater mLayoutInflater) { + this.mLayoutInflater = mLayoutInflater; + Context context = mLayoutInflater.getContext(); + mGroupNames = new ArrayList<>(2); + mGroups = new ArrayList<>(2); + if(!versionList.testedVersions.isEmpty()) { + mGroupNames.add(context.getString(R.string.bta_installer_available_versions)); + mGroups.add(versionList.testedVersions); + } + if(!versionList.untestedVersions.isEmpty()) { + mGroupNames.add(context.getString(R.string.bta_installer_untested_versions)); + mGroups.add(versionList.untestedVersions); + } + mGroupNames.trimToSize(); + mGroups.trimToSize(); + } + + @Override + public int getGroupCount() { + return mGroups.size(); + } + + @Override + public int getChildrenCount(int i) { + return mGroups.get(i).size(); + } + + @Override + public Object getGroup(int i) { + return mGroupNames.get(i); + } + + @Override + public Object getChild(int i, int i1) { + return mGroups.get(i).get(i1); + } + + @Override + public long getGroupId(int i) { + return i; + } + + @Override + public long getChildId(int i, int i1) { + return i1; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getGroupView(int i, boolean b, View convertView, ViewGroup viewGroup) { + if(convertView == null) + convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false); + + ((TextView) convertView).setText((String)getGroup(i)); + + return convertView; + } + + @Override + public View getChildView(int i, int i1, boolean b, View convertView, ViewGroup viewGroup) { + if(convertView == null) + convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false); + ((TextView) convertView).setText(((BTAUtils.BTAVersion)getChild(i,i1)).versionName); + return convertView; + } + + @Override + public boolean isChildSelectable(int i, int i1) { + return true; + } +} diff --git a/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml b/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml index 84222998f1..5ada83d777 100644 --- a/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml +++ b/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml @@ -111,6 +111,14 @@ android:layout_marginTop="@dimen/padding_large" android:text="@string/modpack_install_button" /> + + Unsuitable username The username must be between 3–16 characters long, and must only contain latin letters, numbers 0–9 and underscores. Quick settings + Create \"Better than Adventure!\" profile + Select \"Better than Adventure!\" version + Supported BTA versions + Untested BTA versions diff --git a/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java b/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java index 53ea33596a..f0b877f5e8 100644 --- a/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java +++ b/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java @@ -1045,6 +1045,12 @@ public static void glfwShowWindow(long window) { nglfwSetShowingWindow(window); } + public static void glfwHideWindow(long window) { + GLFWWindowProperties win = internalGetWindow(window); + win.windowAttribs.put(GLFW_HOVERED, 0); + win.windowAttribs.put(GLFW_VISIBLE, 0); + } + public static void glfwWindowHint(int hint, int value) { if (hint == GLFW_VISIBLE) { mGLFWWindowVisibleOnCreation = value == GLFW_TRUE;