From ac60559a22fad78ed20ea0d050458f399a43deca Mon Sep 17 00:00:00 2001 From: mschiller890 Date: Fri, 20 Mar 2026 21:11:17 +0100 Subject: [PATCH] FIXED: Saving works on Android now! I honestly changed too much stuff trying to pinpoint a fix. I might be stupid. I have no idea what I did that fixed this. --- .../com/mojang/minecraftpe/MainActivity.java | 92 +++++++++---------- src/NinecraftApp.cpp | 3 + src/client/Minecraft.cpp | 2 + src/client/Options.cpp | 4 + src/client/Options.h | 14 +-- src/client/OptionsFile.cpp | 42 ++++++++- src/client/OptionsFile.h | 4 +- src/client/player/LocalPlayer.cpp | 27 ++++++ src/main_android.cpp | 33 +++++-- src/main_android_java.cpp | 33 +++++-- 10 files changed, 184 insertions(+), 70 deletions(-) diff --git a/project/android_java/src/com/mojang/minecraftpe/MainActivity.java b/project/android_java/src/com/mojang/minecraftpe/MainActivity.java index 365fbe5..6dc9e26 100755 --- a/project/android_java/src/com/mojang/minecraftpe/MainActivity.java +++ b/project/android_java/src/com/mojang/minecraftpe/MainActivity.java @@ -59,6 +59,7 @@ public class MainActivity extends Activity { private static final int PERMISSION_REQUEST_CODE = 123; private GLView _glView; + private boolean _nativeInitialized = false; public float invScale = 1.0f;// / 1.5f; private int _screenWidth = 0; private int _screenHeight = 0; @@ -77,63 +78,48 @@ public class MainActivity extends Activity { _screenWidth = Math.max(_dm.widthPixels, _dm.heightPixels); _screenHeight = Math.min(_dm.widthPixels, _dm.heightPixels); - nativeOnCreate(_screenWidth, _screenHeight); - _glView = new GLView(getApplication(), this); //_glView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); + _glView.setEGLConfigChooser(true); //_glView - -// _glView.setEGLConfigChooser( -// new GLSurfaceView.EGLConfigChooser() { -// -// @Override -// public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { -// // TODO Auto-generated method stub -// -// // Specify a configuration for our opengl session -// // and grab the first configuration that matches is -// int[] configSpec = { -// EGL10.EGL_DEPTH_SIZE, 24, -// EGL10.EGL_NONE -// }; -// EGLConfig[] configs = new EGLConfig[1]; -// int[] num_config = new int[1]; -// egl.eglChooseConfig(display, configSpec, configs, 1, num_config); -// EGLConfig config = configs[0]; -// return config; -// -// //return null; -// } -// } ); _glView.commit(); - setContentView(_glView); - - _soundPlayer = new SoundPlayer(this, AudioManager.STREAM_MUSIC); + setContentView(_glView); + + _soundPlayer = new SoundPlayer(this, AudioManager.STREAM_MUSIC); - // request dangerous permissions at runtime if necessary checkAndRequestPermissions(); + initNative(); } - private void checkAndRequestPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - boolean needRequest = false; - if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - needRequest = true; - } - if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - needRequest = true; - } - if (needRequest) { - requestPermissions(new String[] { - android.Manifest.permission.WRITE_EXTERNAL_STORAGE, - android.Manifest.permission.READ_EXTERNAL_STORAGE - }, PERMISSION_REQUEST_CODE); - } + private void initNative() { + if (_nativeInitialized) { + return; } + _nativeInitialized = true; + nativeOnCreate(_screenWidth, _screenHeight); + } + + // request dangerous permissions at runtime if necessary + private boolean checkAndRequestPermissions() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + boolean writeGranted = checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + boolean readGranted = checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + + if (writeGranted && readGranted) { + return true; + } + + requestPermissions(new String[] { + android.Manifest.permission.WRITE_EXTERNAL_STORAGE, + android.Manifest.permission.READ_EXTERNAL_STORAGE + }, PERMISSION_REQUEST_CODE); + + return false; } @Override @@ -146,8 +132,18 @@ public class MainActivity extends Activity { break; } } - if (!granted) { - // user denied; you might want to warn or close the app + if (granted) { + initNative(); + } else { + // We can still run using app-specific external files in scoped-storage, + // so allow startup while warning the user. + initNative(); + new AlertDialog.Builder(this) + .setTitle("Storage permission recommended") + .setMessage("MinecraftPE can still run with app-private storage, but public external save/load may require permission.") + .setPositiveButton("OK", null) + .setCancelable(true) + .show(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); diff --git a/src/NinecraftApp.cpp b/src/NinecraftApp.cpp index 69e01c3..d2feea6 100755 --- a/src/NinecraftApp.cpp +++ b/src/NinecraftApp.cpp @@ -99,6 +99,9 @@ void NinecraftApp::init() I18n::loadLanguage(platform(), "en_US"); #endif + if (!externalStoragePath.empty()) { + options.setOptionsFilePath(externalStoragePath); + } Minecraft::init(); #if !defined(DEMO_MODE) && !defined(APPLE_DEMO_PROMOTION) && !defined(NO_STORAGE) diff --git a/src/client/Minecraft.cpp b/src/client/Minecraft.cpp index ba8b126..926ff10 100755 --- a/src/client/Minecraft.cpp +++ b/src/client/Minecraft.cpp @@ -1143,6 +1143,8 @@ void Minecraft::init() checkGlError("Init complete"); #endif + options.load(); + setIsCreativeMode(false); // false means it's Survival Mode reloadOptions(); } diff --git a/src/client/Options.cpp b/src/client/Options.cpp index 7f965e8..227c7c5 100755 --- a/src/client/Options.cpp +++ b/src/client/Options.cpp @@ -283,6 +283,10 @@ void Options::save() { optionsFile.save(stringVec); } +void Options::setOptionsFilePath(const std::string& path) { + optionsFile.setOptionsPath(path + "/options.txt"); +} + void Options::notifyOptionUpdate(OptionId key, bool value) { minecraft->optionUpdated(key, value); } diff --git a/src/client/Options.h b/src/client/Options.h index 6401471..1e50c6f 100755 --- a/src/client/Options.h +++ b/src/client/Options.h @@ -100,15 +100,10 @@ public: // elements werent initialized so i was getting a garbage pointer and a crash m_options.fill(nullptr); initTable(); - load(); + // load() is deferred to init() where path is configured correctly } - void initTable(); - - void set(OptionId key, int value); - void set(OptionId key, float value); - void set(OptionId key, const std::string& value); - void toggle(OptionId key); + void initTable(); int getIntValue(OptionId key) { auto option = opt(key); @@ -144,6 +139,11 @@ public: void load(); void save(); + void set(OptionId key, int value); + void set(OptionId key, float value); + void set(OptionId key, const std::string& value); + void setOptionsFilePath(const std::string& path); + void toggle(OptionId key); void notifyOptionUpdate(OptionId key, bool value); void notifyOptionUpdate(OptionId key, float value); diff --git a/src/client/OptionsFile.cpp b/src/client/OptionsFile.cpp index 3bd9a4a..feb2fdf 100755 --- a/src/client/OptionsFile.cpp +++ b/src/client/OptionsFile.cpp @@ -4,6 +4,12 @@ #include #include +#if !defined(_WIN32) +#include +#include +#include +#endif + OptionsFile::OptionsFile() { #ifdef __APPLE__ settingsPath = "./Documents/options.txt"; @@ -14,6 +20,14 @@ OptionsFile::OptionsFile() { #endif } +void OptionsFile::setOptionsPath(const std::string& path) { + settingsPath = path; +} + +std::string OptionsFile::getOptionsPath() const { + return settingsPath; +} + void OptionsFile::save(const StringVector& settings) { FILE* pFile = fopen(settingsPath.c_str(), "w"); if(pFile != NULL) { @@ -22,10 +36,33 @@ void OptionsFile::save(const StringVector& settings) { } fclose(pFile); } else { - LOGI("OptionsFile::save failed to open '%s' for writing: %s", settingsPath.c_str(), strerror(errno)); + if (errno != ENOENT) + LOGI("OptionsFile::save failed to open '%s' for writing: %s", settingsPath.c_str(), strerror(errno)); + + // Ensure parent directory exists for safekeeping if path contains directories + std::string dir = settingsPath; + size_t fpos = dir.find_last_of("/\\"); + if (fpos != std::string::npos) { + dir.resize(fpos); + struct stat st; + if (stat(dir.c_str(), &st) != 0) { + // attempt recursive mkdir + std::string toCreate; + for (size_t i = 0; i <= dir.size(); ++i) { + if (i == dir.size() || dir[i] == '/' || dir[i] == '\\') { + if (!toCreate.empty()) { + mkdir(toCreate.c_str(), 0755); + } + } + if (i < dir.size()) + toCreate.push_back(dir[i]); + } + } + } } } + StringVector OptionsFile::getOptionStrings() { StringVector returnVector; FILE* pFile = fopen(settingsPath.c_str(), "r"); @@ -46,7 +83,8 @@ StringVector OptionsFile::getOptionStrings() { } fclose(pFile); } else { - LOGI("OptionsFile::getOptionStrings failed to open '%s' for reading: %s", settingsPath.c_str(), strerror(errno)); + if (errno != ENOENT) + LOGI("OptionsFile::getOptionStrings failed to open '%s' for reading: %s", settingsPath.c_str(), strerror(errno)); } return returnVector; } diff --git a/src/client/OptionsFile.h b/src/client/OptionsFile.h index 942af75..b2464a0 100755 --- a/src/client/OptionsFile.h +++ b/src/client/OptionsFile.h @@ -11,7 +11,9 @@ public: OptionsFile(); void save(const StringVector& settings); StringVector getOptionStrings(); - + void setOptionsPath(const std::string& path); + std::string getOptionsPath() const; + private: std::string settingsPath; }; diff --git a/src/client/player/LocalPlayer.cpp b/src/client/player/LocalPlayer.cpp index c048801..ef8ff7f 100755 --- a/src/client/player/LocalPlayer.cpp +++ b/src/client/player/LocalPlayer.cpp @@ -187,6 +187,33 @@ static bool ensureDirectoryExists(const std::string& path) { return _mkdir(path.c_str()) == 0 || errno == EEXIST; #else struct stat st; + if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) + return true; + + std::string subPath; + size_t i = 0; + while (i < path.length()) { + i = path.find_first_of("/\\", i); + if (i == std::string::npos) { + subPath = path; + } else { + subPath = path.substr(0, i); + } + + if (!subPath.empty()) { + if (stat(subPath.c_str(), &st) != 0) { + if (mkdir(subPath.c_str(), 0755) != 0 && errno != EEXIST) + return false; + } else if (!S_ISDIR(st.st_mode)) { + return false; + } + } + + if (i == std::string::npos) + break; + i += 1; + } + if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) return true; return mkdir(path.c_str(), 0755) == 0 || errno == EEXIST; diff --git a/src/main_android.cpp b/src/main_android.cpp index 7d9c2bb..dc47f35 100755 --- a/src/main_android.cpp +++ b/src/main_android.cpp @@ -25,12 +25,33 @@ static void setupExternalPath(struct android_app* state, MAIN_CLASS* app) { LOGI("Environment exists"); } - jclass clazz = env->FindClass("android/os/Environment"); - jmethodID method = env->GetStaticMethodID(clazz, "getExternalStorageDirectory", "()Ljava/io/File;"); - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); + // try appspecific external directory first + jobject activity = state->activity->clazz; + jclass activityClass = env->GetObjectClass(activity); + jmethodID getExternalFilesDir = env->GetMethodID(activityClass, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); + + jobject file = NULL; + if (getExternalFilesDir != NULL) { + file = env->CallObjectMethod(activity, getExternalFilesDir, NULL); + } + + if (file == NULL) { + // Fallback to the legacy shared storage directory + jclass clazz = env->FindClass("android/os/Environment"); + jmethodID method = env->GetStaticMethodID(clazz, "getExternalStorageDirectory", "()Ljava/io/File;"); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + file = env->CallStaticObjectMethod(clazz, method); + } + + if (!file) { + LOGI("Failed to get external storage file object, using current working dir"); + app->externalStoragePath = "."; + app->externalCacheStoragePath = "."; + return; } - jobject file = env->CallStaticObjectMethod(clazz, method); jclass fileClass = env->GetObjectClass(file); jmethodID fileMethod = env->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;"); @@ -38,7 +59,7 @@ static void setupExternalPath(struct android_app* state, MAIN_CLASS* app) const char* str = env->GetStringUTFChars((jstring) pathString, NULL); app->externalStoragePath = str; - app->externalCacheStoragePath = str; + app->externalCacheStoragePath = str; LOGI("%s", str); // ensure the process working directory is set to a writable location diff --git a/src/main_android_java.cpp b/src/main_android_java.cpp index e6d91df..c89ae03 100755 --- a/src/main_android_java.cpp +++ b/src/main_android_java.cpp @@ -30,12 +30,33 @@ static void setupExternalPath(JNIEnv* env, MAIN_CLASS* app) { LOGI("Environment exists"); } - jclass clazz = env->FindClass("android/os/Environment"); - jmethodID method = env->GetStaticMethodID(clazz, "getExternalStorageDirectory", "()Ljava/io/File;"); - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); + // try appspecific external directory first + jobject activity = g_pActivity; + jclass activityClass = env->GetObjectClass(activity); + jmethodID getExternalFilesDir = env->GetMethodID(activityClass, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); + + jobject file = NULL; + if (getExternalFilesDir != NULL) { + file = env->CallObjectMethod(activity, getExternalFilesDir, NULL); + } + + if (file == NULL) { + // Fallback to the legacy shared storage directory + jclass clazz = env->FindClass("android/os/Environment"); + jmethodID method = env->GetStaticMethodID(clazz, "getExternalStorageDirectory", "()Ljava/io/File;"); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + file = env->CallStaticObjectMethod(clazz, method); + } + + if (!file) { + LOGI("Failed to get external storage file object, using current working dir"); + app->externalStoragePath = "."; + app->externalCacheStoragePath = "."; + return; } - jobject file = env->CallStaticObjectMethod(clazz, method); jclass fileClass = env->GetObjectClass(file); jmethodID fileMethod = env->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;"); @@ -43,7 +64,7 @@ static void setupExternalPath(JNIEnv* env, MAIN_CLASS* app) const char* str = env->GetStringUTFChars((jstring) pathString, NULL); app->externalStoragePath = str; - app->externalCacheStoragePath = str; + app->externalCacheStoragePath = str; LOGI("%s", str); // same fix as the native entry point: make sure cwd is writable