[FIXME] Half-finished lwjgl3ify support

Only missing popup that installs lwjgl3ify properly if detected in mods
This commit is contained in:
tomikun
2026-05-14 00:02:03 +08:00
parent e6edcd816e
commit cd8ed668b8
13 changed files with 701 additions and 12 deletions

View File

@@ -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;

View File

@@ -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<File> 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<File> 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;
}
/**

View File

@@ -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<LWJGL3ifyUtils.LWJGL3ifyVersionList>{
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.
}
}

View File

@@ -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<File> f = Tools.getMods("lwjgl3ify-2");
// if(!f.isEmpty()) {
//
// }
});
mShareLogsButton.setOnClickListener((v) -> shareLog(requireContext()));

View File

@@ -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)->

View File

@@ -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<LWJGL3ifyUtils.LWJGL3ifyMod> 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) {
}
}

View File

@@ -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<LWJGL3ifyMod> 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<LWJGL3ifyMod> collectDependencies(LWJGL3ifyMod lwjgl3ifyMod, ModpackApi modpackApi) {
List<LWJGL3ifyMod> 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<LWJGL3ifyMod> supportedVersions;
public final List<LWJGL3ifyMod> brokenVersions; // SDL versions for now
public LWJGL3ifyVersionList(List<LWJGL3ifyMod> mSupportedVersions, List<LWJGL3ifyMod> mBrokenVersions) {
this.supportedVersions = mSupportedVersions;
this.brokenVersions = mBrokenVersions;
}
}
}

View File

@@ -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<String> mGroupNames;
private final ArrayList<List<LWJGL3ifyUtils.LWJGL3ifyMod>> 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;
}
}

View File

@@ -100,6 +100,7 @@ public class CurseforgeApi implements ModpackApi{
@Override
public ModDetail getModDetails(ModItem item) {
fillInMissingModItemData(item);
ArrayList<JsonObject> 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

View File

@@ -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<response.size(); ++i) {
JsonObject version = response.get(i).getAsJsonObject();
names[i] = version.get("name").getAsString();
ids[i] = version.get("id").getAsString();
try {
JsonArray dependenciesJsonArray = version.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, "project_id"),
GsonJsonUtils.getStringSafe(obj, "version_id"),
GsonJsonUtils.getStringSafe(obj, "file_name"),
GsonJsonUtils.getStringSafe(obj, "dependency_type")
);
}
} catch (Exception ignored) {}
mcNames[i] = version.get("game_versions").getAsJsonArray().get(0).getAsString();
urls[i] = version.get("files").getAsJsonArray().get(0).getAsJsonObject().get("url").getAsString();
// Assume there may not be hashes, in case the API changes
@@ -111,7 +132,25 @@ public class ModrinthApi implements ModpackApi{
hashes[i] = hashesMap.get("sha1").getAsString();
}
return new ModDetail(item, names, mcNames, urls, hashes);
return new ModDetail(item, names, ids, mcNames, urls, hashes, dependencies);
}
private void fillInMissingModItemData(ModItem item) {
if (!(item.title == null || item.description == null || item.imageUrl == null)) return;
JsonObject projectResponse = mApiHandler.get(String.format("project/%s", item.id), JsonObject.class);
if (projectResponse == null) return;
if (item.title == null) {
JsonElement title = projectResponse.get("title");
item.title = title != null ? title.getAsString() : "";
}
if (item.description == null) {
JsonElement description = projectResponse.get("description");
item.description = description != null ? description.getAsString() : "";
}
if (item.imageUrl == null) {
JsonElement imageUrl = projectResponse.get("icon_url");
item.imageUrl = imageUrl != null ? imageUrl.getAsString() : null;
}
}
@Override

View File

@@ -12,12 +12,16 @@ public class ModDetail extends ModItem {
public String[] versionUrls;
/* SHA 1 hashes, null if a hash is unavailable */
public String[] versionHashes;
public ModDetail(ModItem item, String[] versionNames, String[] mcVersionNames, String[] versionUrls, String[] hashes) {
public String[] versionIds;
public Dependencies[][] dependencies;
public ModDetail(ModItem item, String[] versionNames, String[] versionIds, String[] mcVersionNames, String[] versionUrls, String[] hashes, Dependencies[][] dependencies) {
super(item.apiSource, item.isModpack, item.id, item.title, item.description, item.imageUrl);
this.versionNames = versionNames;
this.mcVersionNames = mcVersionNames;
this.versionIds = versionIds;
this.versionUrls = versionUrls;
this.versionHashes = hashes;
this.dependencies = dependencies;
// Add the mc version to the version model
for (int i=0; i<versionNames.length; i++){
@@ -32,7 +36,8 @@ public class ModDetail extends ModItem {
return "ModDetail{" +
"versionNames=" + Arrays.toString(versionNames) +
", mcVersionNames=" + Arrays.toString(mcVersionNames) +
", versionIds=" + Arrays.toString(versionUrls) +
", versionIds=" + Arrays.toString(versionIds) +
", versionUrls=" + Arrays.toString(versionUrls) +
", id='" + id + '\'' +
", title='" + title + '\'' +
", description='" + description + '\'' +
@@ -41,4 +46,17 @@ public class ModDetail extends ModItem {
", isModpack=" + isModpack +
'}';
}
public static class Dependencies{
public String project_id; // the main id in item.id
public String version_id;
public String file_name;
public String dependency_type;
public Dependencies(String project_id, String version_id, String file_name, String dependency_type){
this.project_id = project_id;
this.version_id = version_id;
this.file_name = file_name;
this.dependency_type = dependency_type;
}
}
}

View File

@@ -119,6 +119,14 @@
android:layout_marginTop="@dimen/padding_large"
android:text="@string/modpack_install_button" />
<com.kdt.mcgui.MineButton
android:id="@+id/modded_profile_lwjgl3ify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_large"
android:layout_marginTop="@dimen/padding_large"
android:text="@string/create_lwjgl3ify_profile" />
<com.kdt.mcgui.MineButton
android:id="@+id/modded_profile_bta"
android:layout_width="match_parent"

View File

@@ -508,5 +508,9 @@
<string name="global_download">Download</string>
<string name="multirt_no_internet">Failed to download, check your internet connection.</string>
<string name="global_installing">Installing…</string>
<string name="create_lwjgl3ify_profile">Create LWJGL3ify profile</string>
<string name="select_lwjgl3ify_version">Select LWJGL3ify version</string>
<string name="lwjgl3ify_installer_available_versions">Supported LWJGL3IFY versions</string>
<string name="lwjgl3ify_installer_broken_versions">Broken LWJGL3ify versions (SDL)</string>
</resources>