Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b97dcf2
fix(MinecraftDownloader): Account for artifact classifiers
unilock Apr 21, 2026
a876e9e
fix(krypton_wrapper): Crash on old versions
alexytomi Apr 22, 2026
dd94a7f
fix(lwjgl): Load pojavexec earlier
alexytomi Apr 21, 2026
67d8869
fix(pojavexec): Crash on floating window in 26.2-snapshots becasue of…
alexytomi Apr 27, 2026
fea907c
fix: Fullscreen not being toggled when coming from floating window
alexytomi Apr 27, 2026
5dd8c88
bump(MobileGlues): 1.1.5-dev, BGRA Swizzling
alexytomi Apr 26, 2026
70a87ba
fix(build): Correct location of local curseforge_key.txt file
alexytomi Apr 27, 2026
0e9f75b
fix: Incorrect parsing of jvm args from JSON
alexytomi May 4, 2026
405e370
fix: Make renderer autoselect MobileGLues when angelica is found
alexytomi May 5, 2026
f8b7821
bump: version from 1.1.4 to 1.1.5
alexytomi Apr 20, 2026
513be52
Feat: Download mods manually from CurseForges restricted
MaxJubayerYT May 7, 2026
961edca
fix: null mcVersionNames NPE causing restricted CF mods to show loadi…
MaxJubayerYT May 7, 2026
da82686
fix: null mcVersionNames NPE causing restricted CF mods to show loadi…
MaxJubayerYT May 7, 2026
3f6656b
Merge pull request #254 from AngelAuraMC/feat/misc
alexytomi May 7, 2026
4b90827
fix(MinecraftDownloader): Account for artifact classifiers
unilock Apr 21, 2026
d8c85ba
fix(krypton_wrapper): Crash on old versions
alexytomi Apr 22, 2026
6fa8d8b
fix(lwjgl): Load pojavexec earlier
alexytomi Apr 21, 2026
8959173
fix(pojavexec): Crash on floating window in 26.2-snapshots becasue of…
alexytomi Apr 27, 2026
0d42fce
fix: Fullscreen not being toggled when coming from floating window
alexytomi Apr 27, 2026
3b269c2
bump(MobileGlues): 1.1.5-dev, BGRA Swizzling
alexytomi Apr 26, 2026
9baf86d
fix(build): Correct location of local curseforge_key.txt file
alexytomi Apr 27, 2026
f2a3787
fix: Incorrect parsing of jvm args from JSON
alexytomi May 4, 2026
e2329a9
fix: Make renderer autoselect MobileGLues when angelica is found
alexytomi May 5, 2026
a234feb
bump: version from 1.1.4 to 1.1.5
alexytomi Apr 20, 2026
8e1e477
Merge pull request #254 from AngelAuraMC/feat/misc
alexytomi May 7, 2026
b0723ba
Merge pull request #61 from AngelAuraMC/v3_openjdk
MaxJubayerYT May 7, 2026
61af745
fix: CurseForge restricted mod handling and null safety
MaxJubayerYT May 8, 2026
f85fe4b
feat: show CF restricted mods in search with third-party block dialog
MaxJubayerYT May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ app_pojavlauncher/.cxx/
/jre_lwjgl3glfw/gradle/
/jre_lwjgl3glfw/**/build/
/jre_lwjgl3glfw/**/.gradle/
/app_pojavlauncher/curseforge_key.txt
4 changes: 2 additions & 2 deletions app_pojavlauncher/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ static def getDate() { return new Date().format('yyyyMMdd') }
def getVersionName = {
// Get the last version tag, as well as the short head of the last commit
ByteArrayOutputStream TAG = new ByteArrayOutputStream()
String semVer = "1.1.4"
String semVer = "1.1.5"
// Used by the fallback for github actions
ByteArrayOutputStream TAG_PART_COMMIT = new ByteArrayOutputStream()
String TAG_STRING
Expand Down Expand Up @@ -54,7 +54,7 @@ def getVersionName = {
def getCFApiKey = {
String key = System.getenv("CURSEFORGE_API_KEY");
if(key != null) return key;
File curseforgeKeyFile = new File("./curseforge_key.txt");
File curseforgeKeyFile = new File("$projectDir/curseforge_key.txt");
if(curseforgeKeyFile.canRead() && curseforgeKeyFile.isFile()) {
return curseforgeKeyFile.text;
}
Expand Down
Binary file modified app_pojavlauncher/libs/MobileGlues-release.aar
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified app_pojavlauncher/src/main/assets/components/lwjgl3/3.3.3/lwjgl.jar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
d562b83d28c54d14bbb2f0366ed428945100310e
7e958ece157c2208c2a8c715a4f92b684b112cfa
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified app_pojavlauncher/src/main/assets/components/lwjgl3/3.4.1/lwjgl.jar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3999dc44bce7e9d269e034e79c7e430f88a50f3e
80680c59be7562f6efefb301122e421068d6c652
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8fc4f7410f7ff1ebea0d604f69d13531814650ca
2be860186e97caa614b289022a73b9774adcb85b
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) {
mControlLayout.requestLayout();
mControlLayout.post(()->{
// Child of mControlLayout, so refreshing size here is correct
Tools.setFullscreen(this, setFullscreen());
minecraftGLView.refreshSize();
Tools.updateWindowSize(this);
mControlLayout.refreshControlButtonPositions();
Expand Down Expand Up @@ -418,19 +419,22 @@ private void runCraft(String versionId, JMinecraftVersionList.Version version) t
} catch (RuntimeException ignored){
assetVersion = "legacy";
} // If this fails.. oh well.


// FIXME: Automatic detection should be based on provided hint GLFW_CONTEXT_VERSION_MAJOR and GLFW_CONTEXT_VERSION_MINOR
// Autoselect renderer
if (Tools.LOCAL_RENDERER == null) {
// Preferably we could detect when it is modded and swap to zink however that would also
// cover optifine and vanilla+ configurations which are relatively common, degrading their
// experience for no reason. We will compromise with just having users do it themselves.
Tools.LOCAL_RENDERER = "opengles2";
// MobileGlues becomes available post 1.17. It has superior compatibility with mods
// while having fairly similar performance performance compared to GL4ES-based forks.
// while having fairly similar performance compared to GL4ES-based forks.
if(assetVersion.matches("\\d+") || // Should match all digits, which is the modern assetVersioning
"1.17".equals(assetVersion) ||
"1.18".equals(assetVersion) ||
"1.19".equals(assetVersion)) Tools.LOCAL_RENDERER = "opengles_mobileglues";
"1.19".equals(assetVersion) ||
// Angelica gives us GL3.3core on 1.7.10, it's a unique case.
hasMods("angelica")) Tools.LOCAL_RENDERER = "opengles_mobileglues";
}
if(!Tools.checkRendererCompatible(this, Tools.LOCAL_RENDERER)) {
Tools.RenderersList renderersList = Tools.getCompatibleRenderers(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,15 @@ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
}

@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {}
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
/*
Surface recreation in SurfaceView happens very often. When tabbing back in from
out, when minimizing floating window, when turning into floating window, etc.
Whenever the surface isn't in view, it is destroyed. When going into floating
window, it appears to automatically release the associated ANativeWindow. This
can cause a crash if not handled.
*/
}
});

((ViewGroup)getParent()).addView(surfaceView);
Expand Down Expand Up @@ -178,6 +186,11 @@ public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int wid

@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
/*
Surface recreation in TextureView can only really happen once, when turning
into a floating window. Subsequent turns to floating window no longer trigger
recreation. Tabbing out and in does not trigger recreation.
*/
return true;
}

Expand Down
41 changes: 34 additions & 7 deletions app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java
Original file line number Diff line number Diff line change
Expand Up @@ -696,21 +696,44 @@ public static void getCacioJavaArgs(List<String> javaArgList, boolean isJava8, A

public static String[] getMinecraftJVMArgs(String versionName, File gameDir) {
JMinecraftVersionList.Version versionInfo = Tools.getVersionInfo(versionName, true);
// Parse Forge 1.17+ additional JVM Arguments
if (versionInfo.inheritsFrom == null || versionInfo.arguments == null || versionInfo.arguments.jvm == null) {
return new String[0];
}

Map<String, String> varArgMap = new ArrayMap<>();
varArgMap.put("classpath_separator", ":");
varArgMap.put("library_directory", DIR_HOME_LIBRARY);
varArgMap.put("version_name", versionInfo.id);
varArgMap.put("natives_directory", Tools.NATIVE_LIB_DIR);
varArgMap.put("natives_directory", Tools.DIR_CACHE.getAbsolutePath());

List<String> minecraftArgs = new ArrayList<>();
if (versionInfo.arguments != null) {
if (versionInfo.arguments.jvm != null) {
for (Object arg : versionInfo.arguments.jvm) {
if (arg instanceof String) {
// These are defined later on
if (((String) arg).contains("java.library.path")) {
continue;
}
if (arg.equals("-cp")) {
continue;
}
if (arg.equals("${classpath}")){
continue;
}
// Should fix Forge 1.17.1-37.0.12 and older from crashing
// Fixed in forge on https://github.com/MinecraftForge/MinecraftForge/pull/7919
// Released as Forge 1.17.1-37.0.13 in https://maven.minecraftforge.net/net/minecraftforge/forge/1.17.1-37.0.13/forge-1.17.1-37.0.13-changelog.txt
// yes this duplicates it, it's fine.
// FIXME: Workaround old bootstraplauncher <0.1.17 buggy behaviour. See FCL workaround
// https://github.com/FCL-Team/FoldCraftLauncher/blob/00e96bcf8ddc8a550e9aba6091a73d5bee973b54/FCLCore/src/main/java/com/tungsten/fclcore/download/MaintainTask.java#L198-L200
if (((String) arg).startsWith("-DignoreList=")){
minecraftArgs.add(arg+",${version_name}.jar");
continue;
}

// TODO: Implement adding launcher brand and version
if (((String) arg).contains("minecraft.launcher.brand") ||
((String) arg).contains("minecraft.launcher.version")) {
continue;
}

minecraftArgs.add((String) arg);
} //TODO: implement (?maybe?)
}
Expand Down Expand Up @@ -800,7 +823,7 @@ public static String artifactToPath(DependentLibrary library) {
library.downloads.artifact.path != null)
return library.downloads.artifact.path;
String[] libInfos = library.name.split(":");
return libInfos[0].replaceAll("\\.", "/") + "/" + libInfos[1] + "/" + libInfos[2] + "/" + libInfos[1] + "-" + libInfos[2] + ".jar";
return libInfos[0].replaceAll("\\.", "/") + "/" + libInfos[1] + "/" + libInfos[2] + "/" + libInfos[1] + "-" + libInfos[2] + (libInfos.length == 4 ? "-" + libInfos[3] : "") + ".jar";
}

private static String getLibClasspath(JMinecraftVersionList.Version info){
Expand All @@ -823,9 +846,13 @@ public static String generateLaunchClasspath(JMinecraftVersionList.Version info,
String internalLwjglVersion = iLwjglVersion >= 341 ? "3.4.1" : "3.3.3";
File lwjgl3Folder = new File(Tools.DIR_GAME_HOME, "lwjgl3/"+internalLwjglVersion);
String lwjglCore = lwjgl3Folder.getAbsolutePath() + "/lwjgl.jar";
String lwjglMerged = lwjgl3Folder.getAbsolutePath() + "/lwjgl-"+internalLwjglVersion+"-merged-modules";
String lwjglxFile = lwjgl3Folder + "/lwjgl-lwjglx.jar";


launchClasspath.append(lwjglCore).append(":");
// 2nd in priority in case we need to merge lwjgl.jar again for testing
launchClasspath.append(lwjglMerged).append(":");

File[] lwjglModules = lwjgl3Folder.listFiles(pathname ->
pathname.getName().endsWith(".jar") &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters;
import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.profiles.VersionSelectorDialog;
Expand Down Expand Up @@ -231,6 +232,27 @@ public void handleInstallation(Context context, ModDetail modDetail, int selecte
}

String url = modDetail.versionUrls[selectedVersion];

// Check if this is a CF-restricted mod using the flag set during search
boolean isCfRestricted = modDetail.apiSource == Constants.SOURCE_CURSEFORGE
&& (modDetail.isRestricted || url == null || url.isEmpty());

if (isCfRestricted) {
// Show dialog directing user to download from CurseForge website
String cfUrl = "https://www.curseforge.com/minecraft/mc-mods/" + modDetail.id;
Context dialogCtx = mActivityContext != null ? mActivityContext : context;
mMainHandler.post(() ->
new AlertDialog.Builder(dialogCtx)
.setTitle(modDetail.title)
.setMessage("This mod restricts third-party downloads.\n\nDownload it manually from CurseForge and place it in your mods folder:\n\n" + cfUrl)
.setPositiveButton("Open CurseForge", (d, w) ->
Tools.openURL((android.app.Activity) dialogCtx, cfUrl))
.setNegativeButton(android.R.string.cancel, null)
.show()
);
return;
}

if (url == null || url.isEmpty()) {
Tools.showErrorRemote(context, R.string.modpack_install_download_failed,
new IOException("No download URL available for this mod"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,17 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
JsonElement allowModDistribution = dataElement.get("allowModDistribution");
// Gson automatically casts null to false, which leans to issues
// So, only check the distribution flag if it is non-null
if(!allowModDistribution.isJsonNull() && !allowModDistribution.getAsBoolean()) {
Log.i("CurseforgeApi", "Skipping modpack "+dataElement.get("name").getAsString() + " because curseforge sucks");
continue;
}
boolean restricted = !allowModDistribution.isJsonNull() && !allowModDistribution.getAsBoolean();
JsonObject logo = dataElement.getAsJsonObject("logo");
String thumbnailUrl = (logo != null && logo.has("thumbnailUrl") && !logo.get("thumbnailUrl").isJsonNull())
? logo.get("thumbnailUrl").getAsString() : "";
ModItem modItem = new ModItem(Constants.SOURCE_CURSEFORGE,
searchFilters.isModpack,
dataElement.get("id").getAsString(),
dataElement.get("name").getAsString(),
dataElement.get("summary").getAsString(),
dataElement.getAsJsonObject("logo").get("thumbnailUrl").getAsString());
thumbnailUrl);
modItem.isRestricted = restricted;
modItemList.add(modItem);
}
if(curseforgeSearchResult == null) curseforgeSearchResult = new CurseforgeSearchResult();
Expand All @@ -100,6 +101,14 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous

@Override
public ModDetail getModDetails(ModItem item) {
// Short-circuit for restricted mods — no point fetching versions
if (item.isRestricted) {
return new ModDetail(item,
new String[]{"Blocked by the author!"},
new String[]{null},
new String[]{null},
new String[]{null});
}
ArrayList<JsonObject> allModDetails = new ArrayList<>();
int index = 0;
while(index != CURSEFORGE_PAGINATION_END_REACHED &&
Expand All @@ -108,6 +117,26 @@ public ModDetail getModDetails(ModItem item) {
}
if(index == CURSEFORGE_PAGINATION_ERROR) return null;
int length = allModDetails.size();

// Check if ALL versions have null downloadUrl (fully restricted mod)
boolean allRestricted = length > 0;
for (int i = 0; i < length; i++) {
JsonElement url = allModDetails.get(i).get("downloadUrl");
if (url != null && !url.isJsonNull()) {
allRestricted = false;
break;
}
}
if (allRestricted || length == 0) {
// All versions restricted - return a ModDetail with one entry that signals this.
// The version name shown in spinner makes it clear, tapping Install shows CF dialog.
return new ModDetail(item,
new String[]{"Blocked by the author! By clicking the install button it will open the CurseForge page."},
new String[]{null},
new String[]{null},
new String[]{null});
}

String[] versionNames = new String[length];
String[] mcVersionNames = new String[length];
String[] versionUrls = new String[length];
Expand All @@ -118,11 +147,7 @@ public ModDetail getModDetails(ModItem item) {

JsonElement downloadUrl = modDetail.get("downloadUrl");
if (downloadUrl == null || downloadUrl.isJsonNull()) {
// CF restricts direct download for some mods — build edge CDN URL instead
int fileId = modDetail.get("id").getAsInt();
String fileName = modDetail.get("fileName").getAsString();
versionUrls[i] = String.format("https://edge.forgecdn.net/files/%s/%s/%s",
fileId / 1000, fileId % 1000, fileName);
versionUrls[i] = null;
} else {
versionUrls[i] = downloadUrl.getAsString();
}
Expand Down Expand Up @@ -290,4 +315,4 @@ private boolean verifyManifest(CurseManifest manifest) {
static class CurseforgeSearchResult extends SearchResult {
int previousOffset;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public ModDetail(ModItem item, String[] versionNames, String[] mcVersionNames, S

// Add the mc version to the version model
for (int i=0; i<versionNames.length; i++){
if (!versionNames[i].contains(mcVersionNames[i]))
if (mcVersionNames[i] != null && !versionNames[i].contains(mcVersionNames[i]))
versionNames[i] += " - " + mcVersionNames[i];
}
}
Expand All @@ -44,7 +44,7 @@ public String toString() {
return "ModDetail{" +
"versionNames=" + Arrays.toString(versionNames) +
", mcVersionNames=" + Arrays.toString(mcVersionNames) +
", versionIds=" + Arrays.toString(versionUrls) +
", versionUrls=" + Arrays.toString(versionUrls) +
", id='" + id + '\'' +
", title='" + title + '\'' +
", description='" + description + '\'' +
Expand All @@ -53,4 +53,4 @@ public String toString() {
", isModpack=" + isModpack +
'}';
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public class ModItem extends ModSource {
public String title;
public String description;
public String imageUrl;
/** True if the mod author blocked third-party distribution (CF allowModDistribution=false) */
public boolean isRestricted;

public ModItem(int apiSource, boolean isModpack, String id, String title, String description, String imageUrl) {
this.apiSource = apiSource;
Expand All @@ -34,4 +36,4 @@ public String toString() {
public String getIconCacheTag() {
return apiSource+"_"+id;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@ public static void setJavaEnvironment(Activity activity, String jreHome) throws
envMap.put("MG_DIR_PATH", Tools.DIR_DATA + "/MobileGlues");
envMap.put("POJAVEXEC_EGL","libmobileglues.so");
}
if(LOCAL_RENDERER.equals("opengles2")){
envMap.put("LIBGL_ES", "2"); // Krypton Wrapper crashes with 1
}
if (LOCAL_RENDERER.equals("opengles3_desktopgl_zink_kopper")){
envMap.put("POJAVEXEC_EGL","libEGL_mesa.so"); // Use Mesa EGL
if (Tools.shouldUseUBWC()) envMap.put("FD_DEV_FEATURES", "enable_tp_ubwc_flag_hint=1"); // Turnip fix for OneUI rendering issues
}
if (LOCAL_RENDERER.toLowerCase().contains("zink")){
// This is sketch but it fixes a lot of things, if it causes problems we can just undo it.
envMap.put("MESA_GL_VERSION_OVERRIDE","4.6COMPAT");
envMap.put("MESA_GLSL_VERSION_OVERRIDE","460");
}
}
if(LauncherPreferences.PREF_BIG_CORE_AFFINITY) envMap.put("POJAV_BIG_CORE_AFFINITY", "1");
envMap.put("AWTSTUB_WIDTH", Integer.toString(CallbackBridge.windowWidth > 0 ? CallbackBridge.windowWidth : CallbackBridge.physicalWidth));
Expand Down
Loading
Loading