From cd8ed668b84fe97587a6fccddfa18730b2146ee9 Mon Sep 17 00:00:00 2001 From: tomikun <60690056+alexytomi@users.noreply.github.com> Date: Thu, 14 May 2026 00:02:03 +0800 Subject: [PATCH] [FIXME] Half-finished lwjgl3ify support Only missing popup that installs lwjgl3ify properly if detected in mods --- .../net/kdt/pojavlaunch/LauncherActivity.java | 1 + .../main/java/net/kdt/pojavlaunch/Tools.java | 22 +- .../fragments/LWJGL3ifyInstallFragment.java | 86 +++++++ .../fragments/MainMenuFragment.java | 12 +- .../fragments/ProfileTypeSelectFragment.java | 2 + .../modloaders/LWJGL3ifyDownloadTask.java | 225 ++++++++++++++++++ .../modloaders/LWJGL3ifyUtils.java | 154 ++++++++++++ .../LWJGL3ifyVersionListAdapter.java | 95 ++++++++ .../modpacks/api/CurseforgeApi.java | 39 ++- .../modloaders/modpacks/api/ModrinthApi.java | 43 +++- .../modloaders/modpacks/models/ModDetail.java | 22 +- .../main/res/layout/fragment_profile_type.xml | 8 + .../src/main/res/values/strings.xml | 4 + 13 files changed, 701 insertions(+), 12 deletions(-) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LWJGL3ifyInstallFragment.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyDownloadTask.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyUtils.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyVersionListAdapter.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java index 16ec0854d..57d9e380a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java @@ -36,6 +36,7 @@ import net.kdt.pojavlaunch.fragments.MicrosoftLoginFragment; import net.kdt.pojavlaunch.fragments.SelectAuthFragment; import net.kdt.pojavlaunch.lifecycle.ContextAwareDoneListener; import net.kdt.pojavlaunch.lifecycle.ContextExecutor; +import net.kdt.pojavlaunch.modloaders.LWJGL3ifyUtils; import net.kdt.pojavlaunch.modloaders.modpacks.ModloaderInstallTracker; import net.kdt.pojavlaunch.modloaders.modpacks.api.CommonApi; import net.kdt.pojavlaunch.modloaders.modpacks.api.ModLoader; 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 76239b10a..eb58110fd 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -280,15 +280,29 @@ public final class Tools { * @return Whether or not the .jar is found */ public static boolean hasMods(String... filenames) { + return !getMods(filenames).isEmpty(); + } + + /** + * Searches for mod in mods directory of current selected profile + * Not case-sensitive + * @param filenames Filename(s) of the .jar mod(s) + * @return The found mods + */ + public static List getMods(String... filenames) { File gameDir = getGameDir(); File modsDir = new File(gameDir, "mods"); - File[] modFiles = modsDir.listFiles(file -> file.isFile() && file.getName().endsWith(".jar")); - if (modFiles == null) return false; + File[] modFiles = modsDir.listFiles(file -> file.isFile() && file.getName().toLowerCase().endsWith(".jar")); + if (modFiles == null) return new ArrayList<>(); + List foundModFiles = new ArrayList<>(); for (File file : modFiles) { for (String filename : filenames) - if (file.getName().toLowerCase().contains(filename.toLowerCase())) return true; + if (file.getName().toLowerCase().contains(filename.toLowerCase())) { + foundModFiles.add(file); + break; + } } - return false; + return foundModFiles; } /** diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LWJGL3ifyInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LWJGL3ifyInstallFragment.java new file mode 100644 index 000000000..951bf9500 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LWJGL3ifyInstallFragment.java @@ -0,0 +1,86 @@ +package net.kdt.pojavlaunch.fragments; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.ExpandableListAdapter; + +import androidx.annotation.NonNull; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.modloaders.LWJGL3ifyDownloadTask; +import net.kdt.pojavlaunch.modloaders.LWJGL3ifyUtils; +import net.kdt.pojavlaunch.modloaders.LWJGL3ifyVersionListAdapter; +import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy; +import net.kdt.pojavlaunch.modloaders.modpacks.api.CommonApi; +import net.kdt.pojavlaunch.modloaders.modpacks.api.ModpackApi; + +import java.io.File; +import java.io.IOException; + +public class LWJGL3ifyInstallFragment extends ModVersionListFragment{ + public static final String TAG = "LWJGL3ifyInstallFragment"; + private ModpackApi modpackApi; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + modpackApi = new CommonApi(context.getString(R.string.curseforge_api_key)); + } + public LWJGL3ifyInstallFragment() { + super(TAG); + } + + /** + * @return + */ + @Override + public int getTitleText() { + return R.string.select_lwjgl3ify_version; + } + + /** + * @return + */ + @Override + public int getNoDataMsg() { + return R.string.modloader_dl_failed_to_load_list; + } + + /** + * @return + * @throws IOException + */ + @Override + public LWJGL3ifyUtils.LWJGL3ifyVersionList loadVersionList() throws IOException { + return LWJGL3ifyUtils.getLWJGL3ifyVersionList(modpackApi); + } + + /** + * @param versionList + * @param layoutInflater + * @return + */ + @Override + public ExpandableListAdapter createAdapter(LWJGL3ifyUtils.LWJGL3ifyVersionList versionList, LayoutInflater layoutInflater) { + return new LWJGL3ifyVersionListAdapter(versionList, layoutInflater); + } + + /** + * @param selectedVersion + * @param listenerProxy + * @return + */ + @Override + public Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy) { + return new LWJGL3ifyDownloadTask(listenerProxy, (LWJGL3ifyUtils.LWJGL3ifyMod) selectedVersion, requireActivity()); + } + + /** + * @param context + * @param downloadedFile + */ + @Override + public void onDownloadFinished(Context context, File downloadedFile) { + // Nothing to do. + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java index 3c55940a4..4d3bc17ec 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java @@ -1,6 +1,7 @@ package net.kdt.pojavlaunch.fragments; import static net.kdt.pojavlaunch.Tools.dialogOnUiThread; +import static net.kdt.pojavlaunch.Tools.hasMods; import static net.kdt.pojavlaunch.Tools.hasNoOnlineProfileDialog; import static net.kdt.pojavlaunch.Tools.hasOnlineProfile; import static net.kdt.pojavlaunch.Tools.openPath; @@ -27,12 +28,14 @@ import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.extra.ExtraConstants; import net.kdt.pojavlaunch.extra.ExtraCore; +import net.kdt.pojavlaunch.modloaders.LWJGL3ifyUtils; import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; import java.io.File; +import java.util.List; public class MainMenuFragment extends Fragment { public static final String TAG = "MainMenuFragment"; @@ -80,8 +83,13 @@ public class MainMenuFragment extends Fragment { .create(); sodiumWarningDialog.show(); } else ExtraCore.setValue(ExtraConstants.LAUNCH_GAME, true); - - +// if (hasMods("lwjgl3ify-3")) { +// Tools.dialogOnUiThread(requireActivity(), "Error!", "This version of LWJGL3ify is unsupported and won't work!"); +// } +// List f = Tools.getMods("lwjgl3ify-2"); +// if(!f.isEmpty()) { +// +// } }); mShareLogsButton.setOnClickListener((v) -> shareLog(requireContext())); 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 c8870e55d..9f923e2e8 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 @@ -43,6 +43,8 @@ public class ProfileTypeSelectFragment extends Fragment { tryInstall(NeoForgeInstallFragment.class, NeoForgeInstallFragment.TAG)); view.findViewById(R.id.modded_profile_modpack).setOnClickListener((v)-> tryInstall(ModpackCreateFragment.class, ModpackCreateFragment.TAG)); + view.findViewById(R.id.modded_profile_lwjgl3ify).setOnClickListener((v)-> + tryInstall(LWJGL3ifyInstallFragment.class, LWJGL3ifyInstallFragment.TAG)); view.findViewById(R.id.modded_profile_quilt).setOnClickListener((v)-> tryInstall(QuiltInstallFragment.class, QuiltInstallFragment.TAG)); view.findViewById(R.id.modded_profile_bta).setOnClickListener((v)-> diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyDownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyDownloadTask.java new file mode 100644 index 000000000..85b5289ff --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyDownloadTask.java @@ -0,0 +1,225 @@ +package net.kdt.pojavlaunch.modloaders; + +import android.app.Activity; +import android.util.Base64; +import android.util.Base64OutputStream; +import android.util.Log; + +import com.kdt.mcgui.ProgressLayout; + +import net.kdt.pojavlaunch.JMinecraftVersionList; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.modloaders.modpacks.api.CommonApi; +import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; +import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader; +import net.kdt.pojavlaunch.tasks.MinecraftDownloader; +import net.kdt.pojavlaunch.utils.DownloadUtils; +import net.kdt.pojavlaunch.utils.FileUtils; +import net.kdt.pojavlaunch.utils.ZipUtils; +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.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Objects; +import java.util.zip.ZipFile; + +public class LWJGL3ifyDownloadTask implements Runnable, Tools.DownloaderFeedback, AsyncMinecraftDownloader.DoneListener { + private static final String TAG = "LWJGL3ifyDownloadTask"; + private final ModloaderDownloadListener mListener; + private final LWJGL3ifyUtils.LWJGL3ifyMod mLWJGL3ifyMod; + private final Activity mActivity; + + public LWJGL3ifyDownloadTask(ModloaderDownloadListener mListener, LWJGL3ifyUtils.LWJGL3ifyMod mLWJGL3ifyMod, Activity activity) { + this.mListener = mListener; + this.mLWJGL3ifyMod = mLWJGL3ifyMod; + this.mActivity = activity; + } + + @Override + public void run() { + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.fabric_dl_progress, "BTA"); + try { + runCatching(); + mListener.onDownloadFinished(null); + }catch (Exception e) { + mListener.onDownloadError(e); + }finally { + 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(mLWJGL3ifyMod.iconUrl, base64OutputStream); + return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.US_ASCII); + }catch (IOException e) { + Log.w(TAG, "Failed to download base64 icon", e); + }finally { + try { + byteArrayOutputStream.close(); + } catch (IOException e) { + Log.wtf(TAG, "Failed to close a byte array stream??", e); + } + } + return null; + } + + private File tryDownloadModJar() throws IOException { + try { + File jarFile = new File(Tools.DIR_CACHE, "lwjgl3ify-jars/lwjgl3ify-"+ mLWJGL3ifyMod.versionName+".jar"); + if (!(jarFile.exists() && Objects.equals(getSha1(jarFile), mLWJGL3ifyMod.hash))) + DownloadUtils.downloadFileMonitored( + mLWJGL3ifyMod.downloadUrl, + jarFile, + new byte[8192], + this + ); + return jarFile; + } catch (IOException | NoSuchAlgorithmException e) { + throw new IOException("Unable to download LWJGL3ify from " + mLWJGL3ifyMod.downloadUrl, e); + } + } + + private void tryDownloadDeps(File modsDir) throws IOException { + List deps = LWJGL3ifyUtils.collectDependencies(mLWJGL3ifyMod, new CommonApi(mActivity.getString(R.string.curseforge_api_key))); + for (int i=0; i < deps.size(); ++i) { + URI uri = null; + try { + uri = new URI(deps.get(i).downloadUrl); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + String path = uri.getPath(); + String fileName = new File(path).getName(); + try { + DownloadUtils.downloadFileMonitored( + deps.get(i).downloadUrl, + new File(modsDir, fileName), + new byte[8192], + this + ); + } catch (IOException e) { + throw new IOException("Unable to download"+deps.get(i).versionName+" from " + deps.get(i).downloadUrl, e); + } + } + } + + public String getSha1(File file) throws IOException, NoSuchAlgorithmException { + MessageDigest algorithm = MessageDigest.getInstance("SHA-1"); + //noinspection IOStreamConstructor It will reccomend you use an API26 function like a dumb + DigestInputStream hashingStream = new DigestInputStream(new FileInputStream(file), algorithm); + byte[] buffer = new byte[8192]; + while (hashingStream.read(buffer) != -1) {} // just read to update the digest + hashingStream.close(); + byte[] digest = algorithm.digest(); + StringBuilder sb = new StringBuilder(digest.length * 2); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + private static JMinecraftVersionList.Version installJson(String profileID, File modJar) throws IOException { + try { + JMinecraftVersionList.Version version = Tools.GLOBAL_GSON.fromJson( + Tools.read( + ZipUtils.getEntryStream( + new ZipFile(modJar), + "me/eigenraven/lwjgl3ify/relauncher/version.json" + ) + ), + JMinecraftVersionList.Version.class); + version.id = profileID; + Tools.write(Tools.DIR_HOME_VERSION + "/" + profileID + "/" + profileID + ".json", Tools.GLOBAL_GSON.toJson(version)); + return version; + } catch (IOException e) { + throw new IOException("Failed to install "+profileID+" json file."); + } + } + + private MinecraftProfile createProfile(String profileID) { + MinecraftProfile LWJGL3ifyProfile = new MinecraftProfile(); + LWJGL3ifyProfile.lastVersionId = profileID; + LWJGL3ifyProfile.name = "LWJGL3ify - "+ mLWJGL3ifyMod.versionName; + // Allows for smooth-ish upgrades. LWJGL3ify thankfully follows semver. + LWJGL3ifyProfile.gameDir = String.format("./custom_instances/LWJGL3ify_%s", mLWJGL3ifyMod.versionName); + LWJGL3ifyProfile.icon = tryDownloadIcon(); + return LWJGL3ifyProfile; + } + + private void createInstance(MinecraftProfile profile, File modJar) throws IOException { + File modsDir = new File(Tools.DIR_GAME_HOME, profile.gameDir+"/mods"); + if (modsDir.isFile()) { + if (!modsDir.delete()) { + throw new IOException("Failed to delete file where directory should be: " + modsDir.getAbsolutePath()); + } + } + try { + FileUtils.ensureDirectory(modsDir); + } catch (IOException e) { + throw new IOException("Failed to create folder " + modsDir.getAbsolutePath()); + } + // Copy downloaded cached mod jar + try (FileInputStream fis = new FileInputStream(modJar); + FileOutputStream fos = new FileOutputStream(new File(modsDir, "lwjgl3ify-"+ mLWJGL3ifyMod.versionName+".jar"))) { + byte[] buffer = new byte[8192]; + int length; + while ((length = fis.read(buffer)) > 0) { + fos.write(buffer, 0, length); + } + } catch (IOException e) { + e.printStackTrace(); + } + tryDownloadDeps(modsDir); + } + + public void runCatching() throws IOException { + // This cannot be allowed to match the mod.jar ID otherwise conflicts occur and GLFW input breaks + String LWJGL3ifyProfileID = "1.7.10-LWJGL3ify-"+ mLWJGL3ifyMod.versionName; + File modJar = tryDownloadModJar(); + if (!modJar.exists()) throw new IOException("Failed to download LWJGL3ify "+ mLWJGL3ifyMod.versionName); + MinecraftProfile profile = createProfile(LWJGL3ifyProfileID); + new MinecraftDownloader().start(mActivity, installJson(LWJGL3ifyProfileID, modJar), LWJGL3ifyProfileID, this); + createInstance(profile, modJar); + + + LauncherProfiles.load(); + LauncherProfiles.insertMinecraftProfile(profile); + LauncherProfiles.write(); + } + + @Override + public void updateProgress(int curr, int max) { + int progress100 = (int)(((float)curr / (float)max)*100f); + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.of_dl_progress, mLWJGL3ifyMod.versionName); + } + + @Override + public void onDownloadDone() { + + } + + @Override + public void onDownloadFailed(Throwable throwable) { + + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyUtils.java new file mode 100644 index 000000000..46b03bec4 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyUtils.java @@ -0,0 +1,154 @@ +package net.kdt.pojavlaunch.modloaders; + +import androidx.annotation.NonNull; + +import net.kdt.pojavlaunch.modloaders.modpacks.api.ModpackApi; +import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants; +import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail; +import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class LWJGL3ifyUtils { + public static LWJGL3ifyVersionList getLWJGL3ifyVersionList(ModpackApi modpackApi) throws IOException { + ModDetail lwjgl3ifyModDetail = getLWJGL3ifyModDetail(modpackApi); + List supportedVersions = new ArrayList<>(), brokenVersions = new ArrayList<>(); + for (int i = 0; i < lwjgl3ifyModDetail.versionNames.length; i++) { + String normalizedVersion = normalizeVersionName(lwjgl3ifyModDetail.versionNames[i], lwjgl3ifyModDetail.apiSource); + boolean isSupportedVersion = Integer.parseInt(normalizedVersion.split("\\.")[0]) < 3; + + LWJGL3ifyMod version = new LWJGL3ifyMod( + normalizedVersion, + lwjgl3ifyModDetail.versionUrls[i], + lwjgl3ifyModDetail.imageUrl, + lwjgl3ifyModDetail.id, + lwjgl3ifyModDetail.versionHashes[i], + lwjgl3ifyModDetail.dependencies[i] + ); + // LWJGL3ify 3.x uses SDL which needs to be fixed first + (isSupportedVersion ? supportedVersions : brokenVersions).add(version); + } + return new LWJGL3ifyVersionList(supportedVersions, brokenVersions); + } + + /** + * @param jarName LWJGL3ify jar name, the same as Curseforge {@code versionNames} (ex. {@code 3.0.16}, {@code 2.1.18}) + * @param source Either {@link Constants#SOURCE_MODRINTH} or {@link Constants#SOURCE_CURSEFORGE} + * @return Filled out {@link LWJGL3ifyMod} corresponding to version provided + * @throws IllegalArgumentException If LWJGL3ify version was not found in the source provided + * @throws IOException If provided source is not what was expected + */ + public static LWJGL3ifyMod getLWJGL3ifyVersion(String jarName, ModpackApi source) throws IOException { + // This is a hack but it should work + String providedNormalizedVersion = normalizeVersionName(jarName, Constants.SOURCE_CURSEFORGE); + ModDetail lwjgl3ifyModDetail = getLWJGL3ifyModDetail(source); + for (int i = 0; i < lwjgl3ifyModDetail.versionNames.length; i++) { + String normalizedVersion = normalizeVersionName(lwjgl3ifyModDetail.versionNames[i], lwjgl3ifyModDetail.apiSource); + if (providedNormalizedVersion.equals(normalizedVersion)) + return new LWJGL3ifyMod( + providedNormalizedVersion, + lwjgl3ifyModDetail.versionUrls[i], + lwjgl3ifyModDetail.imageUrl, + lwjgl3ifyModDetail.id, + lwjgl3ifyModDetail.versionHashes[i], + lwjgl3ifyModDetail.dependencies[i] + ); + } + String sourceName = (lwjgl3ifyModDetail.apiSource == Constants.SOURCE_MODRINTH) ? "Modrinth" : "Curseforge"; + throw new IllegalArgumentException("Cannot find LWJGL3ify version "+providedNormalizedVersion+" from "+sourceName); + } + + /** + * @return Flat list of all dependencies needed by {@code lwjgl3ifyMod}. + */ + public static List collectDependencies(LWJGL3ifyMod lwjgl3ifyMod, ModpackApi modpackApi) { + List allDeps = new ArrayList<>(); + for (ModDetail.Dependencies dep : lwjgl3ifyMod.dependencies) { + ModDetail detail = getModDetail(modpackApi, dep.project_id); + if (detail != null) { + LWJGL3ifyMod newMod = new LWJGL3ifyMod( + detail.versionNames[0], + detail.versionUrls[0], + detail.imageUrl, + detail.id, + detail.versionHashes[0], + detail.dependencies[0] + ); + + allDeps.add(newMod); + // omg recursion!?!?! + allDeps.addAll(collectDependencies(newMod, modpackApi)); + } + } + return allDeps; + } + + @NonNull + private static String normalizeVersionName(String versionName, int apiSource) { + if (apiSource == Constants.SOURCE_MODRINTH) { // Ex. 3.0.16 - 1.7.10 + versionName = versionName.replaceAll(" - .*", ""); + }else if (apiSource == Constants.SOURCE_CURSEFORGE) { // Ex. lwjgl3ify-3.0.16.jar - 1.7.10 + versionName = versionName.split("-")[1].replace(".jar", ""); + }else throw new IllegalArgumentException("LWJGL3ify is only available on Modrinth or Curseforge!"); + return versionName; + } + + private static ModDetail getModDetail(ModpackApi modpackApi, String id){ + ModDetail modDetail = null; + // Modrinth is more complete in this context. Curseforge is missing some releases. + if (modDetail == null && id != null && !id.isEmpty()) { + modDetail = fetch(modpackApi, Constants.SOURCE_MODRINTH, id); + } + try { + if (modDetail == null && id != null && !id.isEmpty()) { + Integer.parseInt(id); // Triggers exception, skipping call to CF if provided id isn't int + modDetail = fetch(modpackApi, Constants.SOURCE_CURSEFORGE, id); + } + } catch (NumberFormatException ignored) {} + return modDetail; + } + + private static ModDetail fetch(ModpackApi modpackApi, int source, String id) { + ModItem item = new ModItem(source, false, id, null, null, null); + return modpackApi.getModDetails(item); + } + + @NonNull + private static ModDetail getLWJGL3ifyModDetail(ModpackApi modpackApi) throws IOException { + ModDetail lwjgl3ifyModDetail = getModDetail(modpackApi, "lwjgl3ify"); + if (lwjgl3ifyModDetail == null) // Hardcoded ID is a bad idea, but it'll work well enough + lwjgl3ifyModDetail = getModDetail(modpackApi, "998880"); + if (lwjgl3ifyModDetail == null) throw new IOException("Unable to fetch LWJGL3ify version list from Curseforge and Modrinth. " + + "Please check your internet connection and whether Modrinth and Curseforge are accessible."); + return lwjgl3ifyModDetail; + } + // TODO: Turn this into a generic ModItem class for general mods and refactor that crusty modpack naming scheme + public static class LWJGL3ifyMod { + public final String versionName; + public final String downloadUrl; + public final String iconUrl; + public final String id; + public final String hash; + public final ModDetail.Dependencies[] dependencies; + + public LWJGL3ifyMod(String versionName, String downloadUrl, String iconUrl, String id, String hash, ModDetail.Dependencies[] dependencies) { + this.versionName = versionName; + this.downloadUrl = downloadUrl; + this.iconUrl = iconUrl; + this.id = id; + this.hash = hash; + this.dependencies = dependencies; + } + } + public static class LWJGL3ifyVersionList { + public final List supportedVersions; + public final List brokenVersions; // SDL versions for now + + public LWJGL3ifyVersionList(List mSupportedVersions, List mBrokenVersions) { + this.supportedVersions = mSupportedVersions; + this.brokenVersions = mBrokenVersions; + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyVersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyVersionListAdapter.java new file mode 100644 index 000000000..70ab6a078 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/LWJGL3ifyVersionListAdapter.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 LWJGL3ifyVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter { + private final LayoutInflater mLayoutInflater; + private final ArrayList mGroupNames; + private final ArrayList> mGroups; + + public LWJGL3ifyVersionListAdapter(LWJGL3ifyUtils.LWJGL3ifyVersionList versionList, LayoutInflater mLayoutInflater) { + this.mLayoutInflater = mLayoutInflater; + Context context = mLayoutInflater.getContext(); + mGroupNames = new ArrayList<>(2); + mGroups = new ArrayList<>(2); + if(!versionList.supportedVersions.isEmpty()) { + mGroupNames.add(context.getString(R.string.lwjgl3ify_installer_available_versions)); + mGroups.add(versionList.supportedVersions); + } + if(!versionList.brokenVersions.isEmpty()) { + mGroupNames.add(context.getString(R.string.lwjgl3ify_installer_broken_versions)); + mGroups.add(versionList.brokenVersions); + } + 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 LWJGL3ifyUtils.LWJGL3ifyMod 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(getChild(i,i1).versionName); + return convertView; + } + + @Override + public boolean isChildSelectable(int i, int i1) { + return true; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java index cb4c17ab1..a414861bd 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java @@ -100,6 +100,7 @@ public class CurseforgeApi implements ModpackApi{ @Override public ModDetail getModDetails(ModItem item) { + fillInMissingModItemData(item); ArrayList allModDetails = new ArrayList<>(); int index = 0; while(index != CURSEFORGE_PAGINATION_END_REACHED && @@ -109,17 +110,32 @@ public class CurseforgeApi implements ModpackApi{ if(index == CURSEFORGE_PAGINATION_ERROR) return null; int length = allModDetails.size(); String[] versionNames = new String[length]; + String[] versionIds = new String[length]; String[] mcVersionNames = new String[length]; String[] versionUrls = new String[length]; String[] hashes = new String[length]; + ModDetail.Dependencies[][] dependencies = new ModDetail.Dependencies[length][]; for(int i = 0; i < allModDetails.size(); i++) { JsonObject modDetail = allModDetails.get(i); versionNames[i] = modDetail.get("displayName").getAsString(); - + versionIds[i] = modDetail.get("id").getAsString(); JsonElement downloadUrl = modDetail.get("downloadUrl"); versionUrls[i] = downloadUrl.getAsString(); JsonArray gameVersions = modDetail.getAsJsonArray("gameVersions"); + try { + JsonArray dependenciesJsonArray = modDetail.getAsJsonArray("dependencies"); + dependencies[i] = new ModDetail.Dependencies[dependenciesJsonArray.size()]; + for (int i1 = 0; i1 < dependenciesJsonArray.size(); ++i1) { + JsonObject obj = dependenciesJsonArray.get(i1).getAsJsonObject(); + dependencies[i][i1] = new ModDetail.Dependencies( + GsonJsonUtils.getStringSafe(obj, "modId"), + null, + null, // These two are only present on modrinth + GsonJsonUtils.getStringSafe(obj, "relationType") + ); + } + } catch (Exception ignored) {} for(JsonElement jsonElement : gameVersions) { String gameVersion = jsonElement.getAsString(); if(!sMcVersionPattern.matcher(gameVersion).matches()) { @@ -131,7 +147,26 @@ public class CurseforgeApi implements ModpackApi{ hashes[i] = getSha1FromModData(modDetail); } - return new ModDetail(item, versionNames, mcVersionNames, versionUrls, hashes); + return new ModDetail(item, versionNames, versionIds, mcVersionNames, versionUrls, hashes, dependencies); + } + + private void fillInMissingModItemData(ModItem item) { + if (!(item.title == null || item.description == null || item.imageUrl == null)) return; + JsonObject response = mApiHandler.get(String.format("mods/%s", item.id), JsonObject.class); + JsonObject data = GsonJsonUtils.getJsonObjectSafe(response, "data"); + if (data == null) return; + if (item.title == null) { + JsonElement title = data.get("name"); + item.title = title != null ? title.getAsString() : ""; + } + if (item.description == null) { + JsonElement description = data.get("summary"); + item.description = description != null ? description.getAsString() : ""; + } + if (item.imageUrl == null) { + JsonElement imageUrl = data.getAsJsonObject("logo").get("thumbnailUrl"); + item.imageUrl = imageUrl != null ? imageUrl.getAsString() : null; + } } @Override diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java index 325336624..f7ec62197 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.net.Uri; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.kdt.mcgui.ProgressLayout; @@ -16,12 +17,15 @@ import net.kdt.pojavlaunch.modloaders.modpacks.models.ModrinthIndex; import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters; import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchResult; import net.kdt.pojavlaunch.progresskeeper.DownloaderProgressWrapper; +import net.kdt.pojavlaunch.utils.GsonJsonUtils; import net.kdt.pojavlaunch.utils.ZipUtils; import java.io.File; import java.io.IOException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.zip.ZipFile; @@ -86,18 +90,35 @@ public class ModrinthApi implements ModpackApi{ @Override public ModDetail getModDetails(ModItem item) { - + fillInMissingModItemData(item); JsonArray response = mApiHandler.get(String.format("project/%s/version", item.id), JsonArray.class); if(response == null) return null; System.out.println(response); String[] names = new String[response.size()]; + String[] ids = new String[response.size()]; String[] mcNames = new String[response.size()]; String[] urls = new String[response.size()]; String[] hashes = new String[response.size()]; + ModDetail.Dependencies[][] dependencies = new ModDetail.Dependencies[response.size()][]; for (int i=0; i + + Download Failed to download, check your internet connection. Installing… + Create LWJGL3ify profile + Select LWJGL3ify version + Supported LWJGL3IFY versions + Broken LWJGL3ify versions (SDL)