feat: LWJGL3ify support

Now should run lwjgl3ify ootb (assuming non-debug builds, the
forgePatches url is broken there, not our bug)
This commit is contained in:
tomikun
2026-05-14 00:02:03 +08:00
parent 97e70730ba
commit 97a62cf8d1
11 changed files with 678 additions and 7 deletions

View File

@@ -1,7 +1,10 @@
package net.kdt.pojavlaunch;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static net.kdt.pojavlaunch.Tools.getMods;
import static net.kdt.pojavlaunch.Tools.hasMods;
import static net.kdt.pojavlaunch.Tools.hasNoOnlineProfileDialog;
import static net.kdt.pojavlaunch.Tools.isOnline;
import android.Manifest;
import android.app.NotificationManager;
@@ -36,6 +39,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;
@@ -55,10 +59,12 @@ import net.kdt.pojavlaunch.utils.NotificationUtils;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.List;
public class LauncherActivity extends BaseActivity {
public static final String SETTING_FRAGMENT_TAG = "SETTINGS_FRAGMENT";
@@ -172,6 +178,46 @@ public class LauncherActivity extends BaseActivity {
}
}
// Override whatever version is in use and replace it with lwjgl3ify if needed
List<File> lwjgl3ifyJars = getMods("lwjgl3ify-3");
if (!lwjgl3ifyJars.isEmpty()) {
if (lwjgl3ifyJars.size() > 1) {
// "Duplicate LWJGL3ify jars found, cannot launch."
Tools.dialogOnUiThread(this, R.string.global_error, R.string.mc_download_failed);
return false;
}
File lwjgl3ifyJar = lwjgl3ifyJars.get(0);
// If the version contains lwjgl3ify, its probably someone who knows what they're doing
// so lets leave that alone
if (!prof.lastVersionId.toLowerCase().contains("lwjgl3ify")) {
try {
prof.lastVersionId = LWJGL3ifyUtils.installJson(lwjgl3ifyJar).id;
} catch (IOException e) {
throw new RuntimeException(e);
}
LauncherProfiles.mainProfileJson.profiles.put(selectedProfile, prof);
LauncherProfiles.write();
}
// We just installed a json, we need internet + online acc to download so we add super
// basic detection whether lwjgl3ify assets were downloaded
try {
String jsonPath = LWJGL3ifyUtils.getJsonPath(LWJGL3ifyUtils.getProfileID(lwjgl3ifyJar));
File lwjgl3ifyClientJar = new File(jsonPath.replace(".json", ".jar"));
if (!lwjgl3ifyClientJar.exists()){
if (mAccountSpinner.getSelectedAccount().isLocal() || !isOnline(this)){
Tools.dialogOnUiThread(this, R.string.global_error, R.string.mc_download_failed);
return false;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
new MinecraftDownloader().start(
this,
mcVersion,

View File

@@ -280,15 +280,30 @@ 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()) &&
file.getName().toLowerCase().endsWith(".jar")) {
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,6 @@ public class MainMenuFragment extends Fragment {
.create();
sodiumWarningDialog.show();
} else ExtraCore.setValue(ExtraConstants.LAUNCH_GAME, true);
});
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,139 @@
package net.kdt.pojavlaunch.modloaders;
import android.app.Activity;
import com.kdt.mcgui.ProgressLayout;
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.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Objects;
public class LWJGL3ifyDownloadTask implements Runnable, Tools.DownloaderFeedback, AsyncMinecraftDownloader.DoneListener {
protected 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 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();
}
public void runCatching() throws IOException {
File modJar = tryDownloadModJar();
// This cannot be allowed to match the mod.jar ID otherwise conflicts occur and GLFW input breaks
String LWJGL3ifyProfileID = LWJGL3ifyUtils.getProfileID(modJar);
if (!modJar.exists()) throw new IOException("Failed to download LWJGL3ify "+ mLWJGL3ifyMod.versionName);
MinecraftProfile profile = LWJGL3ifyUtils.createProfile(LWJGL3ifyProfileID, mLWJGL3ifyMod.versionName, mLWJGL3ifyMod.iconUrl);
new MinecraftDownloader().start(mActivity, LWJGL3ifyUtils.installJson(modJar), LWJGL3ifyProfileID, this);
LWJGL3ifyUtils.createInstance(profile, modJar, mLWJGL3ifyMod.versionName);
tryDownloadDeps(new File(Tools.DIR_GAME_HOME, profile.gameDir+"/mods"));
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,274 @@
package net.kdt.pojavlaunch.modloaders;
import android.util.Base64;
import android.util.Base64OutputStream;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.JsonObject;
import net.kdt.pojavlaunch.JMinecraftVersionList;
import net.kdt.pojavlaunch.Tools;
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 net.kdt.pojavlaunch.utils.DownloadUtils;
import net.kdt.pojavlaunch.utils.FileUtils;
import net.kdt.pojavlaunch.utils.ZipUtils;
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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipFile;
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]
);
// It's fixed, probably.
// // LWJGL3ify 3.x uses SDL which needs to be fixed first
// (isSupportedVersion ? supportedVersions : brokenVersions).add(version);
supportedVersions.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;
}
@Nullable
private static ModDetail getModDetail(ModpackApi modpackApi, String id){
ModDetail modDetail = null;
try {
// Modrinth is more complete in this context. Curseforge is missing some releases.
if (id != null && !id.isEmpty()) {
modDetail = fetch(modpackApi, Constants.SOURCE_MODRINTH, id);
}
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;
}
public static JMinecraftVersionList.Version installJson(File modJar) throws IOException {
String profileID = getProfileID(modJar);
String jsonPath = getJsonPath(profileID);
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;
if (!org.apache.commons.io.FileUtils.getFile(jsonPath).exists())
Tools.write(jsonPath, Tools.GLOBAL_GSON.toJson(version));
return version;
} catch (IOException e) {
throw new IOException("Failed to install "+profileID+" json file.");
}
}
@NonNull
public static String getJsonPath(String profileID) {
return Tools.DIR_HOME_VERSION + "/" + profileID + "/" + profileID + ".json";
}
@NonNull
public static String getProfileID(File modJar) throws IOException {
return "1.7.10-LWJGL3ify-"+getVersionFromJar(modJar);
}
public static String getVersionFromJar(File modJar) throws IOException {
try (ZipFile zipFile = new ZipFile(modJar)) {
JsonObject root = Tools.GLOBAL_GSON.fromJson(
Tools.read(ZipUtils.getEntryStream(zipFile, "mcmod.info")),
JsonObject.class);
return root.getAsJsonArray("modList")
.get(0).getAsJsonObject()
.get("version").getAsString();
}
}
public static void createInstance(MinecraftProfile profile, File modJar, String versionName) 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-"+ 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();
}
}
public static MinecraftProfile createProfile(String profileID, String versionName, String iconUrl) {
MinecraftProfile LWJGL3ifyProfile = new MinecraftProfile();
LWJGL3ifyProfile.lastVersionId = profileID;
LWJGL3ifyProfile.name = "LWJGL3ify - "+ versionName;
LWJGL3ifyProfile.gameDir = String.format("./custom_instances/LWJGL3ify_%s", versionName);
LWJGL3ifyProfile.icon = tryDownloadIcon(iconUrl);
return LWJGL3ifyProfile;
}
public static String tryDownloadIcon(String iconUrl) {
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(iconUrl, base64OutputStream);
return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.US_ASCII);
}catch (IOException e) {
Log.w(LWJGL3ifyDownloadTask.TAG, "Failed to download base64 icon", e);
}finally {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
Log.wtf(LWJGL3ifyDownloadTask.TAG, "Failed to close a byte array stream??", e);
}
}
return null;
}
// 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

@@ -311,7 +311,8 @@ public class JREUtils {
// Has to run after SDL env vars are set
try {
Os.setenv("SDL_OPENGL_LIBRARY", graphicsLib, true);
Os.setenv("SDL_EGL_LIBRARY", NATIVE_LIB_DIR+"/"+Os.getenv("POJAVEXEC_EGL"), true);
if (Os.getenv("POJAVEXEC_EGL") != null)
Os.setenv("SDL_EGL_LIBRARY", NATIVE_LIB_DIR+"/"+Os.getenv("POJAVEXEC_EGL"), true);
} catch (ErrnoException e) {
Log.wtf("RENDER_LIBRARY", "Failed to load set SDL env vars");
}

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="error_key_unsupported">Error! The key assigned to this button is invalid. Did you try updating?</string>
<string name="error_invalid_value">Error! Invalid value!</string>
<string name="minecraft_no_username_set">Your Minecraft username is not set! You must set it in the Minecraft website before being able to log in!</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>