From 3d7cf8b3adb33e8dac18deb63d932bcab02848c1 Mon Sep 17 00:00:00 2001 From: alexytomi <60690056+alexytomi@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:49:45 +0800 Subject: [PATCH] qol[ProfileType]: Add NeoForge install button I don't know how it manages to make a profile but it does --- .../fragments/NeoForgeInstallFragment.java | 80 ++++++++++++ .../fragments/ProfileTypeSelectFragment.java | 2 + .../modloaders/NeoForgeDownloadTask.java | 88 +++++++++++++ .../NeoForgeVersionListAdapter.java | 119 ++++++++++++++++++ .../main/res/layout/fragment_profile_type.xml | 8 ++ .../src/main/res/values/strings.xml | 3 + 6 files changed, 300 insertions(+) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java new file mode 100644 index 000000000..b901da6e8 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java @@ -0,0 +1,80 @@ +package net.kdt.pojavlaunch.fragments; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.widget.ExpandableListAdapter; + +import androidx.annotation.NonNull; + +import net.kdt.pojavlaunch.JavaGUILauncherActivity; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.modloaders.ForgeDownloadTask; +import net.kdt.pojavlaunch.modloaders.ForgeUtils; +import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy; +import net.kdt.pojavlaunch.modloaders.NeoForgeDownloadTask; +import net.kdt.pojavlaunch.modloaders.NeoForgeVersionListAdapter; +import net.kdt.pojavlaunch.utils.DownloadUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public class NeoForgeInstallFragment extends ModVersionListFragment> { + public static final String TAG = "NeoForgeInstallFragment"; + public NeoForgeInstallFragment() { + super(TAG); + } + + private static final String NEOFORGE_METADATA_URL = "https://meta.prismlauncher.org/v1/net.neoforged/index.json"; + + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + } + + @Override + public int getTitleText() { + return R.string.neoforge_dl_select_version; + } + + @Override + public int getNoDataMsg() { + return R.string.neoforge_dl_no_installer; + } + + @Override + public List loadVersionList() { + String test = null; + try { + test = DownloadUtils.downloadStringCached(NEOFORGE_METADATA_URL, "neoforge_versions", input -> input); + } catch (Exception e) { + Tools.showErrorRemote(e); + } + return Collections.singletonList(test); + // Moved the parsing logic to the adapter because there is no way to get this info easily, we use prism's index + // since neoforge doesn't actually give this information easily anywhere. + // To clarify, neoforge does not provide maven APIs to get supported Minecraft versions for each loader version + + } + + @Override + public ExpandableListAdapter createAdapter(List versionList, LayoutInflater layoutInflater) { + return new NeoForgeVersionListAdapter(versionList, layoutInflater); + } + + @Override + public Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy) { + return new NeoForgeDownloadTask(listenerProxy, (String) selectedVersion); + } + + @Override + public void onDownloadFinished(Context context, File downloadedFile) { + Intent modInstallerStartIntent = new Intent(context, JavaGUILauncherActivity.class); + modInstallerStartIntent.putExtra("javaArgs", "-jar "+downloadedFile.getAbsolutePath()+" --install-client"); + context.startActivity(modInstallerStartIntent); + } +} 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 9a82ccb9f..9acb9d15a 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 @@ -36,6 +36,8 @@ public class ProfileTypeSelectFragment extends Fragment { tryInstall(FabricInstallFragment.class, FabricInstallFragment.TAG)); view.findViewById(R.id.modded_profile_forge).setOnClickListener((v)-> tryInstall(ForgeInstallFragment.class, ForgeInstallFragment.TAG)); + view.findViewById(R.id.modded_profile_neoforge).setOnClickListener((v)-> + tryInstall(NeoForgeInstallFragment.class, NeoForgeInstallFragment.TAG)); view.findViewById(R.id.modded_profile_modpack).setOnClickListener((v)-> tryInstall(SearchModFragment.class, SearchModFragment.TAG)); view.findViewById(R.id.modded_profile_quilt).setOnClickListener((v)-> diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java new file mode 100644 index 000000000..9cb40e683 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java @@ -0,0 +1,88 @@ +package net.kdt.pojavlaunch.modloaders; + +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 java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +public class NeoForgeDownloadTask implements Runnable, Tools.DownloaderFeedback { + private String mDownloadUrl; + private String mFullVersion; + private String mLoaderVersion; + private String mGameVersion; + private final ModloaderDownloadListener mListener; + public NeoForgeDownloadTask(ModloaderDownloadListener listener, String forgeVersion) { + this.mListener = listener; + this.mDownloadUrl = "https://maven.neoforged.net/releases/net/neoforged/neoforge/"+ forgeVersion +"/neoforge-"+forgeVersion+"-installer.jar"; + this.mFullVersion = forgeVersion; + } + + public NeoForgeDownloadTask(ModloaderDownloadListener listener, String gameVersion, String loaderVersion) { + this.mListener = listener; + this.mLoaderVersion = loaderVersion; + this.mGameVersion = gameVersion; + } + @Override + public void run() { + if(determineDownloadUrl()) { + downloadForge(); + } + ProgressLayout.clearProgress(ProgressLayout.INSTALL_MODPACK); + } + + @Override + public void updateProgress(int curr, int max) { + int progress100 = (int)(((float)curr / (float)max)*100f); + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.forge_dl_progress, mFullVersion); + } + + private void downloadForge() { + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_progress, mFullVersion); + try { + File destinationFile = new File(Tools.DIR_CACHE, "neoforge-installer.jar"); + byte[] buffer = new byte[8192]; + DownloadUtils.downloadFileMonitored(mDownloadUrl, destinationFile, buffer, this); + mListener.onDownloadFinished(destinationFile); + }catch (FileNotFoundException e) { + mListener.onDataNotAvailable(); + } catch (IOException e) { + mListener.onDownloadError(e); + } + } + + public boolean determineDownloadUrl() { + if(mDownloadUrl != null && mFullVersion != null) return true; + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_searching); + try { + if(!findVersion()) { + mListener.onDataNotAvailable(); + return false; + } + }catch (IOException e) { + mListener.onDownloadError(e); + return false; + } + return true; + } + + public boolean findVersion() throws IOException { + List forgeVersions = ForgeUtils.downloadForgeVersions(); + if(forgeVersions == null) return false; + String versionStart = mGameVersion+"-"+mLoaderVersion; + for(String versionName : forgeVersions) { + if(!versionName.startsWith(versionStart)) continue; + mFullVersion = versionName; + mDownloadUrl = ForgeUtils.getInstallerUrl(mFullVersion); + return true; + } + return false; + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java new file mode 100644 index 000000000..e02670329 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java @@ -0,0 +1,119 @@ +package net.kdt.pojavlaunch.modloaders; + +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 com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; + +public class NeoForgeVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter { + private final LayoutInflater mLayoutInflater; + private final LinkedHashMap> minecraftToLoaderVersionsHashmap; + private LinkedHashSet generatedHashSet = null; + + + public NeoForgeVersionListAdapter(List forgeVersions, LayoutInflater layoutInflater) { + this.mLayoutInflater = layoutInflater; + minecraftToLoaderVersionsHashmap = new LinkedHashMap<>(); + JsonArray versionsJsonArray = JsonParser.parseString(forgeVersions.get(0)).getAsJsonObject().getAsJsonArray("versions"); + + ArrayList sortedVersionsList = new ArrayList<>(); + for (JsonElement elem : versionsJsonArray) { + sortedVersionsList.add(elem); + } + Collections.sort(sortedVersionsList, (o1, o2) -> { + String versionString1 = ((JsonObject) o1).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + String versionString2 = ((JsonObject) o2).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + return versionString2.compareTo(versionString1); // Sorts by Minecraft version + }); + + for (JsonElement sortedVersionPick : sortedVersionsList) { + String loaderVersion = ((JsonObject) sortedVersionPick).get("version").getAsString(); + String minecraftVersion = ((JsonObject) sortedVersionPick).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + if (minecraftToLoaderVersionsHashmap.containsKey(minecraftVersion)) { + minecraftToLoaderVersionsHashmap.get(minecraftVersion).add(loaderVersion); + } else { + generatedHashSet = new LinkedHashSet<>(); + generatedHashSet.add(loaderVersion); + minecraftToLoaderVersionsHashmap.put(minecraftVersion, generatedHashSet); + } + } + } + @Override + public int getGroupCount() { + return minecraftToLoaderVersionsHashmap.size(); + } + + @Override + public int getChildrenCount(int i) { + return new ArrayList<>(minecraftToLoaderVersionsHashmap.values()).get(i).size(); + } + + @Override + public Object getGroup(int i) { + return getGameVersion(i); + } + + @Override + public Object getChild(int i, int i1) { + return getForgeVersion(i, 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(getGameVersion(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(getForgeVersion(i, i1)); + return convertView; + } + + private String getGameVersion(int i) { + return minecraftToLoaderVersionsHashmap.keySet().toArray()[i].toString(); + } + + private String getForgeVersion(int i, int i1){ + return new ArrayList<>(minecraftToLoaderVersionsHashmap.values()).get(i).toArray()[i1].toString(); + } + + @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 5ada83d77..5ce18cd93 100644 --- a/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml +++ b/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml @@ -103,6 +103,14 @@ android:layout_marginTop="@dimen/padding_large" android:text="@string/modloader_dl_install_forge" /> + + Please change accounts to use this function No Minecraft Account Found This feature requires a Microsoft account that owns Minecraft Java Edition. + Create Neoforge profile + Select NeoForge version + Sorry, but this version of NeoForge does not have an installer, which is not yet supported.