Browse Source

Android: setting page

LouYihua 7 years ago
parent
commit
3f877adaed

+ 1 - 0
.travis.yml

@@ -55,6 +55,7 @@ matrix:
             - tools
             - build-tools-25.0.2
             - android-24
+            - extra-android-m2repository
         jdk: oraclejdk8
         before_install:
           - wget http://dl.google.com/android/repository/android-ndk-$NDK_VERSION-linux-x86_64.zip

+ 9 - 0
android/app/build.gradle

@@ -41,3 +41,12 @@ android {
         main.java.srcDirs += 'src/main/java'
     }
 }
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    compile 'com.android.support:appcompat-v7:25.3.1'
+    compile 'com.android.support:design:25.3.1'
+}

+ 35 - 26
android/app/src/main/AndroidManifest.xml

@@ -1,14 +1,25 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Replace org.libsdl.app with the identifier of your game below, e.g.
+<?xml version="1.0" encoding="utf-8"?><!--
+ Replace org.libsdl.app with the identifier of your game below, e.g.
      com.gamemaker.game
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="io.github.sdlpal"
-      android:versionCode="2"
-      android:versionName="2.0"
-      android:installLocation="auto">
+    package="io.github.sdlpal"
+    android:installLocation="auto"
+    android:versionCode="2"
+    android:versionName="2.0"> <!-- Android 2.3.3 -->
+    <uses-sdk
+        android:minSdkVersion="10"
+        android:targetSdkVersion="12" />
 
-    <!-- Create a Java class extending SDLActivity and place it in a
+    <!-- OpenGL ES 2.0 -->
+    <uses-feature android:glEsVersion="0x00020000" />
+
+    <!-- Allow writing to external storage -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!--
+ Create a Java class extending SDLActivity and place it in a
          directory under src matching the package, e.g.
          	src/com/gamemaker/game/MyGame.java
 
@@ -17,29 +28,27 @@
 
          An example Java class can be found in README-android.txt
     -->
-    <application android:label="@string/app_name"
-                 android:icon="@drawable/ic_launcher"
-                 android:allowBackup="true"
-                 android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-                 android:hardwareAccelerated="true" >
-        <activity android:name="PalActivity"
-                  android:label="@string/app_name"
-                  android:configChanges="keyboardHidden|orientation"
-                  android:screenOrientation="landscape"
-                  >
+    <application
+        android:allowBackup="true"
+        android:hardwareAccelerated="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name">
+        <activity
+            android:name=".PalActivity"
+            android:configChanges="keyboardHidden|orientation"
+            android:label="@string/app_name"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity
+            android:name=".SettingsActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+        </activity>
     </application>
 
-    <!-- Android 2.3.3 -->
-    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="12" />
-
-    <!-- OpenGL ES 2.0 -->
-    <uses-feature android:glEsVersion="0x00020000" /> 
-
-    <!-- Allow writing to external storage -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
-</manifest> 
+</manifest>

+ 149 - 43
android/app/src/main/cpp/android_jni.cpp

@@ -15,23 +15,20 @@
 #include "palcfg.h"
 #include "util.h"
 
-char externalStoragePath[1024];
-char midiInterFile[1024];
+#include <string>
 
-static int jstring_to_utf8(JNIEnv* env, jstring j_str, char *buffer, int capacity)
+static std::string g_basepath, g_configpath, g_midipath;
+static int g_screenWidth = 640, g_screenHeight = 400;
+const char* midiInterFile;
+
+static std::string jstring_to_utf8(JNIEnv* env, jstring j_str)
 {
     jsize length = env->GetStringUTFLength(j_str);
     const char * const base = env->GetStringUTFChars(j_str, NULL);
-    if (base == NULL)
-    {
-        return 0;
-    }
-    if (capacity <= length)
-        length = capacity - 1;
-    strncpy(buffer, base, length);
+    if (base == NULL) return "";
+    std::string value(base, length);
     env->ReleaseStringUTFChars(j_str, base);
-    buffer[length] = '\0';
-    return length;
+    return value;
 }
 
 static JavaVM* gJVM;
@@ -55,29 +52,134 @@ static JNIEnv* getJNIEnv()
     return env;
 }
 
-/* 
- * Class:     io_github_sdlpal_PalActivity 
- * Method:    setExternalStorage 
- * Signature: (Ljava/lang/String;)V
- */  
+/*
+ * Class:     io_github_sdlpal_PalActivity
+ * Method:    setAppPath
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)V
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setAppPath(JNIEnv *env, jclass cls, jstring base_path, jstring cache_path)
+{
+    g_basepath = jstring_to_utf8(env, base_path);
+    g_configpath = jstring_to_utf8(env, cache_path);
+    if (*g_basepath.rbegin() != '/') g_basepath.append("/");
+    if (*g_configpath.rbegin() != '/') g_configpath.append("/");
+    g_midipath = g_configpath + "intermediates.mid";
+    midiInterFile = g_midipath.c_str();
+}
+
+/*
+ * Class:     io_github_sdlpal_PalActivity
+ * Method:    setScreenSize
+ * Signature: (II)V
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setScreenSize(JNIEnv *env, jclass cls, int width, int height)
+{
+    g_screenWidth = width;
+    g_screenHeight = height;
+}
+
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    loadConfigFile
+ * Signature: (V)Z
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_loadConfigFile(JNIEnv *env, jclass cls)
+{
+    PAL_LoadConfig(TRUE);
+    return gConfig.fLaunchSetting ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    saveConfigFile
+ * Signature: (V)Z
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_saveConfigFile(JNIEnv *env, jclass cls)
+{
+    return PAL_SaveConfig() ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    getConfigBoolean
+ * Signature: (Ljava/lang/String;)Z
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigBoolean(JNIEnv *env, jclass cls, jstring j_str)
+{
+    ConfigValue value;
+    std::string name = jstring_to_utf8(env, j_str);
+    return PAL_GetConfigItem(name.c_str(), &value) ? value.bValue : JNI_FALSE;
+}
+
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    getConfigInt
+ * Signature: (Ljava/lang/String;)I
+ */
 EXTERN_C_LINKAGE
-JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setExternalStorage(JNIEnv *env, jclass cls, jstring j_str)  
+JNIEXPORT int JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigInt(JNIEnv *env, jclass cls, jstring j_str)
 {
-    jstring_to_utf8(env, j_str, externalStoragePath, sizeof(externalStoragePath) - 8);
-    strcat(externalStoragePath, "/sdlpal/");
-    LOGV("JNI got externalStoragePath:%s", externalStoragePath);
+    ConfigValue value;
+    std::string name = jstring_to_utf8(env, j_str);
+    return PAL_GetConfigItem(name.c_str(), &value) ? value.iValue : JNI_FALSE;
 }
 
-/* 
- * Class:     io_github_sdlpal_PalActivity 
- * Method:    setMIDIInterFile 
- * Signature: (Ljava/lang/String;)V
- */  
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    getConfigString
+ * Signature: (Ljava/lang/String;)ILjava/lang/String;
+ */
 EXTERN_C_LINKAGE
-JNIEXPORT void JNICALL Java_io_github_sdlpal_PalActivity_setMIDIInterFile(JNIEnv *env, jclass cls, jstring j_str)  
+JNIEXPORT jstring JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigString(JNIEnv *env, jclass cls, jstring j_str)
 {
-    jstring_to_utf8(env, j_str, midiInterFile, sizeof(midiInterFile));
-    LOGV("JNI got midi inter filename:%s", midiInterFile);
+    ConfigValue value;
+    std::string name = jstring_to_utf8(env, j_str);
+    return PAL_GetConfigItem(name.c_str(), &value) ? env->NewStringUTF(value.sValue) : nullptr;
+}
+
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    setConfigBoolean
+ * Signature: (Ljava/lang/String;Z)Z
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_setConfigBoolean(JNIEnv *env, jclass cls, jstring j_str, jboolean val)
+{
+    ConfigValue value = { (LPCSTR)(val ? TRUE : FALSE) };
+    std::string name = jstring_to_utf8(env, j_str);
+    return PAL_SetConfigItem(name.c_str(), &value) ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    setConfigInt
+ * Signature: (Ljava/lang/String;I)Z
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_setConfigInt(JNIEnv *env, jclass cls, jstring j_str, int val)
+{
+    ConfigValue value = { (LPCSTR)val };
+    std::string name = jstring_to_utf8(env, j_str);
+    return PAL_SetConfigItem(name.c_str(), &value) ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+ * Class:     io_github_sdlpal_SettingsActivity
+ * Method:    setConfigString
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+ */
+EXTERN_C_LINKAGE
+JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_setConfigString(JNIEnv *env, jclass cls, jstring j_str, jstring v_str)
+{
+    std::string name = jstring_to_utf8(env, j_str);
+    std::string val = jstring_to_utf8(env, v_str);
+    ConfigValue value = { val.c_str() };
+    return PAL_SetConfigItem(name.c_str(), &value) ? JNI_TRUE : JNI_FALSE;
 }
 
 EXTERN_C_LINKAGE
@@ -138,7 +240,7 @@ UTIL_BasePath(
    VOID
 )
 {
-    return externalStoragePath;
+    return g_basepath.c_str();
 }
 
 EXTERN_C_LINKAGE
@@ -147,7 +249,16 @@ UTIL_SavePath(
    VOID
 )
 {
-    return externalStoragePath;
+    return g_basepath.c_str();
+}
+
+EXTERN_C_LINKAGE
+LPCSTR
+UTIL_ConfigPath(
+   VOID
+)
+{
+    return g_configpath.c_str();
 }
 
 EXTERN_C_LINKAGE
@@ -157,16 +268,9 @@ UTIL_GetScreenSize(
    DWORD *pdwScreenHeight
 )
 {
-    JNIEnv* env = getJNIEnv();
-    jclass clazz = env->FindClass("io/github/sdlpal/PalActivity");
-    *pdwScreenWidth = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "screenWidth", "I"));
-    *pdwScreenHeight = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "screenHeight", "I"));
-    if (*pdwScreenWidth == 0 || *pdwScreenHeight == 0)
-    {
-        *pdwScreenWidth  = 640;
-        *pdwScreenHeight = 400;
-    }
-    return TRUE;
+    if (*pdwScreenWidth) *pdwScreenWidth = g_screenWidth;
+    if (*pdwScreenHeight) *pdwScreenHeight = g_screenHeight;
+    return (pdwScreenWidth && pdwScreenHeight && *pdwScreenWidth && *pdwScreenHeight);
 }
 
 EXTERN_C_LINKAGE
@@ -196,8 +300,9 @@ UTIL_Platform_Init(
 		__android_log_print(level_mapping[level], TAG, "%s", str);
 	});
 
-   gConfig.fLaunchSetting = FALSE;
-   return 0;
+    FILE *fp = fopen((g_configpath + "running").c_str(), "w");
+    if (fp) fclose(fp);
+    return 0;
 }
 
 EXTERN_C_LINKAGE
@@ -206,4 +311,5 @@ UTIL_Platform_Quit(
    VOID
 )
 {
+    unlink((g_configpath + "running").c_str());
 }

+ 1 - 2
android/app/src/main/cpp/android_jni.h

@@ -5,8 +5,7 @@
 extern "C" {
 #endif
 
-extern char externalStoragePath[1024];
-extern char midiInterFile[1024];
+extern char *midiInterFile;
 
 void* JNI_mediaplayer_load(const char *);
 void JNI_mediaplayer_free(void *);

+ 9 - 0
android/app/src/main/cpp/native_midi.c

@@ -29,6 +29,15 @@
 #include "SDL_endian.h"
 #include "native_midi/native_midi.h"
 
+#include <android/log.h>
+#define TAG "sdlpal-jni"
+
+#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG,__VA_ARGS__)
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , TAG,__VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , TAG,__VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , TAG,__VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , TAG,__VA_ARGS__)
+
 /* Native Midi song */
 struct _NativeMidiSong
 {

+ 7 - 1
android/app/src/main/cpp/pal_config.h

@@ -28,6 +28,7 @@
 
 # define PAL_PREFIX            UTIL_BasePath()
 # define PAL_SAVE_PREFIX       UTIL_SavePath()
+# define PAL_CONFIG_PREFIX     UTIL_ConfigPath()
 # define PAL_HAS_TOUCH         1
 # define PAL_DEFAULT_WINDOW_WIDTH   320
 # define PAL_DEFAULT_WINDOW_HEIGHT  200
@@ -46,7 +47,7 @@
 
 #define PAL_HAS_NATIVEMIDI  1
 
-#define PAL_HAS_CONFIG_PAGE 0
+#define PAL_HAS_CONFIG_PAGE 1
 
 #define PAL_FILESYSTEM_IGNORE_CASE 1
 
@@ -64,6 +65,11 @@ UTIL_SavePath(
    VOID
 );
 
+LPCSTR
+UTIL_ConfigPath(
+   VOID
+);
+
 PAL_C_LINKAGE_END
 
 #endif

+ 33 - 14
android/app/src/main/java/io/github/sdlpal/PalActivity.java

@@ -1,17 +1,22 @@
 package io.github.sdlpal;
 
 import org.libsdl.app.SDLActivity;
+
+import android.content.Intent;
 import android.os.*;
 import android.util.*;
 import android.media.*;
 import android.net.Uri;
 import java.io.*;
-import java.util.*;
 
 public class PalActivity extends SDLActivity {
     private static final String TAG = "sdlpal-debug";
     private static MediaPlayer mediaPlayer;
-    private static int screenWidth, screenHeight;
+
+    public static native void setAppPath(String basepath, String cachepath);
+    public static native void setScreenSize(int width, int height);
+
+    public static boolean crashed = false;
 
     private static MediaPlayer JNI_mediaplayer_load(String filename){
         Log.v(TAG, "loading midi:" + filename);
@@ -27,24 +32,38 @@ public class PalActivity extends SDLActivity {
         return mediaPlayer;
     }
 
-    public static native void setExternalStorage(String str);
-    public static native void setMIDIInterFile(String str);
-
     @Override
     public void onCreate(Bundle savedInstanceState) {  
         super.onCreate(savedInstanceState);
-        String interFilePath = mSingleton.getApplicationContext().getCacheDir().getPath() + "/intermediates.mid";
-        Log.v(TAG, "java interfile path " + interFilePath);
-        setMIDIInterFile(interFilePath);
-        String externalStorageState = Environment.getExternalStorageState();
-        if (externalStorageState.equals(Environment.MEDIA_MOUNTED)){
-            setExternalStorage(Environment.getExternalStorageDirectory().getPath());
-            Log.v(TAG, "sdcard path " + Environment.getExternalStorageDirectory().getPath());
+
+        String cachePath = getApplicationContext().getCacheDir().getPath();
+        String sdcardState = Environment.getExternalStorageState();
+        if (sdcardState.equals(Environment.MEDIA_MOUNTED)){
+            setAppPath(Environment.getExternalStorageDirectory().getPath() + "/sdlpal", cachePath);
+        } else {
+            setAppPath("/sdcard/sdlpal", cachePath);
         }
+
         DisplayMetrics metrics = new DisplayMetrics();
         getWindowManager().getDefaultDisplay().getMetrics(metrics);
-        screenWidth = metrics.widthPixels;
-        screenHeight = metrics.heightPixels;
+        setScreenSize(metrics.widthPixels, metrics.heightPixels);
+
+        File runningFile = new File(cachePath + "/running");
+        crashed = runningFile.exists();
+
+        if (SettingsActivity.loadConfigFile() || crashed) {
+            runningFile.delete();
+
+            Intent intent = new Intent(this, SettingsActivity.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            startActivity(intent);
+            finish();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
     }
 
     @Override

+ 209 - 0
android/app/src/main/java/io/github/sdlpal/SettingsActivity.java

@@ -0,0 +1,209 @@
+package io.github.sdlpal;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.AppCompatSpinner;
+import android.support.v7.widget.SwitchCompat;
+import android.support.v7.widget.Toolbar;
+import  android.support.v7.app.AlertDialog;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+
+public class SettingsActivity extends AppCompatActivity {
+
+    public static native boolean loadConfigFile();
+    public static native boolean saveConfigFile();
+    public static native boolean getConfigBoolean(String item);
+    public static native int getConfigInt(String item);
+    public static native String getConfigString(String item);
+    public static native boolean setConfigBoolean(String item, boolean value);
+    public static native boolean setConfigInt(String item, int value);
+    public static native boolean setConfigString(String item, String value);
+
+    private static final String FullScreen = "FullScreen";
+    private static final String KeepAspectRatio = "KeepAspectRatio";
+    private static final String LaunchSetting = "LaunchSetting";
+    private static final String Stereo = "Stereo";
+    private static final String UseEmbeddedFonts = "UseEmbeddedFonts";
+    private static final String UseSurroundOPL = "UseSurroundOPL";
+    private static final String UseTouchOverlay = "UseTouchOverlay";
+    private static final String AudioBufferSize = "AudioBufferSize";
+    private static final String CodePage = "CodePage";
+    private static final String OPLSampleRate = "OPLSampleRate";
+    private static final String ResampleQuality = "ResampleQuality";
+    private static final String SampleRate = "SampleRate";
+    private static final String MusicVolume = "MusicVolume";
+    private static final String SoundVolume = "SoundVolume";
+    private static final String CDFormat = "CD";
+    private static final String GamePath = "GamePath";
+    private static final String SavePath = "SavePath";
+    private static final String MessageFileName = "MessageFileName";
+    private static final String MusicFormat = "Music";
+    private static final String OPLFormat = "OPL";
+
+    private static final int AudioSampleRates[] = { 11025, 22050, 44100 };
+    private static final int AudioBufferSizes[] = { 512, 1024, 2048, 4096, 8192 };
+    private static final int OPLSampleRates[] = { 12429, 24858, 49716 };
+    private static final String CDFormats[] = { "MP3", "OGG" };
+    private static final String MusicFormats[] = { "MIDI", "RIX", "MP3", "OGG" };
+    private static final String OPLFormats[] = { "DOSBOX", "MAME", "DOSBOXNEW" };
+
+    private SettingsActivity mInstance = this;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_settings);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        ((SwitchCompat)findViewById(R.id.swCustomLang)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                findViewById(R.id.swGameLang).setVisibility(isChecked ? View.GONE : View.VISIBLE);
+                findViewById(R.id.edLangFile).setVisibility(isChecked ? View.VISIBLE : View.GONE);
+            }
+        });
+
+        ((AppCompatSpinner)findViewById(R.id.spMusFmt)).setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                findViewById(R.id.layoutOPL).setVisibility(position == 1 ? View.VISIBLE : View.GONE);
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+                findViewById(R.id.layoutOPL).setVisibility(View.VISIBLE);
+            }
+        });
+
+        findViewById(R.id.btnDefault).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setDefaults();
+            }
+        });
+
+        findViewById(R.id.btnFinish).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (!setConfigs()) return;
+                setConfigBoolean(LaunchSetting, false);
+                saveConfigFile();
+
+                AlertDialog.Builder builder = new AlertDialog.Builder(mInstance);
+                builder.setMessage(R.string.msg_exit);
+                builder.setCancelable(false);
+                builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, int i) {
+                        System.exit(0);
+                    }
+                });
+                builder.create().show();
+            }
+        });
+
+        setDefaults();
+
+        if (PalActivity.crashed) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setMessage(R.string.msg_crash);
+            builder.setCancelable(false);
+            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialogInterface, int i) {
+                    dialogInterface.dismiss();
+                }
+            });
+            builder.create().show();
+        }
+    }
+
+    protected int findMatchedIntIndex(int value, int[] values, int defaultIndex) {
+        for(int i = 0; i < values.length; i++) {
+            if (values[i] == value)
+                return i;
+        }
+        return defaultIndex;
+    }
+
+    protected int findMatchedStringIndex(String value, String[] values, int defaultIndex) {
+        for(int i = 0; i < values.length; i++) {
+            if (values[i].equals(value))
+                return i;
+        }
+        return defaultIndex;
+    }
+
+    protected void setDefaults() {
+        findViewById(R.id.edLangFile).setVisibility(View.GONE);
+        findViewById(R.id.layoutOPL).setVisibility(View.VISIBLE);
+
+        ((SeekBar)findViewById(R.id.sbMusVol)).setProgress(getConfigInt(MusicVolume));
+        ((SeekBar)findViewById(R.id.sbSFXVol)).setProgress(getConfigInt(SoundVolume));
+        ((SeekBar)findViewById(R.id.sbQuality)).setProgress(getConfigInt(ResampleQuality)); // Best quality
+
+        String langFile = getConfigString(MessageFileName);
+        ((EditText)findViewById(R.id.edFolder)).setText(getConfigString(GamePath));
+        ((EditText)findViewById(R.id.edLangFile)).setText(langFile);
+
+        ((SwitchCompat)findViewById(R.id.swEmbedFont)).setChecked(getConfigBoolean(UseEmbeddedFonts));
+        ((SwitchCompat)findViewById(R.id.swCustomLang)).setChecked(langFile != null && !langFile.isEmpty());
+        ((SwitchCompat)findViewById(R.id.swGameLang)).setChecked(getConfigInt(CodePage) != 0);
+        ((SwitchCompat)findViewById(R.id.swTouch)).setChecked(getConfigBoolean(UseTouchOverlay));
+        ((SwitchCompat)findViewById(R.id.swAspect)).setChecked(getConfigBoolean(KeepAspectRatio));
+        ((SwitchCompat)findViewById(R.id.swSurround)).setChecked(getConfigBoolean(UseSurroundOPL));
+
+        ((AppCompatSpinner)findViewById(R.id.spSample)).setSelection(findMatchedIntIndex(getConfigInt(SampleRate), AudioSampleRates, 2));    // 44100Hz
+        ((AppCompatSpinner)findViewById(R.id.spBuffer)).setSelection(findMatchedIntIndex(getConfigInt(AudioBufferSize), AudioBufferSizes, 1));    // 1024
+        ((AppCompatSpinner)findViewById(R.id.spCDFmt)).setSelection(findMatchedStringIndex(getConfigString(CDFormat), CDFormats, 1));     // OGG
+        ((AppCompatSpinner)findViewById(R.id.spMusFmt)).setSelection(findMatchedStringIndex(getConfigString(MusicFormat), MusicFormats, 1));    // RIX
+        ((AppCompatSpinner)findViewById(R.id.spOPL)).setSelection(findMatchedStringIndex(getConfigString(OPLFormat), OPLFormats, 1));       // MAME
+        ((AppCompatSpinner)findViewById(R.id.spOPLRate)).setSelection(findMatchedIntIndex(getConfigInt(OPLSampleRate), OPLSampleRates, 2));  // 49716Hz
+    }
+
+
+    protected boolean setConfigs() {
+        if (((EditText)findViewById(R.id.edFolder)).getText().toString().isEmpty()) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setMessage(R.string.msg_empty);
+            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialogInterface, int i) {
+                    dialogInterface.dismiss();
+                }
+            });
+            builder.create().show();
+            return false;
+        }
+
+        setConfigInt(MusicVolume, ((SeekBar)findViewById(R.id.sbMusVol)).getProgress());
+        setConfigInt(SoundVolume, ((SeekBar)findViewById(R.id.sbSFXVol)).getProgress());
+        setConfigInt(ResampleQuality, ((SeekBar)findViewById(R.id.sbQuality)).getProgress());
+
+        setConfigString(GamePath, ((EditText)findViewById(R.id.edFolder)).getText().toString());
+        setConfigString(SavePath, ((EditText)findViewById(R.id.edFolder)).getText().toString());
+        setConfigString(MessageFileName, ((EditText)findViewById(R.id.edLangFile)).getText().toString());
+
+        setConfigBoolean(UseEmbeddedFonts, ((SwitchCompat)findViewById(R.id.swEmbedFont)).isChecked());
+        setConfigInt(CodePage, ((SwitchCompat)findViewById(R.id.swGameLang)).isChecked() ? 1 : 0);
+        setConfigBoolean(UseTouchOverlay, ((SwitchCompat)findViewById(R.id.swTouch)).isChecked());
+        setConfigBoolean(KeepAspectRatio, ((SwitchCompat)findViewById(R.id.swAspect)).isChecked());
+        setConfigBoolean(UseSurroundOPL, ((SwitchCompat)findViewById(R.id.swSurround)).isChecked());
+
+        setConfigInt(SampleRate, Integer.parseInt((String)((AppCompatSpinner)findViewById(R.id.spSample)).getSelectedItem()));
+        setConfigInt(AudioBufferSize, Integer.parseInt((String)((AppCompatSpinner)findViewById(R.id.spBuffer)).getSelectedItem()));
+        setConfigString(CDFormat, (String)((AppCompatSpinner)findViewById(R.id.spCDFmt)).getSelectedItem());
+        setConfigString(MusicFormat, (String)((AppCompatSpinner)findViewById(R.id.spMusFmt)).getSelectedItem());
+        setConfigString(OPLFormat, (String)((AppCompatSpinner)findViewById(R.id.spOPL)).getSelectedItem());
+        setConfigInt(OPLSampleRate, Integer.parseInt((String)((AppCompatSpinner)findViewById(R.id.spOPLRate)).getSelectedItem()));
+
+        return true;
+    }
+}

+ 27 - 0
android/app/src/main/res/layout/activity_settings.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="io.github.myapplication.SettingsActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay"
+            app:subtitle="@string/title_settings"
+            app:title="SDLPal" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_settings" />
+
+</android.support.design.widget.CoordinatorLayout>

+ 256 - 0
android/app/src/main/res/layout/content_settings.xml

@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.widget.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context="io.github.myapplication.SettingsActivity"
+    tools:showIn="@layout/activity_settings">
+
+    <ScrollView
+        android:id="@+id/svParent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            >
+
+            <TextView
+                android:id="@+id/tvFolder"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/label_folder"
+                tools:layout_editor_absoluteX="8dp"
+                tools:layout_editor_absoluteY="-1dp" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="horizontal">
+
+                <EditText
+                    android:id="@+id/edFolder"
+                    android:layout_width="368dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:ems="10"
+                    android:inputType="textUri"
+                    tools:layout_editor_absoluteX="8dp"
+                    tools:layout_editor_absoluteY="16dp" />
+
+            </LinearLayout>
+
+            <android.support.v7.widget.SwitchCompat
+                android:id="@+id/swEmbedFont"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/action_embedded_font"
+                tools:layout_editor_absoluteX="8dp"
+                tools:layout_editor_absoluteY="121dp" />
+
+            <android.support.v7.widget.SwitchCompat
+                android:id="@+id/swCustomLang"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/action_custom_lang"
+                tools:layout_editor_absoluteX="8dp"
+                tools:layout_editor_absoluteY="191dp" />
+
+            <android.support.v7.widget.SwitchCompat
+                android:id="@+id/swGameLang"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/action_lang"
+                android:textOff="@string/switch_traditional"
+                android:textOn="@string/switch_simplified"
+                app:showText="true"
+                tools:layout_editor_absoluteX="8dp"
+                tools:layout_editor_absoluteY="155dp" />
+
+            <EditText
+                android:id="@+id/edLangFile"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:ems="10"
+                android:inputType="textUri" />
+
+            <android.support.v7.widget.SwitchCompat
+                android:id="@+id/swTouch"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/action_touch"
+                tools:layout_editor_absoluteX="8dp"
+                tools:layout_editor_absoluteY="228dp" />
+
+            <android.support.v7.widget.SwitchCompat
+                android:id="@+id/swAspect"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/action_aspect"
+                tools:layout_editor_absoluteX="8dp"
+                tools:layout_editor_absoluteY="269dp" />
+
+            <android.support.v7.widget.SwitchCompat
+                android:id="@+id/swStereo"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/action_stereo"
+                tools:layout_editor_absoluteX="8dp"
+                tools:layout_editor_absoluteY="311dp" />
+
+            <TextView
+                android:id="@+id/tvMusVol"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_musvol" />
+
+            <SeekBar
+                android:id="@+id/sbMusVol"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:max="100" />
+
+            <TextView
+                android:id="@+id/tvSFXVol"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_sfxvol" />
+
+            <SeekBar
+                android:id="@+id/sbSFXVol"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:max="100" />
+
+            <TextView
+                android:id="@+id/tvQuality"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_quality" />
+
+            <SeekBar
+                android:id="@+id/sbQuality"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:max="4" />
+
+            <TextView
+                android:id="@+id/tvSample"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_audrate" />
+
+            <android.support.v7.widget.AppCompatSpinner
+                android:id="@+id/spSample"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/audio_rate"/>
+
+            <TextView
+                android:id="@+id/tvBuffer"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_bufsize" />
+
+            <android.support.v7.widget.AppCompatSpinner
+                android:id="@+id/spBuffer"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/buffer_size" />
+
+            <TextView
+                android:id="@+id/tvCDFmt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_cdfmt" />
+
+            <android.support.v7.widget.AppCompatSpinner
+                android:id="@+id/spCDFmt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/cd_format" />
+
+            <TextView
+                android:id="@+id/tvMusFmt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_musfmt" />
+
+            <android.support.v7.widget.AppCompatSpinner
+                android:id="@+id/spMusFmt"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/music_format" />
+
+            <LinearLayout
+                android:id="@+id/layoutOPL"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/tvOPLRate"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/label_oplrate" />
+
+                <android.support.v7.widget.AppCompatSpinner
+                    android:id="@+id/spOPLRate"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:entries="@array/opl_rate" />
+
+                <TextView
+                    android:id="@+id/tvOPL"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/label_opl" />
+
+                <android.support.v7.widget.AppCompatSpinner
+                    android:id="@+id/spOPL"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:entries="@array/opl_type" />
+
+                <android.support.v7.widget.SwitchCompat
+                    android:id="@+id/swSurround"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/action_suropl" />
+            </LinearLayout>
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+                <Button
+                    android:id="@+id/btnDefault"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentLeft="true"
+                    android:layout_alignParentStart="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginLeft="20dp"
+                    android:layout_marginStart="20dp"
+                    android:text="@string/action_default" />
+
+                <Button
+                    android:id="@+id/btnFinish"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_alignParentRight="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginEnd="20dp"
+                    android:layout_marginRight="20dp"
+                    android:text="@string/action_finish" />
+            </RelativeLayout>
+
+        </LinearLayout>
+    </ScrollView>
+</android.widget.LinearLayout>

+ 1 - 1
android/app/src/main/res/layout/main.xml

@@ -7,7 +7,7 @@
 <TextView  
     android:layout_width="fill_parent" 
     android:layout_height="wrap_content" 
-    android:text="Hello World, SDLActivity"
+    android:text="PALActivity"
     />
 </LinearLayout>
 

+ 29 - 0
android/app/src/main/res/values-zh-rTW/strings.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="label_folder">游戏资源所在文件夹:</string>
+    <string name="action_browse">瀏覽</string>
+    <string name="action_embedded_font">使用遊戲資源內置字體</string>
+    <string name="action_aspect">保持縱橫比</string>
+    <string name="label_bufsize">音訊緩衝區大小</string>
+    <string name="label_musfmt">背景音樂格式</string>
+    <string name="action_default">默認設定</string>
+    <string name="action_finish">完成設定</string>
+    <string name="label_cdfmt">CD 音軌格式</string>
+    <string name="switch_traditional">繁</string>
+    <string name="action_lang">遊戲資源語言</string>
+    <string name="action_custom_lang">自訂語言檔案</string>
+    <string name="label_quality">音訊品質</string>
+    <string name="label_audrate">音訊輸出取樣速率</string>
+    <string name="action_stereo">立體聲</string>
+    <string name="action_suropl">環繞聲 OPL</string>
+    <string name="action_touch">啟用觸屏輔助</string>
+    <string name="title_settings">設定模式</string>
+    <string name="label_sfxvol">音效音量</string>
+    <string name="label_musvol">音樂音量</string>
+    <string name="label_opl">OPL 模擬器</string>
+    <string name="label_oplrate">OPL 模擬器取樣速率</string>
+    <string name="msg_crash">上次程式異常退出,已顯示設定頁供您檢查設定是否正確。</string>
+    <string name="msg_empty">必須指定遊戲資源檔案夾!</string>
+    <string name="msg_exit">您的設定已保存,下次啟動時將直接開始遊戲。在遊戲中,您可以通過遊戲系統功能表的返回設定選項回到本設定頁面。</string>
+    <string name="switch_simplified">簡</string>
+</resources>

+ 29 - 0
android/app/src/main/res/values-zh/strings.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="label_folder">游戏资源所在文件夹:</string>
+    <string name="action_browse">浏览</string>
+    <string name="action_embedded_font">使用游戏资源内置字体</string>
+    <string name="title_settings">设置模式</string>
+    <string name="action_custom_lang">自定义语言文件</string>
+    <string name="action_lang">游戏资源语言</string>
+    <string name="switch_traditional">繁</string>
+    <string name="action_touch">启用触屏辅助</string>
+    <string name="action_aspect">保持纵横比</string>
+    <string name="action_default">默认设置</string>
+    <string name="action_finish">完成设置</string>
+    <string name="action_suropl">环绕声 OPL</string>
+    <string name="label_oplrate">OPL 模拟器采样率</string>
+    <string name="label_opl">OPL 模拟器</string>
+    <string name="label_musfmt">背景音乐格式</string>
+    <string name="label_cdfmt">CD 音轨格式</string>
+    <string name="label_bufsize">音频缓冲区大小</string>
+    <string name="label_audrate">音频输出采样率</string>
+    <string name="label_quality">音频质量</string>
+    <string name="label_sfxvol">音效音量</string>
+    <string name="label_musvol">音乐音量</string>
+    <string name="action_stereo">立体声</string>
+    <string name="msg_crash">上次程序异常退出,已显示设置页供您检查设置是否正确。</string>
+    <string name="msg_empty">必须指定游戏资源文件夹!</string>
+    <string name="msg_exit">您的设置已保存,下次启动时将直接开始游戏。在游戏中,您可以通过游戏系统菜单中的返回设置选项回到本设置页面。</string>
+    <string name="switch_simplified">简</string>
+</resources>

+ 34 - 0
android/app/src/main/res/values/arrays.xml

@@ -0,0 +1,34 @@
+<resources>
+    <string-array name="audio_rate">
+        <item>11025</item>
+        <item>22050</item>
+        <item>44100</item>
+    </string-array>
+    <string-array name="buffer_size">
+        <item>512</item>
+        <item>1024</item>
+        <item>2048</item>
+        <item>4096</item>
+        <item>8192</item>
+    </string-array>
+    <string-array name="cd_format">
+        <item>MP3</item>
+        <item>OGG</item>
+    </string-array>
+    <string-array name="music_format">
+        <item>MIDI</item>
+        <item>RIX</item>
+        <item>MP3</item>
+        <item>OGG</item>
+    </string-array>
+    <string-array name="opl_type">
+        <item>DOSBOX</item>
+        <item>MAME</item>
+        <item>DOSBOXNEW</item>
+    </string-array>
+    <string-array name="opl_rate">
+        <item>12429</item>
+        <item>24858</item>
+        <item>49716</item>
+    </string-array>
+</resources>

+ 6 - 0
android/app/src/main/res/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>

+ 3 - 0
android/app/src/main/res/values/dimens.xml

@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>

+ 27 - 2
android/app/src/main/res/values/strings.xml

@@ -1,4 +1,29 @@
-<?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="app_name">SDLPal</string>
+    <string name="app_name" translatable="false">SDLPal</string>
+    <string name="label_folder">Game resource folder:</string>
+    <string name="action_browse">Browse</string>
+    <string name="action_embedded_font">Using embedded font</string>
+    <string name="title_settings">Settings</string>
+    <string name="action_custom_lang">Use language file</string>
+    <string name="action_lang">Game language</string>
+    <string name="switch_traditional">CHT</string>
+    <string name="switch_simplified">CHS</string>
+    <string name="action_touch">Enable touch overlay</string>
+    <string name="action_aspect">Keep aspect ratio</string>
+    <string name="action_stereo">Stereo</string>
+    <string name="label_musvol">Music volume</string>
+    <string name="label_sfxvol">SFX volume</string>
+    <string name="label_quality">Audio quality</string>
+    <string name="label_audrate">Audio samplerate</string>
+    <string name="label_bufsize">Audio buffer size</string>
+    <string name="label_cdfmt">CD track format</string>
+    <string name="label_musfmt">Music format</string>
+    <string name="label_oplrate">OPL emulator\'s samplerate</string>
+    <string name="label_opl">OPL emulator</string>
+    <string name="action_suropl">Surround OPL</string>
+    <string name="action_default">Default values</string>
+    <string name="action_finish">Finish Setting</string>
+    <string name="msg_exit">Settings have been saved and the game will be started on next launch. You can use the main menu option inside the game to return to this page.</string>
+    <string name="msg_crash">The program is abnormally terminated last time. The setting page has been launched for you. Please check if there are any incorrect settings.</string>
+    <string name="msg_empty">The game resource folder must be specified!</string>
 </resources>

+ 20 - 0
android/app/src/main/res/values/styles.xml

@@ -0,0 +1,20 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>

+ 127 - 2
palcfg.c

@@ -69,6 +69,9 @@ static const ConfigItem gConfigItems[PALCFG_ALL_MAX] = {
 	{ PALCFG_CLIMIDIPLAYER,     PALCFG_STRING,   "CLIMIDIPlayer",     13, MAKE_VALUE(NULL,     NULL, NULL) },
 };
 
+static const char *music_types[] = { "MIDI", "RIX", "MP3", "OGG", "RAW" };
+static const char *opl_types[] = { "DOSBOX", "MAME", "DOSBOXNEW" };
+
 
 static char * ParseStringValue(const char *sValue, char *original)
 {
@@ -455,8 +458,6 @@ PAL_SaveConfig(
 	void
 )
 {
-	static const char *music_types[] = { "MIDI", "RIX", "MP3", "OGG", "RAW" };
-	static const char *opl_types[] = { "DOSBOX", "MAME", "DOSBOXNEW" };
 	char buf[512];
 	FILE *fp = fopen(va("%ssdlpal.cfg", PAL_CONFIG_PREFIX), "w");
 
@@ -501,3 +502,127 @@ PAL_SaveConfig(
 	else
 		return FALSE;
 }
+
+BOOL
+PAL_GetConfigItem(
+	const char  *szName,
+	ConfigValue *value
+)
+{
+	for (int index = PALCFG_ALL_MIN; index < PALCFG_ALL_MAX; index++)
+	{
+		if (SDL_strcasecmp(gConfigItems[index].Name, szName) == 0)
+		{
+			switch (index)
+			{
+			case PALCFG_FULLSCREEN:        value->bValue = gConfig.fFullScreen; return TRUE;
+			case PALCFG_KEEPASPECTRATIO:   value->bValue = gConfig.fKeepAspectRatio; return TRUE;
+			case PALCFG_LAUNCHSETTING:     value->bValue = gConfig.fLaunchSetting; return TRUE;
+			case PALCFG_STEREO:            value->bValue = (gConfig.iAudioChannels == 2); return TRUE;
+			case PALCFG_USEEMBEDDEDFONTS:  value->bValue = gConfig.fUseEmbeddedFonts; return TRUE;
+			case PALCFG_USESURROUNDOPL:    value->bValue = gConfig.fUseSurroundOPL; return TRUE;
+			case PALCFG_USETOUCHOVERLAY:   value->bValue = gConfig.fUseTouchOverlay; return TRUE;
+			case PALCFG_SURROUNDOPLOFFSET: value->iValue = gConfig.iSurroundOPLOffset; return TRUE;
+			case PALCFG_AUDIOBUFFERSIZE:   value->uValue = gConfig.wAudioBufferSize; return TRUE;
+			case PALCFG_CODEPAGE:          value->uValue = gConfig.uCodePage; return TRUE;
+			case PALCFG_OPLSAMPLERATE:     value->uValue = gConfig.iOPLSampleRate; return TRUE;
+			case PALCFG_RESAMPLEQUALITY:   value->uValue = gConfig.iResampleQuality; return TRUE;
+			case PALCFG_SAMPLERATE:        value->uValue = gConfig.iSampleRate; return TRUE;
+			case PALCFG_MUSICVOLUME:       value->uValue = gConfig.iMusicVolume; return TRUE;
+			case PALCFG_SOUNDVOLUME:       value->uValue = gConfig.iSoundVolume; return TRUE;
+			case PALCFG_WINDOWHEIGHT:      value->uValue = gConfig.dwScreenHeight; return TRUE;
+			case PALCFG_WINDOWWIDTH:       value->uValue = gConfig.dwScreenWidth; return TRUE;
+			case PALCFG_CD:                value->sValue = music_types[gConfig.eCDType]; return TRUE;
+			case PALCFG_GAMEPATH:          value->sValue = gConfig.pszGamePath; return TRUE;
+			case PALCFG_SAVEPATH:          value->sValue = gConfig.pszSavePath; return TRUE;
+			case PALCFG_MESSAGEFILE:       value->sValue = gConfig.pszMsgFile; return TRUE;
+			case PALCFG_BDFFILE:           value->sValue = gConfig.pszBdfFile; return TRUE;
+			case PALCFG_MUSIC:             value->sValue = music_types[gConfig.eMusicType]; return TRUE;
+			case PALCFG_OPL:               value->sValue = opl_types[gConfig.eOPLType]; return TRUE;
+			}
+		}
+	}
+	return FALSE;
+}
+
+
+BOOL
+PAL_SetConfigItem(
+	const char  *szName,
+	const ConfigValue *value
+)
+{
+	for (int index = PALCFG_ALL_MIN; index < PALCFG_ALL_MAX; index++)
+	{
+		if (SDL_strcasecmp(gConfigItems[index].Name, szName) == 0)
+		{
+			switch (index)
+			{
+			case PALCFG_FULLSCREEN:        gConfig.fFullScreen = value->bValue; return TRUE;
+			case PALCFG_KEEPASPECTRATIO:   gConfig.fKeepAspectRatio = value->bValue; return TRUE;
+			case PALCFG_LAUNCHSETTING:     gConfig.fLaunchSetting = value->bValue; return TRUE;
+			case PALCFG_STEREO:            gConfig.iAudioChannels = value->bValue ? 2 : 1; return TRUE;
+			case PALCFG_USEEMBEDDEDFONTS:  gConfig.fUseEmbeddedFonts = value->bValue; return TRUE;
+			case PALCFG_USESURROUNDOPL:    gConfig.fUseSurroundOPL = value->bValue; return TRUE;
+			case PALCFG_USETOUCHOVERLAY:   gConfig.fUseTouchOverlay = value->bValue; return TRUE;
+			case PALCFG_SURROUNDOPLOFFSET: gConfig.iSurroundOPLOffset = value->iValue; return TRUE;
+			case PALCFG_AUDIOBUFFERSIZE:   gConfig.wAudioBufferSize = value->uValue; return TRUE;
+			case PALCFG_CODEPAGE:          gConfig.uCodePage = value->uValue; return TRUE;
+			case PALCFG_OPLSAMPLERATE:     gConfig.iOPLSampleRate = value->uValue; return TRUE;
+			case PALCFG_RESAMPLEQUALITY:   gConfig.iResampleQuality = value->uValue; return TRUE;
+			case PALCFG_SAMPLERATE:        gConfig.iSampleRate = value->uValue; return TRUE;
+			case PALCFG_MUSICVOLUME:       gConfig.iMusicVolume = value->uValue; return TRUE;
+			case PALCFG_SOUNDVOLUME:       gConfig.iSoundVolume = value->uValue; return TRUE;
+			case PALCFG_WINDOWHEIGHT:      gConfig.dwScreenHeight = value->uValue; return TRUE;
+			case PALCFG_WINDOWWIDTH:       gConfig.dwScreenWidth = value->uValue; return TRUE;
+			case PALCFG_GAMEPATH:
+				if (gConfig.pszGamePath) free(gConfig.pszGamePath);
+				gConfig.pszGamePath = value->sValue && value->sValue[0] ? strdup(value->sValue) : strdup(PAL_SAVE_PREFIX);
+				return TRUE;
+			case PALCFG_SAVEPATH:
+				if (gConfig.pszSavePath) free(gConfig.pszSavePath);
+				gConfig.pszSavePath = value->sValue && value->sValue[0] ? strdup(value->sValue) : (gConfig.pszGamePath ? strdup(gConfig.pszGamePath) : strdup(PAL_SAVE_PREFIX));
+				return TRUE;
+			case PALCFG_MESSAGEFILE:
+				if (gConfig.pszMsgFile) free(gConfig.pszMsgFile);
+				gConfig.pszMsgFile = value->sValue && value->sValue[0] ? strdup(value->sValue) : NULL;
+				return TRUE;
+			case PALCFG_BDFFILE:
+				if (gConfig.pszBdfFile) free(gConfig.pszBdfFile);
+				gConfig.pszBdfFile = value->sValue && value->sValue[0] ? strdup(value->sValue) : NULL;
+				return TRUE;
+			case PALCFG_CD:
+				for (int i = 0; i < sizeof(music_types) / sizeof(music_types[0]); i++)
+				{
+					if (SDL_strcasecmp(value->sValue, music_types[i]) == 0)
+					{
+						gConfig.eCDType = (MUSICTYPE)i;
+						return TRUE;
+					}
+				}
+				break;
+			case PALCFG_MUSIC:
+				for (int i = 0; i < sizeof(music_types) / sizeof(music_types[0]); i++)
+				{
+					if (SDL_strcasecmp(value->sValue, music_types[i]) == 0)
+					{
+						gConfig.eMusicType = (MUSICTYPE)i;
+						return TRUE;
+					}
+				}
+				break;
+			case PALCFG_OPL:
+				for (int i = 0; i < sizeof(opl_types) / sizeof(opl_types[0]); i++)
+				{
+					if (SDL_strcasecmp(value->sValue, opl_types[i]) == 0)
+					{
+						gConfig.eOPLType = (OPLTYPE)i;
+						return TRUE;
+					}
+				}
+				break;
+			}
+		}
+	}
+	return FALSE;
+}

+ 12 - 0
palcfg.h

@@ -243,6 +243,18 @@ PAL_LimitConfig(
 	ConfigValue * pValue
 );
 
+BOOL
+PAL_GetConfigItem(
+	const char  *szName,
+	ConfigValue *value
+);
+
+BOOL
+PAL_SetConfigItem(
+	const char  *szName,
+	const ConfigValue *value
+);
+
 PAL_C_LINKAGE_END
 
 #endif