diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java
index 6189b2872b..8716525d47 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Architecture.java
@@ -12,6 +12,22 @@ public class Architecture {
public static final int ARCH_X86 = 0x4;
public static final int ARCH_X86_64 = 0x8;
+ /* On both 32-bit ARM and x86, the top 1GB is reserved for kernel use. */
+ public static final long ADDRESS_SPACE_LIMIT_32_BIT = 0xbfffffffL;
+ /*
+ * Technically, this is supposed to be 48 bits on x86_64, but nobody's allocating
+ * 524288 terabytes of RAM on Pojav any time soon.
+ */
+ public static final long ADDRESS_SPACE_LIMIT_64_BIT = 0x7fffffffffL;
+
+ /**
+ * Get the highest byte accessible within the process's address space.
+ * @return the highest byte accessible within the process's address space.
+ */
+ public static long getAddressSpaceLimit() {
+ return is64BitsDevice() ? ADDRESS_SPACE_LIMIT_64_BIT : ADDRESS_SPACE_LIMIT_32_BIT;
+ }
+
/**
* Tell us if the device supports 64 bits architecture
* @return If the device supports 64 bits architecture
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 3bce2e602c..1ab1e1c8c3 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java
@@ -50,6 +50,8 @@
import net.kdt.pojavlaunch.lifecycle.ContextExecutor;
import net.kdt.pojavlaunch.lifecycle.ContextExecutorTask;
import net.kdt.pojavlaunch.lifecycle.LifecycleAwareAlertDialog;
+import net.kdt.pojavlaunch.memory.MemoryHoleFinder;
+import net.kdt.pojavlaunch.memory.SelfMapsParser;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.Runtime;
import net.kdt.pojavlaunch.plugins.FFmpegPlugin;
@@ -170,9 +172,20 @@ public static void initContextConstants(Context ctx){
public static void launchMinecraft(final AppCompatActivity activity, MinecraftAccount minecraftAccount,
MinecraftProfile minecraftProfile, String versionId, int versionJavaRequirement) throws Throwable {
int freeDeviceMemory = getFreeDeviceMemory(activity);
+ int localeString;
+ int freeAddressSpace = Architecture.is32BitsDevice() ? getMaxContinuousAddressSpaceSize() : -1;
+ Log.i("MemStat", "Free RAM: " + freeDeviceMemory + " Addressable: " + freeAddressSpace);
+ if(freeDeviceMemory > freeAddressSpace && freeAddressSpace != -1) {
+ freeDeviceMemory = freeAddressSpace;
+ localeString = R.string.address_memory_warning_msg;
+ } else {
+ localeString = R.string.memory_warning_msg;
+ }
+
if(LauncherPreferences.PREF_RAM_ALLOCATION > freeDeviceMemory) {
+ int finalDeviceMemory = freeDeviceMemory;
LifecycleAwareAlertDialog.DialogCreator dialogCreator = (dialog, builder) ->
- builder.setMessage(activity.getString(R.string.memory_warning_msg, freeDeviceMemory, LauncherPreferences.PREF_RAM_ALLOCATION))
+ builder.setMessage(activity.getString(localeString, finalDeviceMemory, LauncherPreferences.PREF_RAM_ALLOCATION))
.setPositiveButton(android.R.string.ok, (d, w)->{});
if(LifecycleAwareAlertDialog.haltOnDialog(activity.getLifecycle(), activity, dialogCreator)) {
@@ -923,6 +936,23 @@ public static int getFreeDeviceMemory(Context ctx){
return (int) (memInfo.availMem / 1048576L);
}
+ private static int internalGetMaxContinuousAddressSpaceSize() throws Exception{
+ MemoryHoleFinder memoryHoleFinder = new MemoryHoleFinder();
+ new SelfMapsParser(memoryHoleFinder).run();
+ long largestHole = memoryHoleFinder.getLargestHole();
+ if(largestHole == -1) return -1;
+ else return (int)(largestHole / 1048576L);
+ }
+
+ public static int getMaxContinuousAddressSpaceSize() {
+ try {
+ return internalGetMaxContinuousAddressSpaceSize();
+ }catch (Exception e){
+ Log.w("Tools", "Failed to find the largest uninterrupted address space");
+ return -1;
+ }
+ }
+
public static int getDisplayFriendlyRes(int displaySideRes, float scaling){
displaySideRes *= scaling;
if(displaySideRes % 2 != 0) displaySideRes --;
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/MemoryHoleFinder.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/MemoryHoleFinder.java
new file mode 100644
index 0000000000..283f6651ef
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/MemoryHoleFinder.java
@@ -0,0 +1,22 @@
+package net.kdt.pojavlaunch.memory;
+
+import net.kdt.pojavlaunch.Architecture;
+
+public class MemoryHoleFinder implements SelfMapsParser.Callback {
+ private long mPreviousEnd = 0;
+ private long mLargestHole = -1;
+ private final long mAddressingLimit = Architecture.getAddressSpaceLimit();
+ @Override
+ public boolean process(long begin, long end, String wholeLine) {
+ if(begin >= mAddressingLimit) begin = mAddressingLimit;
+ long holeSize = begin - mPreviousEnd;
+ if(mLargestHole < holeSize) mLargestHole = holeSize;
+ if(begin == mAddressingLimit) return false;
+ mPreviousEnd = end;
+ return true;
+ }
+
+ public long getLargestHole() {
+ return mLargestHole;
+ }
+}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/SelfMapsParser.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/SelfMapsParser.java
new file mode 100644
index 0000000000..6b8bab341b
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/memory/SelfMapsParser.java
@@ -0,0 +1,35 @@
+package net.kdt.pojavlaunch.memory;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Scanner;
+
+public class SelfMapsParser {
+ private final Callback mCallback;
+ public SelfMapsParser(Callback callback) {
+ mCallback = callback;
+ }
+
+ public void run() throws IOException, NumberFormatException {
+ try (FileInputStream fileInputStream = new FileInputStream("/proc/self/maps")) {
+ Scanner scanner = new Scanner(fileInputStream);
+ while(scanner.hasNextLine()) {
+ if(!forEachLine(scanner.nextLine())) break;
+ }
+ }
+ }
+
+ private boolean forEachLine(String line) throws NumberFormatException {
+ int firstSpaceIndex = line.indexOf(' ');
+ String addresses = line.substring(0, firstSpaceIndex);
+ String[] addressArray = addresses.split("-");
+ if(addressArray.length < 2) return true;
+ long begin = Long.parseLong(addressArray[0], 16);
+ long end = Long.parseLong(addressArray[1], 16);
+ return mCallback.process(begin, end, line);
+ }
+
+ public interface Callback {
+ boolean process(long startAddress, long endAddress, String wholeLine);
+ }
+}
diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml
index 90405249cd..bac17568c5 100644
--- a/app_pojavlauncher/src/main/res/values/strings.xml
+++ b/app_pojavlauncher/src/main/res/values/strings.xml
@@ -181,6 +181,7 @@
Forward lock
Absolute finger tracking
The current amount of free RAM (%d) is lower than allocated RAM (%d), which may lead to crashes. Change the allocation if the game crashes.
+ The current amount of free addressable RAM space (%d) is lower than the allocated RAM (%d), which will lead to crashes. Change the allocation if the game crashes.
Memory allocation
Controls how much memory is given to Minecraft.
Corrupted Java Runtime