Browse Source

Merge branch 'master' of https://github.com/sdlpal/sdlpal

Yihua LOU 7 years ago
parent
commit
968ad0f535
39 changed files with 1520 additions and 868 deletions
  1. 4 4
      .travis.yml
  2. 3 3
      android/app/build.gradle
  3. 0 0
      android/app/proguard-rules.txt
  4. 12 28
      android/app/src/main/cpp/android_jni.cpp
  5. 1 1
      android/app/src/main/cpp/pal_config.h
  6. 47 20
      android/app/src/main/java/io/github/sdlpal/SettingsActivity.java
  7. 34 26
      android/app/src/main/res/layout/content_settings.xml
  8. 10 0
      android/app/src/main/res/values-zh-rTW/arrays.xml
  9. 4 5
      android/app/src/main/res/values-zh-rTW/strings.xml
  10. 10 0
      android/app/src/main/res/values-zh/arrays.xml
  11. 4 5
      android/app/src/main/res/values-zh/strings.xml
  12. 14 6
      android/app/src/main/res/values/arrays.xml
  13. 4 5
      android/app/src/main/res/values/strings.xml
  14. 20 8
      font.c
  15. 11 56
      global.c
  16. 1 1
      global.h
  17. 276 141
      palcfg.c
  18. 77 17
      palcfg.h
  19. 66 0
      text.c
  20. 8 0
      text.h
  21. 6 4
      unix/native_midi.cpp
  22. 70 68
      unix/unix.cpp
  23. 18 12
      win32/resource.h
  24. 185 200
      win32/sdlpal.rc
  25. 3 3
      win32/sdlpal.vcxproj.filters
  26. 104 16
      win32/win32.cpp
  27. 57 17
      winrt/SDLPal.Common/MainPage.xaml
  28. 123 33
      winrt/SDLPal.Common/MainPage.xaml.cpp
  29. 42 4
      winrt/SDLPal.Common/MainPage.xaml.h
  30. 3 0
      winrt/SDLPal.Common/SDLPal.Common.def
  31. 11 6
      winrt/SDLPal.Common/StringHelper.h
  32. 47 38
      winrt/SDLPal.Common/Strings/en/Resources.resw
  33. 45 36
      winrt/SDLPal.Common/Strings/zh-hans/Resources.resw
  34. 45 36
      winrt/SDLPal.Common/Strings/zh-hant/Resources.resw
  35. 93 26
      winrt/SDLPal.Common/WinRTIO.cpp
  36. 38 27
      winrt/SDLPal.Common/WinRTUtil.cpp
  37. 22 12
      winrt/SDLPal.WindowsPhone/App.xaml.cpp
  38. 0 1
      winrt/SDLPal.WindowsPhone/App.xaml.h
  39. 2 3
      winrt/pal_config.h

+ 4 - 4
.travis.yml

@@ -53,8 +53,8 @@ matrix:
           components:
             - platform-tools
             - tools
-            - build-tools-25.0.2
-            - android-24
+            - build-tools-25.0.3
+            - android-25
             - extra-android-m2repository
         jdk: oraclejdk8
         before_install:
@@ -64,8 +64,8 @@ matrix:
           - export PATH=${PATH}:${ANDROID_NDK_HOME}
         script:
           - cd android
-          - ./gradlew assembleDebug
-          - mv app/build/outputs/apk/app-debug.apk ../deploy/sdlpal-debug.apk
+          - ./gradlew assembleRelease
+          - mv app/build/outputs/apk/app-release-unsigned.apk ../deploy/sdlpal-release.apk
           - cd ..
 
       - os: osx

+ 3 - 3
android/app/build.gradle

@@ -1,8 +1,8 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 24
-    buildToolsVersion "25.0.2"
+    compileSdkVersion 25
+    buildToolsVersion "25.0.3"
 
     defaultConfig {
         applicationId "io.github.sdlpal"
@@ -18,7 +18,7 @@ android {
 
     buildTypes {
         release {
-            minifyEnabled true
+            minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
         }
         debug {

+ 0 - 0
android/app/proguard-rules.txt


+ 12 - 28
android/app/src/main/cpp/android_jni.cpp

@@ -114,9 +114,8 @@ JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_saveConfigFile
 EXTERN_C_LINKAGE
 JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigBoolean(JNIEnv *env, jclass cls, jstring j_str, jboolean defval)
 {
-    ConfigValue value;
-    std::string name = jstring_to_utf8(env, j_str);
-    return PAL_GetConfigItem(name.c_str(), &value, defval) ? value.bValue : JNI_FALSE;
+    PALCFG_ITEM item = PAL_ConfigIndex(jstring_to_utf8(env, j_str).c_str());
+    return item >= 0 ? PAL_GetConfigBoolean(item, defval) : JNI_FALSE;
 }
 
 /*
@@ -127,9 +126,8 @@ JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigBoole
 EXTERN_C_LINKAGE
 JNIEXPORT int JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigInt(JNIEnv *env, jclass cls, jstring j_str, jboolean defval)
 {
-    ConfigValue value;
-    std::string name = jstring_to_utf8(env, j_str);
-    return PAL_GetConfigItem(name.c_str(), &value, defval) ? value.iValue : JNI_FALSE;
+    PALCFG_ITEM item = PAL_ConfigIndex(jstring_to_utf8(env, j_str).c_str());
+    return item >= 0 ? (int)PAL_GetConfigNumber(item, defval) : 0;
 }
 
 /*
@@ -140,9 +138,8 @@ JNIEXPORT int JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigInt(JNIEnv
 EXTERN_C_LINKAGE
 JNIEXPORT jstring JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigString(JNIEnv *env, jclass cls, jstring j_str, jboolean defval)
 {
-    ConfigValue value;
-    std::string name = jstring_to_utf8(env, j_str);
-    return PAL_GetConfigItem(name.c_str(), &value, defval) ? env->NewStringUTF(value.sValue) : nullptr;
+    PALCFG_ITEM item = PAL_ConfigIndex(jstring_to_utf8(env, j_str).c_str());
+    return item >= 0 ? env->NewStringUTF(PAL_GetConfigString(item, defval)) : nullptr;
 }
 
 /*
@@ -153,9 +150,8 @@ JNIEXPORT jstring JNICALL Java_io_github_sdlpal_SettingsActivity_getConfigString
 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;
+    PALCFG_ITEM item = PAL_ConfigIndex(jstring_to_utf8(env, j_str).c_str());
+    return item >= 0 ? PAL_SetConfigBoolean(item, val ? TRUE : FALSE) : JNI_FALSE;
 }
 
 /*
@@ -166,9 +162,8 @@ JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_setConfigBoole
 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;
+    PALCFG_ITEM item = PAL_ConfigIndex(jstring_to_utf8(env, j_str).c_str());
+    return item >= 0 ? PAL_SetConfigNumber(item, (long)val) : JNI_FALSE;
 }
 
 /*
@@ -179,10 +174,8 @@ JNIEXPORT jboolean JNICALL Java_io_github_sdlpal_SettingsActivity_setConfigInt(J
 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;
+    PALCFG_ITEM item = PAL_ConfigIndex(jstring_to_utf8(env, j_str).c_str());
+    return item >= 0 ? PAL_SetConfigString(item, v_str ? jstring_to_utf8(env, v_str).c_str() : nullptr) : JNI_FALSE;
 }
 
 EXTERN_C_LINKAGE
@@ -246,15 +239,6 @@ UTIL_BasePath(
     return g_basepath.c_str();
 }
 
-EXTERN_C_LINKAGE
-LPCSTR
-UTIL_SavePath(
-   VOID
-)
-{
-    return g_basepath.c_str();
-}
-
 EXTERN_C_LINKAGE
 LPCSTR
 UTIL_ConfigPath(

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

@@ -27,7 +27,7 @@
 # define PAL_CONFIG_H
 
 # define PAL_PREFIX            UTIL_BasePath()
-# define PAL_SAVE_PREFIX       UTIL_SavePath()
+# define PAL_SAVE_PREFIX       UTIL_BasePath()
 # define PAL_CONFIG_PREFIX     UTIL_ConfigPath()
 # define PAL_HAS_TOUCH         1
 # define PAL_DEFAULT_WINDOW_WIDTH   320

+ 47 - 20
android/app/src/main/java/io/github/sdlpal/SettingsActivity.java

@@ -10,11 +10,14 @@ import android.support.v7.widget.Toolbar;
 import  android.support.v7.app.AlertDialog;
 import android.view.View;
 import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
 import android.widget.CompoundButton;
 import android.widget.EditText;
 import android.widget.SeekBar;
 import android.widget.Spinner;
 
+import java.util.ArrayList;
+
 public class SettingsActivity extends AppCompatActivity {
 
     public static native boolean loadConfigFile();
@@ -26,15 +29,13 @@ public class SettingsActivity extends AppCompatActivity {
     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 LogLevel = "LogLevel";
     private static final String OPLSampleRate = "OPLSampleRate";
     private static final String ResampleQuality = "ResampleQuality";
     private static final String SampleRate = "SampleRate";
@@ -44,6 +45,8 @@ public class SettingsActivity extends AppCompatActivity {
     private static final String GamePath = "GamePath";
     private static final String SavePath = "SavePath";
     private static final String MessageFileName = "MessageFileName";
+    private static final String LogFileName = "LogFileName";
+    private static final String FontFileName = "FontFileName";
     private static final String MusicFormat = "Music";
     private static final String OPLFormat = "OPL";
 
@@ -63,11 +66,24 @@ public class SettingsActivity extends AppCompatActivity {
         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
         setSupportActionBar(toolbar);
 
-        ((SwitchCompat)findViewById(R.id.swCustomLang)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+        ((SwitchCompat)findViewById(R.id.swMsgFile)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                findViewById(R.id.edMsgFile).setVisibility(isChecked ? View.VISIBLE : View.GONE);
+            }
+        });
+
+        ((SwitchCompat)findViewById(R.id.swFontFile)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                findViewById(R.id.edFontFile).setVisibility(isChecked ? View.VISIBLE : View.GONE);
+            }
+        });
+
+        ((SwitchCompat)findViewById(R.id.swLogFile)).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);
+                findViewById(R.id.edLogFile).setVisibility(isChecked ? View.VISIBLE : View.GONE);
             }
         });
 
@@ -152,7 +168,9 @@ public class SettingsActivity extends AppCompatActivity {
     protected void setDefaults() {
         String sdcardState = Environment.getExternalStorageState();
 
-        findViewById(R.id.edLangFile).setVisibility(View.GONE);
+        findViewById(R.id.edMsgFile).setVisibility(View.GONE);
+        findViewById(R.id.edFontFile).setVisibility(View.GONE);
+        findViewById(R.id.edLogFile).setVisibility(View.GONE);
         findViewById(R.id.layoutOPL).setVisibility(View.VISIBLE);
 
         ((SeekBar)findViewById(R.id.sbMusVol)).setProgress(getConfigInt(MusicVolume, true));
@@ -164,16 +182,19 @@ public class SettingsActivity extends AppCompatActivity {
         } else {
             ((EditText)findViewById(R.id.edFolder)).setText("/sdcard/sdlpal/");
         }
-        ((EditText)findViewById(R.id.edLangFile)).setText("");
+        ((EditText)findViewById(R.id.edMsgFile)).setText("");
+        ((EditText)findViewById(R.id.edFontFile)).setText("");
+        ((EditText)findViewById(R.id.edLogFile)).setText("");
 
-        ((SwitchCompat)findViewById(R.id.swEmbedFont)).setChecked(getConfigBoolean(UseEmbeddedFonts, true));
-        ((SwitchCompat)findViewById(R.id.swCustomLang)).setChecked(false);
-        ((SwitchCompat)findViewById(R.id.swGameLang)).setChecked(getConfigInt(CodePage, true) != 0);
+        ((SwitchCompat)findViewById(R.id.swMsgFile)).setChecked(false);
+        ((SwitchCompat)findViewById(R.id.swFontFile)).setChecked(false);
+        ((SwitchCompat)findViewById(R.id.swLogFile)).setChecked(false);
         ((SwitchCompat)findViewById(R.id.swTouch)).setChecked(getConfigBoolean(UseTouchOverlay, true));
         ((SwitchCompat)findViewById(R.id.swAspect)).setChecked(getConfigBoolean(KeepAspectRatio, true));
         ((SwitchCompat)findViewById(R.id.swSurround)).setChecked(getConfigBoolean(UseSurroundOPL, true));
         ((SwitchCompat)findViewById(R.id.swStereo)).setChecked(getConfigBoolean(Stereo, true));
 
+        ((AppCompatSpinner)findViewById(R.id.spLogLevel)).setSelection(getConfigInt(LogLevel, true));
         ((AppCompatSpinner)findViewById(R.id.spSample)).setSelection(findMatchedIntIndex(getConfigInt(SampleRate, true), AudioSampleRates, 2));    // 44100Hz
         ((AppCompatSpinner)findViewById(R.id.spBuffer)).setSelection(findMatchedIntIndex(getConfigInt(AudioBufferSize, true), AudioBufferSizes, 1));    // 1024
         ((AppCompatSpinner)findViewById(R.id.spCDFmt)).setSelection(findMatchedStringIndex(getConfigString(CDFormat, true), CDFormats, 1));     // OGG
@@ -184,25 +205,30 @@ public class SettingsActivity extends AppCompatActivity {
 
 
     protected void resetConfigs() {
-        findViewById(R.id.edLangFile).setVisibility(View.GONE);
+        findViewById(R.id.edMsgFile).setVisibility(View.GONE);
+        findViewById(R.id.edFontFile).setVisibility(View.GONE);
+        findViewById(R.id.edLogFile).setVisibility(View.GONE);
         findViewById(R.id.layoutOPL).setVisibility(View.VISIBLE);
 
         ((SeekBar)findViewById(R.id.sbMusVol)).setProgress(getConfigInt(MusicVolume, false));
         ((SeekBar)findViewById(R.id.sbSFXVol)).setProgress(getConfigInt(SoundVolume, false));
         ((SeekBar)findViewById(R.id.sbQuality)).setProgress(getConfigInt(ResampleQuality, false)); // Best quality
 
-        String langFile = getConfigString(MessageFileName, false);
+        String msgFile, fontFile, logFile;
         ((EditText)findViewById(R.id.edFolder)).setText(getConfigString(GamePath, false));
-        ((EditText)findViewById(R.id.edLangFile)).setText(langFile);
+        ((EditText)findViewById(R.id.edMsgFile)).setText(msgFile = getConfigString(MessageFileName, false));
+        ((EditText)findViewById(R.id.edFontFile)).setText(fontFile = getConfigString(FontFileName, false));
+        ((EditText)findViewById(R.id.edLogFile)).setText(logFile = getConfigString(LogFileName, false));
 
-        ((SwitchCompat)findViewById(R.id.swEmbedFont)).setChecked(getConfigBoolean(UseEmbeddedFonts, false));
-        ((SwitchCompat)findViewById(R.id.swCustomLang)).setChecked(langFile != null && !langFile.isEmpty());
-        ((SwitchCompat)findViewById(R.id.swGameLang)).setChecked(getConfigInt(CodePage, false) != 0);
+        ((SwitchCompat)findViewById(R.id.swMsgFile)).setChecked(msgFile != null && !msgFile.isEmpty());
+        ((SwitchCompat)findViewById(R.id.swFontFile)).setChecked(fontFile != null && !fontFile.isEmpty());
+        ((SwitchCompat)findViewById(R.id.swLogFile)).setChecked(logFile != null && !logFile.isEmpty());
         ((SwitchCompat)findViewById(R.id.swTouch)).setChecked(getConfigBoolean(UseTouchOverlay, false));
         ((SwitchCompat)findViewById(R.id.swAspect)).setChecked(getConfigBoolean(KeepAspectRatio, false));
         ((SwitchCompat)findViewById(R.id.swSurround)).setChecked(getConfigBoolean(UseSurroundOPL, false));
         ((SwitchCompat)findViewById(R.id.swStereo)).setChecked(getConfigBoolean(Stereo, false));
 
+        ((AppCompatSpinner)findViewById(R.id.spLogLevel)).setSelection(getConfigInt(LogLevel, false));
         ((AppCompatSpinner)findViewById(R.id.spSample)).setSelection(findMatchedIntIndex(getConfigInt(SampleRate, false), AudioSampleRates, 2));    // 44100Hz
         ((AppCompatSpinner)findViewById(R.id.spBuffer)).setSelection(findMatchedIntIndex(getConfigInt(AudioBufferSize, false), AudioBufferSizes, 1));    // 1024
         ((AppCompatSpinner)findViewById(R.id.spCDFmt)).setSelection(findMatchedStringIndex(getConfigString(CDFormat, false), CDFormats, 1));     // OGG
@@ -231,15 +257,16 @@ public class SettingsActivity extends AppCompatActivity {
 
         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());
+        setConfigString(MessageFileName, ((SwitchCompat)findViewById(R.id.swMsgFile)).isChecked() ? ((EditText)findViewById(R.id.edMsgFile)).getText().toString() : null);
+        setConfigString(FontFileName, ((SwitchCompat)findViewById(R.id.swFontFile)).isChecked() ? ((EditText)findViewById(R.id.edFontFile)).getText().toString() : null);
+        setConfigString(LogFileName, ((SwitchCompat)findViewById(R.id.swLogFile)).isChecked() ? ((EditText)findViewById(R.id.edLogFile)).getText().toString() : null);
 
-        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());
         setConfigBoolean(Stereo, ((SwitchCompat)findViewById(R.id.swStereo)).isChecked());
 
+        setConfigInt(LogLevel, ((AppCompatSpinner)findViewById(R.id.spLogLevel)).getSelectedItemPosition());
         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());

+ 34 - 26
android/app/src/main/res/layout/content_settings.xml

@@ -20,7 +20,6 @@
             >
 
             <TextView
-                android:id="@+id/tvFolder"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="@string/label_folder"
@@ -38,37 +37,55 @@
                 tools:layout_editor_absoluteY="16dp" />
 
             <android.support.v7.widget.SwitchCompat
-                android:id="@+id/swEmbedFont"
+                android:id="@+id/swMsgFile"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/action_embedded_font"
+                android:text="@string/action_usemsgfile"
                 tools:layout_editor_absoluteX="8dp"
-                tools:layout_editor_absoluteY="121dp" />
+                tools:layout_editor_absoluteY="191dp" />
+
+            <EditText
+                android:id="@+id/edMsgFile"
+                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/swCustomLang"
+                android:id="@+id/swFontFile"
                 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:text="@string/action_usefontfile" />
+
+            <EditText
+                android:id="@+id/edFontFile"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ems="10"
+                android:inputType="textUri" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/label_loglevel" />
+
+            <android.support.v7.widget.AppCompatSpinner
+                android:id="@+id/spLogLevel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/log_level" />
 
             <android.support.v7.widget.SwitchCompat
-                android:id="@+id/swGameLang"
+                android:id="@+id/swLogFile"
                 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" />
+                android:text="@string/action_uselogfile" />
 
             <EditText
-                android:id="@+id/edLangFile"
+                android:id="@+id/edLogFile"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_weight="1"
                 android:ems="10"
                 android:inputType="textUri" />
 
@@ -97,7 +114,6 @@
                 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" />
@@ -109,7 +125,6 @@
                 android:max="100" />
 
             <TextView
-                android:id="@+id/tvSFXVol"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/label_sfxvol" />
@@ -121,7 +136,6 @@
                 android:max="100" />
 
             <TextView
-                android:id="@+id/tvQuality"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/label_quality" />
@@ -133,7 +147,6 @@
                 android:max="4" />
 
             <TextView
-                android:id="@+id/tvSample"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/label_audrate" />
@@ -145,7 +158,6 @@
                 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" />
@@ -157,7 +169,6 @@
                 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" />
@@ -169,7 +180,6 @@
                 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" />
@@ -187,7 +197,6 @@
                 android:orientation="vertical">
 
                 <TextView
-                    android:id="@+id/tvOPLRate"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:text="@string/label_oplrate" />
@@ -199,7 +208,6 @@
                     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" />

+ 10 - 0
android/app/src/main/res/values-zh-rTW/arrays.xml

@@ -0,0 +1,10 @@
+<resources>
+    <string-array name="log_level">
+        <item>詳細</item>
+        <item>調試</item>
+        <item>信息</item>
+        <item>警告</item>
+        <item>錯誤</item>
+        <item>致命</item>
+    </string-array>
+</resources>

+ 4 - 5
android/app/src/main/res/values-zh-rTW/strings.xml

@@ -2,16 +2,12 @@
 <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>
@@ -25,6 +21,9 @@
     <string name="msg_crash">上次程式異常退出,已顯示設定頁供您檢查設定是否正確。</string>
     <string name="msg_empty">必須指定遊戲資源檔案夾!</string>
     <string name="msg_exit">您的設定已保存,下次啟動時將直接開始遊戲。在遊戲中,您可以通過遊戲系統功能表的返回設定選項回到本設定頁面。</string>
-    <string name="switch_simplified">簡</string>
     <string name="action_restore">撤銷修改</string>
+    <string name="label_loglevel">日誌記錄級別</string>
+    <string name="action_usefontfile">自訂字體檔案</string>
+    <string name="action_usemsgfile">自訂語言檔案</string>
+    <string name="action_uselogfile">記錄到檔案</string>
 </resources>

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

@@ -0,0 +1,10 @@
+<resources>
+    <string-array name="log_level">
+        <item>详细</item>
+        <item>调试</item>
+        <item>信息</item>
+        <item>警告</item>
+        <item>错误</item>
+        <item>致命</item>
+    </string-array>
+</resources>

+ 4 - 5
android/app/src/main/res/values-zh/strings.xml

@@ -2,11 +2,7 @@
 <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>
@@ -25,6 +21,9 @@
     <string name="msg_crash">上次程序异常退出,已显示设置页供您检查设置是否正确。</string>
     <string name="msg_empty">必须指定游戏资源文件夹!</string>
     <string name="msg_exit">您的设置已保存,下次启动时将直接开始游戏。在游戏中,您可以通过游戏系统菜单中的返回设置选项回到本设置页面。</string>
-    <string name="switch_simplified">简</string>
     <string name="action_restore">撤销修改</string>
+    <string name="label_loglevel">日志记录级别</string>
+    <string name="action_uselogfile">记录到文件</string>
+    <string name="action_usemsgfile">自定义语言文件</string>
+    <string name="action_usefontfile">自定义字体文件</string>
 </resources>

+ 14 - 6
android/app/src/main/res/values/arrays.xml

@@ -1,34 +1,42 @@
 <resources>
-    <string-array name="audio_rate">
+    <string-array name="audio_rate" translatable="false">
         <item>11025</item>
         <item>22050</item>
         <item>44100</item>
     </string-array>
-    <string-array name="buffer_size">
+    <string-array name="buffer_size" translatable="false">
         <item>512</item>
         <item>1024</item>
         <item>2048</item>
         <item>4096</item>
         <item>8192</item>
     </string-array>
-    <string-array name="cd_format">
+    <string-array name="cd_format" translatable="false">
         <item>MP3</item>
         <item>OGG</item>
     </string-array>
-    <string-array name="music_format">
+    <string-array name="music_format" translatable="false">
         <item>MIDI</item>
         <item>RIX</item>
         <item>MP3</item>
         <item>OGG</item>
     </string-array>
-    <string-array name="opl_type">
+    <string-array name="opl_type" translatable="false">
         <item>DOSBOX</item>
         <item>MAME</item>
         <item>DOSBOXNEW</item>
     </string-array>
-    <string-array name="opl_rate">
+    <string-array name="opl_rate" translatable="false">
         <item>12429</item>
         <item>24858</item>
         <item>49716</item>
     </string-array>
+    <string-array name="log_level">
+        <item>Verbose</item>
+        <item>Debug</item>
+        <item>Informational</item>
+        <item>Warning</item>
+        <item>Error</item>
+        <item>Fatal</item>
+    </string-array>
 </resources>

+ 4 - 5
android/app/src/main/res/values/strings.xml

@@ -2,12 +2,7 @@
     <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>
@@ -27,4 +22,8 @@
     <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>
+    <string name="label_loglevel">Logging level</string>
+    <string name="action_usemsgfile">Customized message file</string>
+    <string name="action_usefontfile">Customized font file</string>
+    <string name="action_uselogfile">Log to file</string>
 </resources>

+ 20 - 8
font.c

@@ -41,7 +41,7 @@ static uint8_t reverseBits(uint8_t x) {
     return y;
 }
 
-static void PAL_InitEmbeddedFont(void)
+static void PAL_LoadEmbeddedFont(void)
 {
 	FILE *fp;
 	char *char_buf;
@@ -83,16 +83,28 @@ static void PAL_InitEmbeddedFont(void)
 	//
 	fclose(fp);
 
+	//
+	// Detect the codepage of 'wor16.asc' and exit if not BIG5 or probability < 99
+	// Note: 100% probability is impossible as the function does not recognize some special
+	// characters such as bopomofo that may be used by 'wor16.asc'.
+	//
+	if (PAL_DetectCodePageForString(char_buf, nBytes, CP_BIG5, &i) != CP_BIG5 || i < 99)
+	{
+		free(char_buf);
+		return;
+	}
+
 	//
 	// Convert characters into unicode
+	// Explictly specify BIG5 here for compatibility with codepage auto-detection
 	//
-	nChars = PAL_MultiByteToWideChar(char_buf, nBytes, NULL, 0);
+	nChars = PAL_MultiByteToWideCharCP(CP_BIG5, char_buf, nBytes, NULL, 0);
 	if (NULL == (wchar_buf = (wchar_t *)malloc(nChars * sizeof(wchar_t))))
 	{
 		free(char_buf);
 		return;
 	}
-	PAL_MultiByteToWideChar(char_buf, nBytes, wchar_buf, nChars);
+	PAL_MultiByteToWideCharCP(CP_BIG5, char_buf, nBytes, wchar_buf, nChars);
 	free(char_buf);
 
 	//
@@ -130,7 +142,7 @@ static void PAL_InitEmbeddedFont(void)
 }
 
 INT
-PAL_LoadBdfFont(
+PAL_LoadUserFont(
    LPCSTR      pszBdfFileName
 )
 /*++
@@ -237,14 +249,14 @@ PAL_InitFont(
 	const CONFIGURATION* cfg
 )
 {
-	if (cfg->pszBdfFile)
+	if (!cfg->fIsWIN95)
 	{
-		PAL_LoadBdfFont(cfg->pszBdfFile);
+		PAL_LoadEmbeddedFont();
 	}
 
-	if (!cfg->fIsWIN95 && cfg->fUseEmbeddedFonts)
+	if (cfg->pszFontFile)
 	{
-		PAL_InitEmbeddedFont();
+		PAL_LoadUserFont(cfg->pszFontFile);
 	}
 
 	return 0;

+ 11 - 56
global.c

@@ -111,27 +111,21 @@ PAL_IsWINVersion_Exit:
 
 CODEPAGE
 PAL_DetectCodePage(
-	void
+	const char *   filename
 )
 {
-	// Try to convert the content of word.dat with different codepages,
-	// and use the codepage with minimal inconvertible characters
-	// Works fine currently for detecting Simplified Chinese & Traditional Chinese.
-	// Since we're using language files to support additional languages, this detection
-	// should be fine for us now.
-
 	FILE *fp;
 	char *word_buf = NULL;
 	long word_len = 0;
-	CODEPAGE cp = CP_BIG5;	// Defaults to BIG5
+	CODEPAGE cp = CP_BIG5;
 
-	if (NULL != (fp = UTIL_OpenFile("word.dat")))
+	if (NULL != (fp = UTIL_OpenFile(filename)))
 	{
 		fseek(fp, 0, SEEK_END);
 		word_len = ftell(fp);
 		word_buf = (char *)malloc(word_len);
 		fseek(fp, 0, SEEK_SET);
-		fread(word_buf, 1, word_len, fp);
+		word_len = fread(word_buf, 1, word_len, fp);
 		UTIL_CloseFile(fp);
 		// Eliminates null characters so that PAL_MultiByteToWideCharCP works properly
 		for (char *ptr = word_buf; ptr < word_buf + word_len; ptr++)
@@ -143,54 +137,15 @@ PAL_DetectCodePage(
 
 	if (word_buf)
 	{
-		// The WORD.DAT should not contain characters outside these ranges
-		const static int valid_ranges[][2] = {
-			{ 0x4E00, 0x9FFF }, // CJK Unified Ideographs
-			{ 0x3400, 0x4DBF }, // CJK Unified Ideographs Extension A
-			{ 0xF900, 0xFAFF }, // CJK Compatibility Ideographs
-			{ 0x0030, 0x0039 }, // 0 - 9, ASCII
-			{ 0xFF10, 0xFF19 }, // 0 - 9, full wide
-			{ 0x0041, 0x005A }, // A - Z, ASCII
-			{ 0xFF21, 0xFF3A }, // A - Z, full wide
-			{ 0x0061, 0x007A }, // a - z, ASCII
-			{ 0xFF41, 0xFF5A }, // a - z, full wide
-		};
-		int min_invalids = INT_MAX;
-
-		for (CODEPAGE i = CP_BIG5; i <= CP_GBK; i++)
-		{
-			int invalids, length = PAL_MultiByteToWideCharCP(i, word_buf, word_len, NULL, 0);
-			WCHAR *wbuf = (WCHAR *)malloc(length * sizeof(WCHAR));
-			PAL_MultiByteToWideCharCP(i, word_buf, word_len, wbuf, length);
-			for (int j = invalids = 0; j < length; j++)
-			{
-				int score = 1;
-				if (iswspace(wbuf[j])) continue;
-				for (int k = 0; k < sizeof(valid_ranges) / sizeof(valid_ranges[0]); k++)
-				{
-					if (wbuf[j] >= valid_ranges[k][0] &&
-						wbuf[j] <= valid_ranges[k][1])
-					{
-						score = 0;
-						break;
-					}
-				}
-				invalids += score;
-			}
-			// code page with less invalid chars wins
-			if (invalids < min_invalids)
-			{
-				min_invalids = invalids;
-				cp = i;
-			}
-			free(wbuf);
-		}
+		int probability;
+		cp = PAL_DetectCodePageForString(word_buf, (int)word_len, cp, &probability);
+
 		free(word_buf);
 
-		if (min_invalids == 0)
-			UTIL_LogOutput(LOGLEVEL_INFO, "PAL_DetectCodePage detected code page: %s\n", cp ? "GBK" : "BIG5");
+		if (probability == 100)
+			UTIL_LogOutput(LOGLEVEL_INFO, "PAL_DetectCodePage detected code page '%s' for %s\n", cp ? "GBK" : "BIG5", filename);
 		else
-			UTIL_LogOutput(LOGLEVEL_WARNING, "PAL_DetectCodePage detected possible code page: %s [%d invalids]\n", cp ? "GBK" : "BIG5", min_invalids);
+			UTIL_LogOutput(LOGLEVEL_WARNING, "PAL_DetectCodePage detected the most possible (%d) code page '%s' for %s\n", probability, cp ? "GBK" : "BIG5", filename);
 	}
 
 	return cp;
@@ -235,7 +190,7 @@ PAL_InitGlobals(
    //
    // Detect game language only when no message file specified
    //
-   if (!gConfig.pszMsgFile) PAL_SetCodePage(PAL_DetectCodePage());
+   if (!gConfig.pszMsgFile) PAL_SetCodePage(PAL_DetectCodePage("word.dat"));
 
    //
    // Set decompress function

+ 1 - 1
global.h

@@ -556,7 +556,7 @@ PAL_IsWINVersion(
 
 CODEPAGE
 PAL_DetectCodePage(
-	void
+	const char *   filename
 );
 
 INT

+ 276 - 141
palcfg.c

@@ -40,7 +40,6 @@ static const ConfigItem gConfigItems[PALCFG_ALL_MAX] = {
 	{ PALCFG_KEEPASPECTRATIO,   PALCFG_BOOLEAN,  "KeepAspectRatio",   15, MAKE_VALUE(TRUE,                          FALSE,                 TRUE) },
 	{ PALCFG_LAUNCHSETTING,     PALCFG_BOOLEAN,  "LaunchSetting",     13, MAKE_VALUE(PAL_HAS_CONFIG_PAGE,           FALSE,                 TRUE) },
 	{ PALCFG_STEREO,            PALCFG_BOOLEAN,  "Stereo",             6, MAKE_VALUE(TRUE,                          FALSE,                 TRUE) },                  // Default for stereo audio
-	{ PALCFG_USEEMBEDDEDFONTS,  PALCFG_BOOLEAN,  "UseEmbeddedFonts",  16, MAKE_VALUE(TRUE,                          FALSE,                 TRUE) },                  // Default for using embedded fonts in DOS version
 	{ PALCFG_USESURROUNDOPL,    PALCFG_BOOLEAN,  "UseSurroundOPL",    14, MAKE_VALUE(TRUE,                          FALSE,                 TRUE) },                  // Default for using surround opl
 	{ PALCFG_USETOUCHOVERLAY,   PALCFG_BOOLEAN,  "UseTouchOverlay",   15, MAKE_VALUE(PAL_HAS_TOUCH,                 FALSE,                 TRUE) },
 
@@ -48,7 +47,6 @@ static const ConfigItem gConfigItems[PALCFG_ALL_MAX] = {
 	{ PALCFG_LOGLEVEL,          PALCFG_INTEGER,  "LogLevel",           8, MAKE_VALUE(PAL_DEFAULT_LOGLEVEL,          LOGLEVEL_MIN,          LOGLEVEL_MAX) },
 
 	{ PALCFG_AUDIOBUFFERSIZE,   PALCFG_UNSIGNED, "AudioBufferSize",   15, MAKE_VALUE(PAL_AUDIO_DEFAULT_BUFFER_SIZE, 2,                     32768) },
-	{ PALCFG_CODEPAGE,          PALCFG_UNSIGNED, "CodePage",           8, MAKE_VALUE(CP_BIG5,                       CP_MIN,                CP_MAX - 1) },            // Default for BIG5
 	{ PALCFG_OPLSAMPLERATE,     PALCFG_UNSIGNED, "OPLSampleRate",     13, MAKE_VALUE(49716,                         0,                     UINT32_MAX) },
 	{ PALCFG_RESAMPLEQUALITY,   PALCFG_UNSIGNED, "ResampleQuality",   15, MAKE_VALUE(RESAMPLER_QUALITY_MAX,         RESAMPLER_QUALITY_MIN, RESAMPLER_QUALITY_MAX) }, // Default for best quality
 	{ PALCFG_SAMPLERATE,        PALCFG_UNSIGNED, "SampleRate",        10, MAKE_VALUE(44100,                         0,                     PAL_MAX_SAMPLERATE) },
@@ -61,10 +59,10 @@ static const ConfigItem gConfigItems[PALCFG_ALL_MAX] = {
 	{ PALCFG_GAMEPATH,          PALCFG_STRING,   "GamePath",           8, MAKE_VALUE(NULL,     NULL, NULL) },
 	{ PALCFG_SAVEPATH,          PALCFG_STRING,   "SavePath",           8, MAKE_VALUE(NULL,     NULL, NULL) },
 	{ PALCFG_MESSAGEFILE,       PALCFG_STRING,   "MessageFileName",   15, MAKE_VALUE(NULL,     NULL, NULL) },
-	{ PALCFG_BDFFILE,           PALCFG_STRING,   "BDFFileName",       11, MAKE_VALUE(NULL,     NULL, NULL) },
+	{ PALCFG_FONTFILE,          PALCFG_STRING,   "FontFileName",      12, MAKE_VALUE(NULL,     NULL, NULL) },
 	{ PALCFG_MUSIC,             PALCFG_STRING,   "Music",              5, MAKE_VALUE("RIX",    NULL, NULL) },
 	{ PALCFG_OPL,               PALCFG_STRING,   "OPL",                3, MAKE_VALUE("DOSBOX", NULL, NULL) },
-	{ PALCFG_LOGFILE,           PALCFG_STRING,   "LogFile",            7, MAKE_VALUE(NULL,     NULL, NULL) },
+	{ PALCFG_LOGFILE,           PALCFG_STRING,   "LogFileName",       11, MAKE_VALUE(NULL,     NULL, NULL) },
 	{ PALCFG_RIXEXTRAINIT,      PALCFG_STRING,   "RIXExtraInit",      12, MAKE_VALUE(NULL,     NULL, NULL) },
 	{ PALCFG_CLIMIDIPLAYER,     PALCFG_STRING,   "CLIMIDIPlayer",     13, MAKE_VALUE(NULL,     NULL, NULL) },
 };
@@ -161,20 +159,35 @@ PAL_ParseConfigLine(
 	return FALSE;
 }
 
-ConfigValue
-PAL_DefaultConfig(
+const char *
+PAL_ConfigName(
 	PALCFG_ITEM item
 )
 {
-	return gConfigItems[item].DefaultValue;
+	return gConfigItems[item].Name;
 }
 
-const char *
-PAL_ConfigName(
+PALCFG_ITEM
+PAL_ConfigIndex(
+	const char  *name
+)
+{
+	for (int index = PALCFG_ALL_MIN; index < PALCFG_ALL_MAX; index++)
+	{
+		if (SDL_strcasecmp(gConfigItems[index].Name, name) == 0)
+		{
+			return index;
+		}
+	}
+	return -1;
+}
+
+PALCFG_TYPE
+PAL_ConfigType(
 	PALCFG_ITEM item
 )
 {
-	return gConfigItems[item].Name;
+	return gConfigItems[item].Type;
 }
 
 BOOL
@@ -239,7 +252,7 @@ PAL_FreeConfig(
 	free(gConfig.dwExtraLength);
 #endif
 	free(gConfig.pszMsgFile);
-	free(gConfig.pszBdfFile);
+	free(gConfig.pszFontFile);
 	free(gConfig.pszGamePath);
 	free(gConfig.pszSavePath);
 	free(gConfig.pszLogFile);
@@ -279,7 +292,7 @@ PAL_LoadConfig(
 		PAL_XY(0, 0), PAL_XY(0, 0)
 	};
 
-	for (PALCFG_ITEM i = PALCFG_ALL_MIN; i < PALCFG_ALL_MAX; i++) values[i] = PAL_DefaultConfig(i);
+	for (PALCFG_ITEM i = PALCFG_ALL_MIN; i < PALCFG_ALL_MAX; i++) values[i] = gConfigItems[i].DefaultValue;
 
 	if (fFromFile && (fp = fopen(va("%ssdlpal.cfg", PAL_CONFIG_PREFIX), "r")))
 	{
@@ -309,8 +322,8 @@ PAL_LoadConfig(
 				case PALCFG_MESSAGEFILE:
 					gConfig.pszMsgFile = ParseStringValue(value.sValue, gConfig.pszMsgFile);
 					break;
-				case PALCFG_BDFFILE:
-					gConfig.pszBdfFile = ParseStringValue(value.sValue, gConfig.pszBdfFile);
+				case PALCFG_FONTFILE:
+					gConfig.pszFontFile = ParseStringValue(value.sValue, gConfig.pszFontFile);
 					break;
 				case PALCFG_GAMEPATH:
 					gConfig.pszGamePath = ParseStringValue(value.sValue, gConfig.pszGamePath);
@@ -421,7 +434,6 @@ PAL_LoadConfig(
 	gConfig.ScreenLayout = screen_layout;
 
 	gConfig.fIsWIN95 = FALSE;	// Default for DOS version
-	gConfig.fUseEmbeddedFonts = values[PALCFG_USEEMBEDDEDFONTS].bValue;
 	gConfig.fUseSurroundOPL = values[PALCFG_STEREO].bValue && values[PALCFG_USESURROUNDOPL].bValue;
 	gConfig.fLaunchSetting = values[PALCFG_LAUNCHSETTING].bValue;
 	gConfig.fUseTouchOverlay = values[PALCFG_USETOUCHOVERLAY].bValue;
@@ -435,7 +447,6 @@ PAL_LoadConfig(
 	gConfig.iSampleRate = values[PALCFG_SAMPLERATE].uValue;
 	gConfig.iOPLSampleRate = values[PALCFG_OPLSAMPLERATE].uValue;
 	gConfig.iResampleQuality = values[PALCFG_RESAMPLEQUALITY].uValue;
-	gConfig.uCodePage = values[PALCFG_CODEPAGE].uValue;
 	gConfig.wAudioBufferSize = (WORD)values[PALCFG_AUDIOBUFFERSIZE].uValue;
 	gConfig.iMusicVolume = values[PALCFG_MUSICVOLUME].uValue;
 	gConfig.iSoundVolume = values[PALCFG_SOUNDVOLUME].uValue;
@@ -467,7 +478,6 @@ PAL_SaveConfig(
 		sprintf(buf, "%s=%d\n", PAL_ConfigName(PALCFG_FULLSCREEN), gConfig.fFullScreen); fputs(buf, fp);
 		sprintf(buf, "%s=%d\n", PAL_ConfigName(PALCFG_LAUNCHSETTING), gConfig.fLaunchSetting); fputs(buf, fp);
 		sprintf(buf, "%s=%d\n", PAL_ConfigName(PALCFG_STEREO), gConfig.iAudioChannels == 2 ? TRUE : FALSE); fputs(buf, fp);
-		sprintf(buf, "%s=%d\n", PAL_ConfigName(PALCFG_USEEMBEDDEDFONTS), gConfig.fUseEmbeddedFonts); fputs(buf, fp);
 		sprintf(buf, "%s=%d\n", PAL_ConfigName(PALCFG_USESURROUNDOPL), gConfig.fUseSurroundOPL); fputs(buf, fp);
 		sprintf(buf, "%s=%d\n", PAL_ConfigName(PALCFG_USETOUCHOVERLAY), gConfig.fUseTouchOverlay); fputs(buf, fp);
 
@@ -475,7 +485,6 @@ PAL_SaveConfig(
 		sprintf(buf, "%s=%d\n", PAL_ConfigName(PALCFG_LOGLEVEL), gConfig.iLogLevel); fputs(buf, fp);
 
 		sprintf(buf, "%s=%u\n", PAL_ConfigName(PALCFG_AUDIOBUFFERSIZE), gConfig.wAudioBufferSize); fputs(buf, fp);
-		sprintf(buf, "%s=%u\n", PAL_ConfigName(PALCFG_CODEPAGE), gConfig.uCodePage); fputs(buf, fp);
 		sprintf(buf, "%s=%u\n", PAL_ConfigName(PALCFG_OPLSAMPLERATE), gConfig.iOPLSampleRate); fputs(buf, fp);
 		sprintf(buf, "%s=%u\n", PAL_ConfigName(PALCFG_RESAMPLEQUALITY), gConfig.iResampleQuality); fputs(buf, fp);
 		sprintf(buf, "%s=%u\n", PAL_ConfigName(PALCFG_SAMPLERATE), gConfig.iSampleRate); fputs(buf, fp);
@@ -488,10 +497,10 @@ PAL_SaveConfig(
 		sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_MUSIC), music_types[gConfig.eMusicType]); fputs(buf, fp);
 		sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_OPL), opl_types[gConfig.eOPLType]); fputs(buf, fp);
 
-		if (gConfig.pszGamePath && *gConfig.pszGamePath && strncmp(gConfig.pszGamePath, PAL_PREFIX, strnlen(gConfig.pszGamePath, PAL_MAX_PATH)) != 0) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_GAMEPATH), gConfig.pszGamePath); fputs(buf, fp); }
-		if (gConfig.pszSavePath && *gConfig.pszSavePath && strncmp(gConfig.pszSavePath, PAL_SAVE_PREFIX, strnlen(gConfig.pszSavePath, PAL_MAX_PATH)) != 0) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_SAVEPATH), gConfig.pszSavePath); fputs(buf, fp); }
+		if (gConfig.pszGamePath && *gConfig.pszGamePath && strcmp(gConfig.pszGamePath, PAL_PREFIX) != 0) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_GAMEPATH), gConfig.pszGamePath); fputs(buf, fp); }
+		if (gConfig.pszSavePath && *gConfig.pszSavePath && strcmp(gConfig.pszSavePath, PAL_SAVE_PREFIX) != 0) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_SAVEPATH), gConfig.pszSavePath); fputs(buf, fp); }
 		if (gConfig.pszMsgFile && *gConfig.pszMsgFile) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_MESSAGEFILE), gConfig.pszMsgFile); fputs(buf, fp); }
-		if (gConfig.pszBdfFile && *gConfig.pszBdfFile) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_BDFFILE), gConfig.pszBdfFile); fputs(buf, fp); }
+		if (gConfig.pszFontFile && *gConfig.pszFontFile) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_FONTFILE), gConfig.pszFontFile); fputs(buf, fp); }
 		if (gConfig.pszLogFile && *gConfig.pszLogFile) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_LOGFILE), gConfig.pszLogFile); fputs(buf, fp); }
 		if (gConfig.pszCLIMIDIPlayerPath && *gConfig.pszCLIMIDIPlayerPath) { sprintf(buf, "%s=%s\n", PAL_ConfigName(PALCFG_CLIMIDIPLAYER), gConfig.pszCLIMIDIPlayerPath); fputs(buf, fp); }
 
@@ -503,143 +512,269 @@ PAL_SaveConfig(
 		return FALSE;
 }
 
-BOOL
+
+ConfigValue
 PAL_GetConfigItem(
-	const char  *szName,
-	ConfigValue *value,
-	BOOL isDefault
+	PALCFG_ITEM   item,
+	BOOL default_value
 )
 {
-	for (int index = PALCFG_ALL_MIN; index < PALCFG_ALL_MAX; index++)
+	ConfigValue value = gConfigItems[item].DefaultValue;
+	if (!default_value)
 	{
-		if (SDL_strcasecmp(gConfigItems[index].Name, szName) == 0)
+		switch (item)
 		{
-			if (isDefault)
-			{
-				*value = gConfigItems[index].DefaultValue;
-				return TRUE;
-			}
-			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_LOGFILE:           value->sValue = gConfig.pszLogFile; return TRUE;
-			case PALCFG_CLIMIDIPLAYER:     value->sValue = gConfig.pszCLIMIDIPlayerPath; return TRUE;
-			case PALCFG_MUSIC:             value->sValue = music_types[gConfig.eMusicType]; return TRUE;
-			case PALCFG_OPL:               value->sValue = opl_types[gConfig.eOPLType]; return TRUE;
-			}
-			break;
+		case PALCFG_FULLSCREEN:        value.bValue = gConfig.fFullScreen; break;
+		case PALCFG_KEEPASPECTRATIO:   value.bValue = gConfig.fKeepAspectRatio; break;
+		case PALCFG_LAUNCHSETTING:     value.bValue = gConfig.fLaunchSetting; break;
+		case PALCFG_STEREO:            value.bValue = (gConfig.iAudioChannels == 2); break;
+		case PALCFG_USESURROUNDOPL:    value.bValue = gConfig.fUseSurroundOPL; break;
+		case PALCFG_USETOUCHOVERLAY:   value.bValue = gConfig.fUseTouchOverlay; break;
+		case PALCFG_SURROUNDOPLOFFSET: value.iValue = gConfig.iSurroundOPLOffset; break;
+		case PALCFG_LOGLEVEL:          value.iValue = gConfig.iLogLevel; break;
+		case PALCFG_AUDIOBUFFERSIZE:   value.uValue = gConfig.wAudioBufferSize; break;
+		case PALCFG_OPLSAMPLERATE:     value.uValue = gConfig.iOPLSampleRate; break;
+		case PALCFG_RESAMPLEQUALITY:   value.uValue = gConfig.iResampleQuality; break;
+		case PALCFG_SAMPLERATE:        value.uValue = gConfig.iSampleRate; break;
+		case PALCFG_MUSICVOLUME:       value.uValue = gConfig.iMusicVolume; break;
+		case PALCFG_SOUNDVOLUME:       value.uValue = gConfig.iSoundVolume; break;
+		case PALCFG_WINDOWHEIGHT:      value.uValue = gConfig.dwScreenHeight; break;
+		case PALCFG_WINDOWWIDTH:       value.uValue = gConfig.dwScreenWidth; break;
+		case PALCFG_CD:                value.sValue = music_types[gConfig.eCDType]; break;
+		case PALCFG_GAMEPATH:          value.sValue = gConfig.pszGamePath; break;
+		case PALCFG_SAVEPATH:          value.sValue = gConfig.pszSavePath; break;
+		case PALCFG_MESSAGEFILE:       value.sValue = gConfig.pszMsgFile; break;
+		case PALCFG_FONTFILE:          value.sValue = gConfig.pszFontFile; break;
+		case PALCFG_LOGFILE:           value.sValue = gConfig.pszLogFile; break;
+		case PALCFG_CLIMIDIPLAYER:     value.sValue = gConfig.pszCLIMIDIPlayerPath; break;
+		case PALCFG_MUSIC:             value.sValue = music_types[gConfig.eMusicType]; break;
+		case PALCFG_OPL:               value.sValue = opl_types[gConfig.eOPLType]; break;
+		default:                       break;
 		}
 	}
-	return FALSE;
+	return value;
 }
 
 
-BOOL
+void
 PAL_SetConfigItem(
-	const char  *szName,
-	const ConfigValue *value
+	PALCFG_ITEM       item,
+	const ConfigValue value
 )
 {
-	for (int index = PALCFG_ALL_MIN; index < PALCFG_ALL_MAX; index++)
+	switch (item)
 	{
-		if (SDL_strcasecmp(gConfigItems[index].Name, szName) == 0)
+	case PALCFG_FULLSCREEN:        gConfig.fFullScreen = value.bValue; break;
+	case PALCFG_KEEPASPECTRATIO:   gConfig.fKeepAspectRatio = value.bValue; break;
+	case PALCFG_LAUNCHSETTING:     gConfig.fLaunchSetting = value.bValue; break;
+	case PALCFG_STEREO:            gConfig.iAudioChannels = value.bValue ? 2 : 1; break;
+	case PALCFG_USESURROUNDOPL:    gConfig.fUseSurroundOPL = value.bValue; break;
+	case PALCFG_USETOUCHOVERLAY:   gConfig.fUseTouchOverlay = value.bValue; break;
+	case PALCFG_SURROUNDOPLOFFSET: gConfig.iSurroundOPLOffset = value.iValue; break;
+	case PALCFG_LOGLEVEL:          gConfig.iLogLevel = value.iValue; break;
+	case PALCFG_AUDIOBUFFERSIZE:   gConfig.wAudioBufferSize = value.uValue; break;
+	case PALCFG_OPLSAMPLERATE:     gConfig.iOPLSampleRate = value.uValue; break;
+	case PALCFG_RESAMPLEQUALITY:   gConfig.iResampleQuality = value.uValue; break;
+	case PALCFG_SAMPLERATE:        gConfig.iSampleRate = value.uValue; break;
+	case PALCFG_MUSICVOLUME:       gConfig.iMusicVolume = value.uValue; break;
+	case PALCFG_SOUNDVOLUME:       gConfig.iSoundVolume = value.uValue; break;
+	case PALCFG_WINDOWHEIGHT:      gConfig.dwScreenHeight = value.uValue; break;
+	case PALCFG_WINDOWWIDTH:       gConfig.dwScreenWidth = value.uValue; break;
+	case PALCFG_GAMEPATH:
+		if (gConfig.pszGamePath) free(gConfig.pszGamePath);
+		gConfig.pszGamePath = value.sValue && value.sValue[0] ? strdup(value.sValue) : strdup(PAL_PREFIX);
+		break;
+	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));
+		break;
+	case PALCFG_MESSAGEFILE:
+		if (gConfig.pszMsgFile) free(gConfig.pszMsgFile);
+		gConfig.pszMsgFile = value.sValue && value.sValue[0] ? strdup(value.sValue) : NULL;
+		break;
+	case PALCFG_FONTFILE:
+		if (gConfig.pszFontFile) free(gConfig.pszFontFile);
+		gConfig.pszFontFile = value.sValue && value.sValue[0] ? strdup(value.sValue) : NULL;
+		break;
+	case PALCFG_LOGFILE:
+		if (gConfig.pszLogFile) free(gConfig.pszLogFile);
+		gConfig.pszLogFile = value.sValue && value.sValue[0] ? strdup(value.sValue) : NULL;
+		break;
+	case PALCFG_CLIMIDIPLAYER:
+		if (gConfig.pszCLIMIDIPlayerPath) free(gConfig.pszCLIMIDIPlayerPath);
+		gConfig.pszCLIMIDIPlayerPath = value.sValue && value.sValue[0] ? strdup(value.sValue) : NULL;
+		break;
+	case PALCFG_CD:
+		for (int i = 0; i < sizeof(music_types) / sizeof(music_types[0]); i++)
 		{
-			switch (index)
+			if (SDL_strcasecmp(value.sValue, music_types[i]) == 0)
 			{
-			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_LOGFILE:
-				if (gConfig.pszLogFile) free(gConfig.pszLogFile);
-				gConfig.pszLogFile = value->sValue && value->sValue[0] ? strdup(value->sValue) : NULL;
-				return TRUE;
-			case PALCFG_CLIMIDIPLAYER:
-				if (gConfig.pszCLIMIDIPlayerPath) free(gConfig.pszCLIMIDIPlayerPath);
-				gConfig.pszCLIMIDIPlayerPath = 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;
+				gConfig.eCDType = (MUSICTYPE)i;
+				return;
+			}
+		}
+		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;
 			}
 		}
+		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;
+			}
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+BOOL
+PAL_GetConfigBoolean(
+	PALCFG_ITEM item,
+	BOOL        default_value
+)
+{
+	return gConfigItems[item].Type == PALCFG_BOOLEAN ? PAL_GetConfigItem(item, default_value).bValue : FALSE;
+}
+
+long
+PAL_GetConfigNumber(
+	PALCFG_ITEM item,
+	BOOL        default_value
+)
+{
+	switch (gConfigItems[item].Type)
+	{
+	case PALCFG_INTEGER:  return (long)PAL_GetConfigItem(item, default_value).iValue;
+	case PALCFG_UNSIGNED: return (long)PAL_GetConfigItem(item, default_value).uValue;
+	default:              return 0;
+	}
+}
+
+int
+PAL_GetConfigInteger(
+	PALCFG_ITEM item,
+	BOOL        default_value
+)
+{
+	return gConfigItems[item].Type == PALCFG_INTEGER ? PAL_GetConfigItem(item, default_value).iValue : 0;
+}
+
+unsigned int
+PAL_GetConfigUnsigned(
+	PALCFG_ITEM item,
+	BOOL        default_value
+)
+{
+	return gConfigItems[item].Type == PALCFG_UNSIGNED ? PAL_GetConfigItem(item, default_value).uValue : 0;
+}
+
+const char *
+PAL_GetConfigString(
+	PALCFG_ITEM item,
+	BOOL        default_value
+)
+{
+	return gConfigItems[item].Type == PALCFG_STRING ? PAL_GetConfigItem(item, default_value).sValue : NULL;
+}
+
+BOOL
+PAL_SetConfigBoolean(
+	PALCFG_ITEM item,
+	BOOL        value
+)
+{
+	if (gConfigItems[item].Type == PALCFG_BOOLEAN)
+	{
+		ConfigValue val = { (const char *)(intptr_t)value };
+		PAL_SetConfigItem(item, val);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+BOOL
+PAL_SetConfigNumber(
+	PALCFG_ITEM item,
+	long        value
+)
+{
+	if (gConfigItems[item].Type == PALCFG_INTEGER || gConfigItems[item].Type == PALCFG_UNSIGNED)
+	{
+		ConfigValue val = { (const char *)(intptr_t)value };
+		PAL_SetConfigItem(item, val);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+BOOL
+PAL_SetConfigInteger(
+	PALCFG_ITEM item,
+	int         value
+)
+{
+	if (gConfigItems[item].Type == PALCFG_INTEGER)
+	{
+		ConfigValue val = { (const char *)(intptr_t)value };
+		PAL_SetConfigItem(item, val);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+BOOL
+PAL_SetConfigUnsigned(
+	PALCFG_ITEM  item,
+	unsigned int value
+)
+{
+	if (gConfigItems[item].Type == PALCFG_UNSIGNED)
+	{
+		ConfigValue val = { (const char *)(intptr_t)value };
+		PAL_SetConfigItem(item, val);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+BOOL
+PAL_SetConfigString(
+	PALCFG_ITEM item,
+	const char *value
+)
+{
+	if (gConfigItems[item].Type == PALCFG_STRING)
+	{
+		ConfigValue val = { value };
+		PAL_SetConfigItem(item, val);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
 	}
-	return FALSE;
 }

+ 77 - 17
palcfg.h

@@ -42,7 +42,6 @@ typedef enum tagPALCFG_ITEM
 	PALCFG_KEEPASPECTRATIO,
 	PALCFG_LAUNCHSETTING,
 	PALCFG_STEREO,
-	PALCFG_USEEMBEDDEDFONTS,
 	PALCFG_USESURROUNDOPL,
 	PALCFG_USETOUCHOVERLAY,
 	/* Booleans */
@@ -58,7 +57,6 @@ typedef enum tagPALCFG_ITEM
 	PALCFG_UNSIGNED_MIN = PALCFG_INTEGER_MAX,
 	/* Unsigneds */
 	PALCFG_AUDIOBUFFERSIZE = PALCFG_UNSIGNED_MIN,
-	PALCFG_CODEPAGE,
 	PALCFG_OPLSAMPLERATE,
 	PALCFG_RESAMPLEQUALITY,
 	PALCFG_SAMPLERATE,
@@ -75,7 +73,7 @@ typedef enum tagPALCFG_ITEM
 	PALCFG_GAMEPATH,
 	PALCFG_SAVEPATH,
 	PALCFG_MESSAGEFILE,
-	PALCFG_BDFFILE,
+	PALCFG_FONTFILE,
 	PALCFG_MUSIC,
 	PALCFG_OPL,
 	PALCFG_LOGFILE,
@@ -166,10 +164,9 @@ typedef struct tagCONFIGURATION
 	char            *pszGamePath;
 	char            *pszSavePath;
 	char            *pszMsgFile;
-	char            *pszBdfFile;
+	char            *pszFontFile;
 	char            *pszCLIMIDIPlayerPath;
 	char            *pszLogFile;
-	CODEPAGE         uCodePage;
 	DWORD            dwWordLength;
 	DWORD            dwScreenWidth;
 	DWORD            dwScreenHeight;
@@ -186,7 +183,6 @@ typedef struct tagCONFIGURATION
 	OPLTYPE          eOPLType;
 	WORD             wAudioBufferSize;
 	BOOL             fIsWIN95;
-	BOOL             fUseEmbeddedFonts;
 	BOOL             fUseSurroundOPL;
 	BOOL             fKeepAspectRatio;
 	BOOL             fFullScreen;
@@ -227,13 +223,18 @@ PAL_ParseConfigLine(
 	ConfigValue * pValue
 );
 
-ConfigValue
-PAL_DefaultConfig(
+const char *
+PAL_ConfigName(
 	PALCFG_ITEM item
 );
 
-const char *
-PAL_ConfigName(
+PALCFG_ITEM
+PAL_ConfigIndex(
+	const char *name
+);
+
+PALCFG_TYPE
+PAL_ConfigType(
 	PALCFG_ITEM item
 );
 
@@ -243,17 +244,76 @@ PAL_LimitConfig(
 	ConfigValue * pValue
 );
 
-BOOL
+ConfigValue
 PAL_GetConfigItem(
-	const char * szName,
-	ConfigValue * pValue,
-	BOOL isDefault
+	PALCFG_ITEM   item,
+	BOOL default_value
 );
 
-BOOL
+void
 PAL_SetConfigItem(
-	const char * szName,
-	const ConfigValue * pValue
+	PALCFG_ITEM       item,
+	const ConfigValue value
+);
+
+BOOL
+PAL_GetConfigBoolean(
+	PALCFG_ITEM item,
+	BOOL        default_value
+);
+
+long
+PAL_GetConfigNumber(
+	PALCFG_ITEM item,
+	BOOL        default_value
+);
+
+int
+PAL_GetConfigInteger(
+	PALCFG_ITEM item,
+	BOOL        default_value
+);
+
+unsigned int
+PAL_GetConfigUnsigned(
+	PALCFG_ITEM item,
+	BOOL        default_value
+);
+
+const char *
+PAL_GetConfigString(
+	PALCFG_ITEM item,
+	BOOL        default_value
+);
+
+BOOL
+PAL_SetConfigBoolean(
+	PALCFG_ITEM item,
+	BOOL        value
+);
+
+BOOL
+PAL_SetConfigNumber(
+	PALCFG_ITEM item,
+	long        value
+);
+
+BOOL
+PAL_SetConfigInteger(
+	PALCFG_ITEM item,
+	int         value
+);
+
+BOOL
+PAL_SetConfigUnsigned(
+	PALCFG_ITEM  item,
+	unsigned int value
+);
+
+BOOL
+PAL_SetConfigString(
+	PALCFG_ITEM item,
+	const char *value
 );
 
 PAL_C_LINKAGE_END

+ 66 - 0
text.c

@@ -1545,6 +1545,72 @@ PAL_SetCodePage(
 	g_codepage = uCodePage;
 }
 
+CODEPAGE
+PAL_DetectCodePageForString(
+	const char *   text,
+	int            text_len,
+	CODEPAGE       default_cp,
+	int *          probability
+)
+{
+	// Try to convert the content of word.dat with different codepages,
+	// and use the codepage with minimal inconvertible characters
+	// Works fine currently for detecting Simplified Chinese & Traditional Chinese.
+	// Since we're using language files to support additional languages, this detection
+	// should be fine for us now.
+	int min_invalids = INT_MAX;
+
+	if (text && text_len > 0)
+	{
+		// The file to be detected should not contain characters outside these ranges
+		const static int valid_ranges[][2] = {
+			{ 0x4E00, 0x9FFF }, // CJK Unified Ideographs
+			{ 0x3400, 0x4DBF }, // CJK Unified Ideographs Extension A
+			{ 0xF900, 0xFAFF }, // CJK Compatibility Ideographs
+			{ 0x0020, 0x007E }, // Basic ASCII
+			{ 0x3000, 0x301E }, // CJK Symbols
+			{ 0xFF01, 0xFF5E }, // Fullwidth Forms
+		};
+
+		for (CODEPAGE i = CP_BIG5; i <= CP_GBK; i++)
+		{
+			int invalids, length = PAL_MultiByteToWideCharCP(i, text, text_len, NULL, 0);
+			WCHAR *wbuf = (WCHAR *)malloc(length * sizeof(WCHAR));
+			PAL_MultiByteToWideCharCP(i, text, text_len, wbuf, length);
+			for (int j = invalids = 0; j < length; j++)
+			{
+				int score = 1;
+				for (int k = 0; k < sizeof(valid_ranges) / sizeof(valid_ranges[0]); k++)
+				{
+					if (wbuf[j] >= valid_ranges[k][0] &&
+						wbuf[j] <= valid_ranges[k][1])
+					{
+						score = 0;
+						break;
+					}
+				}
+				invalids += score;
+			}
+			// code page with less invalid chars wins
+			if (invalids < min_invalids)
+			{
+				min_invalids = invalids;
+				default_cp = i;
+			}
+			free(wbuf);
+		}
+	}
+	if (probability)
+	{
+		if (min_invalids < text_len / 2)
+			*probability = (text_len / 2 - min_invalids) * 200 / text_len;
+		else
+			*probability = 0;
+	}
+
+	return default_cp;
+}
+
 INT
 PAL_MultiByteToWideCharCP(
    CODEPAGE      cp,

+ 8 - 0
text.h

@@ -143,6 +143,14 @@ PAL_SetCodePage(
 	CODEPAGE    uCodePage
 );
 
+CODEPAGE
+PAL_DetectCodePageForString(
+	const char *   text,
+	int            text_len,
+	CODEPAGE       default_cp,
+	int *          probability
+);
+
 INT
 PAL_swprintf(
 	LPWSTR buffer,

+ 6 - 4
unix/native_midi.cpp

@@ -34,15 +34,17 @@ char* cliplayer = nullptr;
 
 static char *which(const char *cmd)
 {
-    char path[PATH_MAX];
+	static char path[PATH_MAX] = { '\0' };
     FILE *fp = popen(va("which %s", cmd), "r");
     if (fp == NULL) {
         return NULL;
     }else{
-        fgets(path, sizeof(path)-1, fp);
+        if (fgets(path, sizeof(path)-1, fp) != NULL)
+		{
+			if (path[strlen(path) - 1] == '\n') path[strlen(path) - 1] = '\0';
+			if (path[strlen(path) - 1] == '\r') path[strlen(path) - 1] = '\0';
+		}
         pclose(fp);
-        if( path[strlen(path)-1] == '\n' ) path[strlen(path)-1] = '\0';
-        if( path[strlen(path)-1] == '\r' ) path[strlen(path)-1] = '\0';
         return path;
     }
 }

+ 70 - 68
unix/unix.cpp

@@ -21,21 +21,15 @@
 #include <FL/Fl_Int_Input.H>
 #include <FL/Fl_Choice.H>
 
-#ifdef PAL_HAS_NATIVEMIDI
-    #define MUSIC_MIN MUSIC_MIDI
-#else
-    #define MUSIC_MIN MUSIC_RIX
-#endif
-
 struct {
    Fl_Input* gamepath;
-   Fl_Radio_Round_Button* cht;
-   Fl_Radio_Round_Button* chs;
+   Fl_Input* fontfile;
+   Fl_Input* logfile;
    Fl_Input* msgfile;
-   Fl_Check_Button* font;
    Fl_Check_Button* touch;
    Fl_Check_Button* aspect;
    Fl_Check_Button* fullscreen;
+   Fl_Choice* loglevel;
    Fl_Choice* cd;
    Fl_Choice* bgm;
    Fl_Choice* opl;
@@ -54,11 +48,12 @@ struct {
    const char* language;
    const char* display;
    const char* audio;
+   const char* logging;
    const char* gamepath;
-   const char* cht;
-   const char* chs;
    const char* msgfile;
-   const char* font;
+   const char* fontfile;
+   const char* logfile;
+   const char* loglevel;
    const char* touch;
    const char* aspect;
    const char* fullscreen;
@@ -69,46 +64,48 @@ struct {
    const char* stereo;
    const char* oplrate;
    const char* surround;
-   const char* buffer;
-   const char* quality;
    const char* musvol;
    const char* sndvol;
+   const char* buffer;
+   const char* quality;
    const char* exit;
    const char* launch;
    const char* def;
+   const char* levels;
 } gLabels[3] = {
-   { "SDLPAL Launcher", "Game language", "Display", "Audio", "Game resource path:",
-     "&Traditional Chinese", "&Simplified Chinese", "Message file:", "Use &embedded font",
-     "Use touc&h overlay", "&Keep aspect ratio", "&Full screen", "&CD type:", "&BGM type:",
-     "&OPL type:", "Sample rate:", "Ste&reo", "OPL rate:", "Surround O&PL", "Buffer:",
-     "Quality:", "Music volume:", "Sound volume:", "E&xit", "&Launch game", "&Default" },
-   { "SDLPAL 启动器", "游戏语言设置", "显示设置", "音频设置", "游戏资源目录:",
-     "繁体中文(&T)", "简体中文(&S)", "语言文件:", "使用游戏资源内嵌字体(&E)",
-     "启用触屏辅助(&H)", "保持纵横比(&K)", "全屏模式(&F)", "&CD 音源:", "&BGM 音源:",
-     "&OPL 类型:", "采样率:", "立体声(&R)", "OPL 采样率:", "环绕声 O&PL", "缓冲区:",
-     "质量:", "音乐音量:", "音效音量:", "退出(&X)", "启动游戏(&L)", "默认设置(&D)" },
-   { "SDLPAL 啟動器", "遊戲語言設置", "顯示設定", "音訊設定", "遊戲資源目錄:",
-     "繁體中文(&T)", "簡體中文(&S)", "語言檔:", "使用遊戲資源內嵌字體(&E)",
-     "啟用觸屏輔助(&H)", "保持縱橫比(&K)", "全屏模式(&F)", "&CD 音源:", "&BGM 音源:",
-     "&OPL 類型:", "取樣速率:", "立體聲(&R)", "OPL 取樣速率:", "環繞聲 O&PL", "緩衝區:",
-     "品質:", "音樂音量:", "音效音量:", "退出(&X)", "啟動遊戲(&L)", "默認設定(&D)" },
+   { "SDLPAL Launcher",     "Language & Font",    "Display",      "Audio",      "Logging",
+     "Game resource path:", "Message file:",      "Font file:",   "Log file:",  "Log level:",
+     "Use touc&h overlay",  "&Keep aspect ratio", "&Full screen", "&CD type:",  "&BGM type:",
+     "&OPL type:",          "Sample rate:",       "Ste&reo",      "OPL rate:",  "Surround O&PL",
+     "Music volume:",       "Sound volume:",      "Buffer:",      "Quality:",   "E&xit",
+     "&Launch game",        "&Default",           "Verbose|Debug|Informational|Warning|Error|Fatal" },
+   { "SDLPAL 启动器",        "字体及语言设置",      "显示设置",      "音频设置",     "日志记录设置",
+     "游戏资源目录:",        "语言文件:",         "字体文件",      "日志文件",     "日志记录级别:",
+     "启用触屏辅助(&H)",      "保持纵横比(&K)",     "全屏模式(&F)",  "&CD 音源:",   "&BGM 音源:",
+     "&OPL 类型:",          "采样率:",           "立体声(&R)",    "OPL 采样率:", "环绕声 O&PL",
+     "音乐音量:",            "音效音量:",         "缓冲区:",      "质量:",       "退出(&X)",
+     "启动游戏(&L)",          "默认设置(&D)",       "详细|调试|信息|警告|错误|致命" },
+   { "SDLPAL 啟動器",        "字體及語言設定",      "顯示設定",      "音訊設定",      "日誌記錄設定",
+     "遊戲資源檔夾:",        "語言檔:",           "字體檔:",      "日誌檔:",      "日誌記錄級別:",
+     "啟用觸屏輔助(&H)",      "保持縱橫比(&K)",     "全屏模式(&F)",  "&CD 音源:",    "&BGM 音源:",
+     "&OPL 類型:",          "取樣速率:",          "立體聲(&R)",   "OPL 取樣速率:", "環繞聲 O&PL",
+     "音樂音量:",            "音效音量:",         "緩衝區:",      "品質:",        "退出(&X)",
+     "啟動遊戲(&L)",          "默認設定(&D)",       "詳細|調試|信息|警告|錯誤|致命" },
 };
 
 void InitControls()
 {
    char buffer[64];
    gWidgets.gamepath->value(gConfig.pszGamePath);
-   gWidgets.cht->value(gConfig.uCodePage == CP_BIG5 ? 1 : 0);
-   gWidgets.chs->value(gConfig.uCodePage == CP_GBK ? 1 : 0);
    gWidgets.msgfile->value(gConfig.pszMsgFile);
-   gWidgets.font->value(gConfig.fUseEmbeddedFonts ? 1 : 0);
+   gWidgets.fontfile->value(gConfig.pszFontFile);
+   gWidgets.logfile->value(gConfig.pszLogFile);
+   gWidgets.loglevel->value(gConfig.iLogLevel);
    gWidgets.touch->value(gConfig.fUseTouchOverlay ? 1 : 0);
-#if SDL_VERSION_ATLEAST(2,0,0)
    gWidgets.aspect->value(gConfig.fKeepAspectRatio ? 1 : 0);
-#endif
    gWidgets.fullscreen->value(gConfig.fFullScreen ? 1 : 0);
    gWidgets.cd->value(gConfig.eCDType - MUSIC_MP3);
-   gWidgets.bgm->value(gConfig.eMusicType - MUSIC_MIN);
+   gWidgets.bgm->value(gConfig.eMusicType);
    gWidgets.stereo->value(gConfig.iAudioChannels == 2 ? 1 : 0);
    sprintf(buffer, "%d", gConfig.iSampleRate); gWidgets.samplerate->value(buffer);
    gWidgets.opl->value(gConfig.eOPLType);
@@ -123,18 +120,21 @@ void InitControls()
 void SaveControls()
 {
    free(gConfig.pszGamePath);
-   gConfig.pszGamePath = strlen(gWidgets.gamepath->value()) ? strdup(gWidgets.gamepath->value()) : nullptr;
    free(gConfig.pszMsgFile);
-   gConfig.pszMsgFile = strlen(gWidgets.msgfile->value()) ? strdup(gWidgets.msgfile->value()) : nullptr;
-   gConfig.uCodePage = (gWidgets.cht->value() ? CP_BIG5 : CP_GBK);
-   gConfig.fUseEmbeddedFonts = gWidgets.font->value();
+   free(gConfig.pszFontFile);
+   free(gConfig.pszLogFile);
+
+   gConfig.pszGamePath = *gWidgets.gamepath->value() ? strdup(gWidgets.gamepath->value()) : nullptr;
+   gConfig.pszMsgFile = *gWidgets.msgfile->value() ? strdup(gWidgets.msgfile->value()) : nullptr;
+   gConfig.pszFontFile = *gWidgets.fontfile->value() ? strdup(gWidgets.fontfile->value()) : nullptr;
+   gConfig.pszLogFile = *gWidgets.logfile->value() ? strdup(gWidgets.logfile->value()) : nullptr;
+
+   gConfig.iLogLevel = (LOGLEVEL)gWidgets.loglevel->value();
    gConfig.fUseTouchOverlay = gWidgets.touch->value();
-#if SDL_VERSION_ATLEAST(2,0,0)
    gConfig.fKeepAspectRatio = gWidgets.aspect->value();
-#endif
    gConfig.fFullScreen = gWidgets.fullscreen->value();
    gConfig.eCDType = (MUSICTYPE)(gWidgets.cd->value() + MUSIC_MP3);
-   gConfig.eMusicType = (MUSICTYPE)(gWidgets.bgm->value() + MUSIC_MIN);
+   gConfig.eMusicType = (MUSICTYPE)(gWidgets.bgm->value());
    gConfig.iAudioChannels = gWidgets.stereo->value() ? 2 : 1;
    gConfig.iSampleRate = atoi(gWidgets.samplerate->value());
    gConfig.eOPLType = (OPLTYPE)gWidgets.opl->value();
@@ -167,40 +167,42 @@ Fl_Window* InitWindow()
    int lang = GetLanguage();
    Fl_Window* window = new Fl_Window(640, 400, gLabels[lang].title);
 
-   (gWidgets.gamepath = new Fl_Input(160, 9, 475, 22, gLabels[lang].gamepath))->value(gConfig.pszGamePath);
-
-   (new Fl_Box(FL_BORDER_BOX, 5, 70, 310, 100, gLabels[lang].language))->align(FL_ALIGN_TOP);
-   gWidgets.cht = new Fl_Radio_Round_Button(10, 80, 160, 20, gLabels[lang].cht);
-   gWidgets.chs = new Fl_Radio_Round_Button(10, 110, 160, 20, gLabels[lang].chs);
-   (gWidgets.msgfile = new Fl_Input(109, 139, 196, 22, gLabels[lang].msgfile))->value(gConfig.pszMsgFile);
-
-   (new Fl_Box(FL_BORDER_BOX, 325, 70, 310, 100, gLabels[lang].display))->align(FL_ALIGN_TOP);
-   gWidgets.font = new Fl_Check_Button(330, 80, 160, 20, gLabels[lang].font);
-   gWidgets.touch = new Fl_Check_Button(330, 110, 160, 20, gLabels[lang].touch);
-   gWidgets.aspect = new Fl_Check_Button(330, 140, 160, 20, gLabels[lang].aspect);
-   gWidgets.fullscreen = new Fl_Check_Button(520, 140, 120, 20, gLabels[lang].fullscreen);
-
-   (new Fl_Box(FL_BORDER_BOX, 5, 210, 630, 130, gLabels[lang].audio))->align(FL_ALIGN_TOP);
-   (gWidgets.cd = new Fl_Choice(84, 219, lang ? 100 : 120, 22, gLabels[lang].cd))->add("MP3|OGG");
-   (gWidgets.bgm = new Fl_Choice(285, 219, 60, 22, gLabels[lang].bgm))->add( va("%s%s",(PAL_HAS_NATIVEMIDI ? "MIDI|" : ""), "RIX|MP3|OGG") );
-   gWidgets.stereo = new Fl_Check_Button(365, 220, 60, 20, gLabels[lang].stereo);
-   gWidgets.samplerate = new Fl_Int_Input(570, 219, 60, 22, gLabels[lang].samplerate);
-   (gWidgets.opl = new Fl_Choice(84, 249, lang ? 100 : 120, 22, gLabels[lang].opl))->add("DOSBOX|MAME|DOSBOXNEW");
-   gWidgets.oplrate = new Fl_Int_Input(285, 249, 60, 22, gLabels[lang].oplrate);
-   gWidgets.surround = new Fl_Check_Button(365, 250, 60, 20, gLabels[lang].surround);
-   gWidgets.buffer = new Fl_Int_Input(570, 249, 60, 22, gLabels[lang].buffer);
-
-   gWidgets.quality = new Fl_Hor_Value_Slider(72, 279, 180, 22, gLabels[lang].quality);
+   gWidgets.gamepath = new Fl_Input(160, 9, 475, 22, gLabels[lang].gamepath);
+
+   (new Fl_Box(FL_BORDER_BOX, 5, 50, 630, 55, gLabels[lang].language))->align(FL_ALIGN_TOP);
+   gWidgets.msgfile = new Fl_Input(109, 54, 516, 22, gLabels[lang].msgfile);
+   gWidgets.fontfile = new Fl_Input(109, 79, 516, 22, gLabels[lang].fontfile);
+
+   (new Fl_Box(FL_BORDER_BOX, 5, 127, 630, 30, gLabels[lang].logging))->align(FL_ALIGN_TOP);
+   (gWidgets.loglevel = new Fl_Choice(85, 132, 120, 20, gLabels[lang].loglevel))->add(gLabels[lang].levels);
+   gWidgets.logfile = new Fl_Input(284, 131, 341, 22, gLabels[lang].logfile);
+
+   (new Fl_Box(FL_BORDER_BOX, 5, 180, 630, 30, gLabels[lang].display))->align(FL_ALIGN_TOP);
+   gWidgets.touch = new Fl_Check_Button(30, 185, 160, 20, gLabels[lang].touch);
+   gWidgets.aspect = new Fl_Check_Button(260, 185, 160, 20, gLabels[lang].aspect);
+   gWidgets.fullscreen = new Fl_Check_Button(510, 185, 120, 20, gLabels[lang].fullscreen);
+
+   (new Fl_Box(FL_BORDER_BOX, 5, 230, 630, 130, gLabels[lang].audio))->align(FL_ALIGN_TOP);
+   (gWidgets.cd = new Fl_Choice(84, 239, lang ? 100 : 120, 22, gLabels[lang].cd))->add("MP3|OGG");
+   (gWidgets.bgm = new Fl_Choice(285, 239, 60, 22, gLabels[lang].bgm))->add("MIDI|RIX|MP3|OGG");
+   gWidgets.stereo = new Fl_Check_Button(365, 240, 60, 20, gLabels[lang].stereo);
+   gWidgets.samplerate = new Fl_Int_Input(570, 239, 60, 22, gLabels[lang].samplerate);
+   (gWidgets.opl = new Fl_Choice(84, 269, lang ? 100 : 120, 22, gLabels[lang].opl))->add("DOSBOX|MAME|DOSBOXNEW");
+   gWidgets.oplrate = new Fl_Int_Input(285, 269, 60, 22, gLabels[lang].oplrate);
+   gWidgets.surround = new Fl_Check_Button(365, 270, 60, 20, gLabels[lang].surround);
+   gWidgets.buffer = new Fl_Int_Input(570, 269, 60, 22, gLabels[lang].buffer);
+
+   gWidgets.quality = new Fl_Hor_Value_Slider(72, 299, 180, 22, gLabels[lang].quality);
    gWidgets.quality->align(FL_ALIGN_LEFT);
    gWidgets.quality->bounds(0, 4);
    gWidgets.quality->precision(0);
 
-   gWidgets.music = new Fl_Hor_Value_Slider(380, 279, 250, 22, gLabels[lang].musvol);
+   gWidgets.music = new Fl_Hor_Value_Slider(380, 299, 250, 22, gLabels[lang].musvol);
    gWidgets.music->align(FL_ALIGN_LEFT);
    gWidgets.music->bounds(0, 100);
    gWidgets.music->precision(0);
 
-   gWidgets.sound = new Fl_Hor_Value_Slider(380, 309, 250, 22, gLabels[lang].sndvol);
+   gWidgets.sound = new Fl_Hor_Value_Slider(380, 329, 250, 22, gLabels[lang].sndvol);
    gWidgets.sound->align(FL_ALIGN_LEFT);
    gWidgets.sound->bounds(0, 100);
    gWidgets.sound->precision(0);

+ 18 - 12
win32/resource.h

@@ -2,19 +2,19 @@
 // Microsoft Visual C++ 生成的包含文件。
 // 供 sdlpal.rc 使用
 //
+#define APP_MANIFEST                    1
 #define IDI_SDLPAL                      101
 #define IDD_LAUNCHER                    101
 #define IDS_CONFIRM                     103
-#define APP_MANIFEST                    1
-#define IDC_GAMEPATH                    1000
-#define IDC_CHT                         1001
-#define IDC_CHS                         1002
-#define IDC_EMBEDFONT                   1003
-#define IDC_ASPECTRATIO                 1004
-#define IDC_FULLSCREEN                  1005
-#define IDC_TOUCHOVERLAY                1006
-#define IDC_MSGFILE                     1007
-#define IDC_BRGAME                      1008
+#define IDC_DEFAULT                     1000
+#define IDC_BRGAME                      1001
+#define IDC_GAMEPATH                    1002
+#define IDC_ASPECTRATIO                 1003
+#define IDC_FULLSCREEN                  1004
+#define IDC_TOUCHOVERLAY                1005
+#define IDC_USEMSGFILE                  1006
+#define IDC_BRMSG                       1007
+#define IDC_MSGFILE                     1008
 #define IDC_CD                          1009
 #define IDC_BGM                         1010
 #define IDC_OPL                         1011
@@ -26,7 +26,13 @@
 #define IDC_QUALITY                     1017
 #define IDC_SOUNDVOLUME                 1018
 #define IDC_MUSICVOLUME                 1019
-#define IDC_DEFAULT                     1020
+#define IDC_LOGLEVEL                    1020
+#define IDC_USEFONTFILE                 1021
+#define IDC_BRFONT                      1022
+#define IDC_FONTFILE                    1023
+#define IDC_USELOGFILE                  1024
+#define IDC_BRLOG                       1025
+#define IDC_LOGFILE                     1026
 #define IDC_STATIC                      -1
 
 // Next default values for new objects
@@ -35,7 +41,7 @@
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_NEXT_RESOURCE_VALUE        104
 #define _APS_NEXT_COMMAND_VALUE         40001
-#define _APS_NEXT_CONTROL_VALUE         1021
+#define _APS_NEXT_CONTROL_VALUE         1024
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif

+ 185 - 200
win32/sdlpal.rc

@@ -24,52 +24,59 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
 // Dialog
 //
 
-IDD_LAUNCHER DIALOGEX 0, 0, 405, 206
+IDD_LAUNCHER DIALOGEX 0, 0, 405, 250
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
 EXSTYLE WS_EX_APPWINDOW
 CAPTION "SDL PAL Launcher"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "&Launch game",IDOK,177,185,50,14
-    PUSHBUTTON      "E&xit",IDCANCEL,7,185,50,14
+    DEFPUSHBUTTON   "&Launch game",IDOK,177,229,50,14
+    PUSHBUTTON      "E&xit",IDCANCEL,7,229,50,14
     LTEXT           "Game resource path:",IDC_STATIC,14,10,68,8
     EDITTEXT        IDC_GAMEPATH,84,7,256,14,ES_AUTOHSCROLL | ES_READONLY
-    LTEXT           "CD source:",IDC_STATIC,14,106,36,8
-    GROUPBOX        "Game Language",IDC_STATIC,7,28,192,50
-    CONTROL         "&Traditional Chinese",IDC_CHT,"Button",BS_AUTORADIOBUTTON | WS_GROUP,14,44,77,10
-    CONTROL         "Simplified &Chinese",IDC_CHS,"Button",BS_AUTORADIOBUTTON,102,44,73,10
-    CONTROL         "Use &embedded font",IDC_EMBEDFONT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,215,44,79,10
-    GROUPBOX        "Display",IDC_STATIC,206,28,192,50
-    CONTROL         "&Keep aspect ratio",IDC_ASPECTRATIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,215,60,72,10
-    LTEXT           "BGM source:",IDC_STATIC,115,106,41,8
-    CONTROL         "&Full screen",IDC_FULLSCREEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,310,44,50,10
-    EDITTEXT        IDC_MSGFILE,59,57,131,14,ES_AUTOHSCROLL
-    GROUPBOX        "Audio",IDC_STATIC,7,86,391,85
-    COMBOBOX        IDC_CD,54,103,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    COMBOBOX        IDC_BGM,157,103,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    LTEXT           "OPL type:",IDC_STATIC,14,125,33,8
-    COMBOBOX        IDC_OPL,54,122,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    CONTROL         "S&tereo",IDC_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,106,37,10
-    PUSHBUTTON      "&Default",IDC_DEFAULT,348,185,50,14
-    CONTROL         "Surround &OPL",IDC_SURROUNDOPL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,125,55,10
-    LTEXT           "Audio sample rate:                      Hz",IDC_STATIC,280,106,113,8
-    EDITTEXT        IDC_SAMPLERATE,342,103,40,14,ES_AUTOHSCROLL | ES_NUMBER
-    LTEXT           "OPL sample rate:                       Hz",IDC_STATIC,108,125,110,8
-    EDITTEXT        IDC_OPLSR,165,122,40,14,ES_AUTOHSCROLL | ES_NUMBER
-    LTEXT           "Audio buffer:",IDC_STATIC,298,125,44,8
-    EDITTEXT        IDC_AUDIOBUFFER,342,122,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "CD source:",IDC_STATIC,14,160,36,8
+    GROUPBOX        "Font && Language",IDC_STATIC,7,30,391,50
+    GROUPBOX        "Display",IDC_STATIC,7,110,391,30
+    CONTROL         "&Keep aspect ratio",IDC_ASPECTRATIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,124,72,10
+    LTEXT           "BGM source:",IDC_STATIC,115,160,41,8
+    CONTROL         "&Full screen",IDC_FULLSCREEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,124,50,10
+    EDITTEXT        IDC_MSGFILE,70,61,270,14,ES_AUTOHSCROLL | ES_READONLY
+    GROUPBOX        "Audio",IDC_STATIC,7,140,391,85
+    COMBOBOX        IDC_CD,54,157,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    COMBOBOX        IDC_BGM,157,157,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    LTEXT           "OPL type:",IDC_STATIC,14,179,33,8
+    COMBOBOX        IDC_OPL,54,176,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    CONTROL         "S&tereo",IDC_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,160,37,10
+    PUSHBUTTON      "&Default",IDC_DEFAULT,348,229,50,14
+    CONTROL         "Surround &OPL",IDC_SURROUNDOPL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,179,55,10
+    LTEXT           "Audio sample rate:                      Hz",IDC_STATIC,280,160,113,8
+    EDITTEXT        IDC_SAMPLERATE,342,157,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "OPL sample rate:                       Hz",IDC_STATIC,108,179,110,8
+    EDITTEXT        IDC_OPLSR,165,176,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "Audio buffer:",IDC_STATIC,298,179,44,8
+    EDITTEXT        IDC_AUDIOBUFFER,342,176,40,14,ES_AUTOHSCROLL | ES_NUMBER
     PUSHBUTTON      "&Browse",IDC_BRGAME,348,7,50,14
-    LTEXT           "Message file:",IDC_STATIC,14,60,43,8
-    CONTROL         "Enable &touch overlay",IDC_TOUCHOVERLAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,310,60,83,10
-    LTEXT           "Quality:",IDC_STATIC,14,144,26,8
-    LTEXT           "Low                              High",IDC_STATIC,31,158,88,8
-    CONTROL         "",IDC_QUALITY,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,43,143,60,15
-    LTEXT           "Music volume:",IDC_STATIC,118,144,46,8
-    LTEXT           "Low                                      High",IDC_STATIC,153,158,104,8
-    CONTROL         "",IDC_MUSICVOLUME,"msctls_trackbar32",WS_TABSTOP,165,143,80,15
-    LTEXT           "Sound volume:",IDC_STATIC,256,145,48,8
-    LTEXT           "Low                                      High",IDC_STATIC,291,158,104,8
-    CONTROL         "",IDC_SOUNDVOLUME,"msctls_trackbar32",WS_TABSTOP,303,144,80,15
+    CONTROL         "Message file:",IDC_USEMSGFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,64,56,8
+    CONTROL         "Enable &touch overlay",IDC_TOUCHOVERLAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,306,124,83,10
+    LTEXT           "Quality:",IDC_STATIC,14,198,26,8
+    LTEXT           "Low                              High",IDC_STATIC,31,212,88,8
+    CONTROL         "",IDC_QUALITY,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,43,197,60,15
+    LTEXT           "Music volume:",IDC_STATIC,118,198,46,8
+    LTEXT           "Low                                      High",IDC_STATIC,153,212,104,8
+    CONTROL         "",IDC_MUSICVOLUME,"msctls_trackbar32",WS_TABSTOP,165,197,80,15
+    LTEXT           "Sound volume:",IDC_STATIC,256,199,48,8
+    LTEXT           "Low                                      High",IDC_STATIC,291,212,104,8
+    CONTROL         "",IDC_SOUNDVOLUME,"msctls_trackbar32",WS_TABSTOP,303,198,80,15
+    EDITTEXT        IDC_FONTFILE,70,43,270,14,ES_AUTOHSCROLL | ES_READONLY
+    CONTROL         "Font file:",IDC_USEFONTFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,46,56,8
+    GROUPBOX        "Logging",IDC_STATIC,7,80,391,30
+    LTEXT           "Log level:",IDC_STATIC,14,95,32,8
+    COMBOBOX        IDC_LOGLEVEL,46,92,61,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    CONTROL         "Log file:",IDC_USELOGFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,95,40,8
+    EDITTEXT        IDC_LOGFILE,160,92,180,14,ES_AUTOHSCROLL | ES_READONLY
+    PUSHBUTTON      "&Browse",IDC_BRFONT,348,43,50,14,WS_DISABLED
+    PUSHBUTTON      "&Browse",IDC_BRMSG,348,61,50,14,WS_DISABLED
+    PUSHBUTTON      "&Browse",IDC_BRLOG,348,92,50,14,WS_DISABLED
 END
 
 
@@ -86,7 +93,7 @@ BEGIN
         LEFTMARGIN, 7
         RIGHTMARGIN, 398
         TOPMARGIN, 7
-        BOTTOMMARGIN, 199
+        BOTTOMMARGIN, 243
     END
 END
 #endif    // APSTUDIO_INVOKED
@@ -103,33 +110,6 @@ BEGIN
 END
 
 
-/////////////////////////////////////////////////////////////////////////////
-//
-// Dialog Info
-//
-
-IDD_LAUNCHER DLGINIT
-BEGIN
-    IDC_CD, 0x403, 4, 0,
-0x504d, 0x0033, 
-    IDC_CD, 0x403, 4, 0,
-0x474f, 0x0047, 
-    IDC_BGM, 0x403, 4, 0,
-0x4952, 0x0058, 
-    IDC_BGM, 0x403, 5, 0,
-0x494d, 0x4944, "\000", 
-    IDC_BGM, 0x403, 4, 0,
-0x504d, 0x0033, 
-    IDC_BGM, 0x403, 4, 0,
-0x474f, 0x0047, 
-    IDC_OPL, 0x403, 7, 0,
-0x4f44, 0x4253, 0x584f, "\000", 
-    IDC_OPL, 0x403, 5, 0,
-0x414d, 0x454d, "\000", 
-    0
-END
-
-
 /////////////////////////////////////////////////////////////////////////////
 //
 // RT_MANIFEST
@@ -200,6 +180,21 @@ END
 STRINGTABLE
 BEGIN
     IDC_BRGAME              "Open game resource folder"
+    IDC_BRMSG               "Select customized game message file"
+END
+
+STRINGTABLE
+BEGIN
+    IDC_MSGFILE             "Message file (*.msg)|*.msg||"
+    IDC_LOGLEVEL            "Verbose;Debug;Informational;Warning;Error;Fatal"
+    IDC_BRFONT              "Select customized font file"
+    IDC_FONTFILE            "Font file (*.bdf)|*.bdf||"
+END
+
+STRINGTABLE
+BEGIN
+    IDC_BRLOG               "Select file to record logs"
+    IDC_LOGFILE             "Log file (*.log, *.txt)|*.log;*.txt||"
 END
 
 #endif    // 非特定语言 resources
@@ -218,52 +213,59 @@ LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
 // Dialog
 //
 
-IDD_LAUNCHER DIALOGEX 0, 0, 405, 206
+IDD_LAUNCHER DIALOGEX 0, 0, 405, 250
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
 EXSTYLE WS_EX_APPWINDOW
 CAPTION "SDL PAL 启动器"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "启动游戏(&L)",IDOK,177,185,50,14
-    PUSHBUTTON      "退出(&X)",IDCANCEL,7,185,50,14
-    LTEXT           "游戏资源目录:",IDC_STATIC,14,10,57,8
-    EDITTEXT        IDC_GAMEPATH,71,7,270,14,ES_AUTOHSCROLL | ES_READONLY
-    LTEXT           "CD 音源:",IDC_STATIC,14,106,36,8
-    GROUPBOX        "游戏语言设置",IDC_STATIC,7,28,192,50
-    CONTROL         "繁体中文(&T)",IDC_CHT,"Button",BS_AUTORADIOBUTTON | WS_GROUP,14,44,57,10
-    CONTROL         "简体中文(&C)",IDC_CHS,"Button",BS_AUTORADIOBUTTON,102,44,58,10
-    CONTROL         "使用游戏内字体(&E)",IDC_EMBEDFONT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,215,44,81,10
-    GROUPBOX        "显示设置",IDC_STATIC,206,28,192,50
-    CONTROL         "保持纵横比(&K)",IDC_ASPECTRATIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,215,60,65,10
-    LTEXT           "BGM 音源:",IDC_STATIC,115,106,41,8
-    CONTROL         "全屏启动游戏(&F)",IDC_FULLSCREEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,310,44,73,10
-    EDITTEXT        IDC_MSGFILE,57,57,133,14,ES_AUTOHSCROLL
-    GROUPBOX        "音频设置",IDC_STATIC,7,86,391,85
-    COMBOBOX        IDC_CD,54,103,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    COMBOBOX        IDC_BGM,157,103,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    LTEXT           "OPL 类型:",IDC_STATIC,14,125,40,8
-    COMBOBOX        IDC_OPL,54,122,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    CONTROL         "立体声(&T)",IDC_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,106,49,10
-    PUSHBUTTON      "默认设置(&D)",IDC_DEFAULT,348,185,50,14
-    CONTROL         "环绕声 &OPL",IDC_SURROUNDOPL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,125,55,10
-    LTEXT           "音频采样率:                      Hz",IDC_STATIC,285,106,101,8
-    EDITTEXT        IDC_SAMPLERATE,335,103,40,14,ES_AUTOHSCROLL | ES_NUMBER
-    LTEXT           "OPL 采样率:                       Hz",IDC_STATIC,115,125,102,8
-    EDITTEXT        IDC_OPLSR,165,122,40,14,ES_AUTOHSCROLL | ES_NUMBER
-    LTEXT           "音频缓冲区:",IDC_STATIC,285,125,49,8
-    EDITTEXT        IDC_AUDIOBUFFER,335,122,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    DEFPUSHBUTTON   "启动游戏(&L)",IDOK,177,229,50,14
+    PUSHBUTTON      "退出(&X)",IDCANCEL,7,229,50,14
+    LTEXT           "游戏资源目录:",IDC_STATIC,14,10,56,8
+    EDITTEXT        IDC_GAMEPATH,70,7,270,14,ES_AUTOHSCROLL | ES_READONLY
+    LTEXT           "CD 音源:",IDC_STATIC,14,160,36,8
+    GROUPBOX        "字体及语言设置",IDC_STATIC,7,30,391,50
+    GROUPBOX        "显示设置",IDC_STATIC,7,110,391,30
+    CONTROL         "保持纵横比(&K)",IDC_ASPECTRATIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,160,124,65,10
+    LTEXT           "BGM 音源:",IDC_STATIC,115,160,41,8
+    CONTROL         "全屏启动游戏(&F)",IDC_FULLSCREEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,124,73,10
+    EDITTEXT        IDC_MSGFILE,70,61,270,14,ES_AUTOHSCROLL | ES_READONLY
+    GROUPBOX        "音频设置",IDC_STATIC,7,140,391,85
+    COMBOBOX        IDC_CD,54,157,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    COMBOBOX        IDC_BGM,157,157,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    LTEXT           "OPL 类型:",IDC_STATIC,14,179,40,8
+    COMBOBOX        IDC_OPL,54,176,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    CONTROL         "立体声(&T)",IDC_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,160,49,10
+    PUSHBUTTON      "默认设置(&D)",IDC_DEFAULT,348,229,50,14
+    CONTROL         "环绕声 &OPL",IDC_SURROUNDOPL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,179,55,10
+    LTEXT           "音频采样率:                      Hz",IDC_STATIC,285,160,101,8
+    EDITTEXT        IDC_SAMPLERATE,335,157,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "OPL 采样率:                       Hz",IDC_STATIC,115,179,102,8
+    EDITTEXT        IDC_OPLSR,165,176,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "音频缓冲区:",IDC_STATIC,285,179,49,8
+    EDITTEXT        IDC_AUDIOBUFFER,335,176,40,14,ES_AUTOHSCROLL | ES_NUMBER
     PUSHBUTTON      "浏览(&B)",IDC_BRGAME,348,7,50,14
-    LTEXT           "语言文件:",IDC_STATIC,14,60,41,8
-    CONTROL         "启用触屏辅助(&T)",IDC_TOUCHOVERLAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,310,60,73,10
-    LTEXT           "音频质量:",IDC_STATIC,14,144,41,8
-    LTEXT           "低                              高",IDC_STATIC,47,158,77,8
-    CONTROL         "",IDC_QUALITY,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,55,143,60,15
-    LTEXT           "音乐音量:",IDC_STATIC,131,144,41,8
-    CONTROL         "",IDC_MUSICVOLUME,"msctls_trackbar32",WS_TABSTOP,172,143,80,15
-    LTEXT           "低                                      高",IDC_STATIC,165,158,93,8
-    LTEXT           "音效音量:",IDC_STATIC,266,145,41,8
-    CONTROL         "",IDC_SOUNDVOLUME,"msctls_trackbar32",WS_TABSTOP,307,144,80,15
-    LTEXT           "低                                      高",IDC_STATIC,300,158,93,8
+    CONTROL         "语言文件:",IDC_USEMSGFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,64,56,8
+    CONTROL         "启用触屏辅助(&T)",IDC_TOUCHOVERLAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,309,124,73,10
+    LTEXT           "音频质量:",IDC_STATIC,14,198,41,8
+    LTEXT           "低                              高",IDC_STATIC,47,212,77,8
+    CONTROL         "",IDC_QUALITY,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,55,197,60,15
+    LTEXT           "音乐音量:",IDC_STATIC,131,198,41,8
+    CONTROL         "",IDC_MUSICVOLUME,"msctls_trackbar32",WS_TABSTOP,172,197,80,15
+    LTEXT           "低                                      高",IDC_STATIC,165,212,93,8
+    LTEXT           "音效音量:",IDC_STATIC,266,199,41,8
+    CONTROL         "",IDC_SOUNDVOLUME,"msctls_trackbar32",WS_TABSTOP,307,198,80,15
+    LTEXT           "低                                      高",IDC_STATIC,300,212,93,8
+    EDITTEXT        IDC_FONTFILE,70,43,270,14,ES_AUTOHSCROLL | ES_READONLY
+    CONTROL         "字体文件:",IDC_USEFONTFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,46,56,8
+    GROUPBOX        "日志记录设置",IDC_STATIC,7,80,391,30
+    LTEXT           "记录级别:",IDC_STATIC,14,95,41,8
+    COMBOBOX        IDC_LOGLEVEL,57,92,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    CONTROL         "记录到文件:",IDC_USELOGFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,95,55,8
+    EDITTEXT        IDC_LOGFILE,175,92,165,14,ES_AUTOHSCROLL | ES_READONLY
+    PUSHBUTTON      "浏览(&B)",IDC_BRFONT,348,43,50,14,WS_DISABLED
+    PUSHBUTTON      "浏览(&B)",IDC_BRMSG,348,61,50,14,WS_DISABLED
+    PUSHBUTTON      "浏览(&B)",IDC_BRLOG,348,92,50,14,WS_DISABLED
 END
 
 
@@ -280,7 +282,7 @@ BEGIN
         LEFTMARGIN, 7
         RIGHTMARGIN, 398
         TOPMARGIN, 7
-        BOTTOMMARGIN, 199
+        BOTTOMMARGIN, 243
     END
 END
 #endif    // APSTUDIO_INVOKED
@@ -297,33 +299,6 @@ BEGIN
 END
 
 
-/////////////////////////////////////////////////////////////////////////////
-//
-// Dialog Info
-//
-
-IDD_LAUNCHER DLGINIT
-BEGIN
-    IDC_CD, 0x403, 4, 0,
-0x504d, 0x0033, 
-    IDC_CD, 0x403, 4, 0,
-0x474f, 0x0047, 
-    IDC_BGM, 0x403, 4, 0,
-0x4952, 0x0058, 
-    IDC_BGM, 0x403, 5, 0,
-0x494d, 0x4944, "\000", 
-    IDC_BGM, 0x403, 4, 0,
-0x504d, 0x0033, 
-    IDC_BGM, 0x403, 4, 0,
-0x474f, 0x0047, 
-    IDC_OPL, 0x403, 7, 0,
-0x4f44, 0x4253, 0x584f, "\000", 
-    IDC_OPL, 0x403, 5, 0,
-0x414d, 0x454d, "\000", 
-    0
-END
-
-
 #ifdef APSTUDIO_INVOKED
 /////////////////////////////////////////////////////////////////////////////
 //
@@ -363,6 +338,21 @@ END
 STRINGTABLE
 BEGIN
     IDC_BRGAME              "打开游戏资源文件夹"
+    IDC_BRMSG               "选择自定义游戏语言文件"
+END
+
+STRINGTABLE
+BEGIN
+    IDC_MSGFILE             "语言文件(*.msg)|*.msg||"
+    IDC_LOGLEVEL            "详细;调试;信息;警告;错误;致命"
+    IDC_BRFONT              "选择自定义字体文件"
+    IDC_FONTFILE            "字体文件(*.bdf)|*.bdf||"
+END
+
+STRINGTABLE
+BEGIN
+    IDC_BRLOG               "选择用于日志记录的文件"
+    IDC_LOGFILE             "日志文件(*.log, *.txt)|*.log;*.txt||"
 END
 
 #endif    // 中文(简体,中国) resources
@@ -381,52 +371,59 @@ LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
 // Dialog
 //
 
-IDD_LAUNCHER DIALOGEX 0, 0, 405, 206
+IDD_LAUNCHER DIALOGEX 0, 0, 405, 250
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
 EXSTYLE WS_EX_APPWINDOW
 CAPTION "SDL PAL 币笆竟"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "币笆笴栏(&L)",IDOK,177,185,50,14
-    PUSHBUTTON      "癶�(&X)",IDCANCEL,7,185,50,14
+    DEFPUSHBUTTON   "币笆笴栏(&L)",IDOK,177,229,50,14
+    PUSHBUTTON      "癶�(&X)",IDCANCEL,7,229,50,14
     LTEXT           "笴栏戈方郎Ж�",IDC_STATIC,14,10,57,8
-    EDITTEXT        IDC_GAMEPATH,71,7,270,14,ES_AUTOHSCROLL | ES_READONLY
-    LTEXT           "CD �方�",IDC_STATIC,14,106,36,8
-    GROUPBOX        "笴栏粂ē砞﹚",IDC_STATIC,7,28,192,50
-    CONTROL         "羉砰いゅ(&T)",IDC_CHT,"Button",BS_AUTORADIOBUTTON | WS_GROUP,14,44,57,10
-    CONTROL         "虏砰いゅ(&C)",IDC_CHS,"Button",BS_AUTORADIOBUTTON,102,44,58,10
-    CONTROL         "ㄏノ笴栏ず�砰(&E)",IDC_EMBEDFONT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,215,44,81,10
-    GROUPBOX        "陪ボ砞﹚",IDC_STATIC,206,28,192,50
-    CONTROL         "玂�羇绢ゑ(&K)",IDC_ASPECTRATIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,215,60,65,10
-    LTEXT           "BGM �方�",IDC_STATIC,115,106,41,8
-    CONTROL         "��币笆笴栏(&F)",IDC_FULLSCREEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,310,44,73,10
-    EDITTEXT        IDC_MSGFILE,57,57,133,14,ES_AUTOHSCROLL
-    GROUPBOX        "�癟砞﹚",IDC_STATIC,7,86,391,85
-    COMBOBOX        IDC_CD,54,103,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    COMBOBOX        IDC_BGM,157,103,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    LTEXT           "OPL 摸��",IDC_STATIC,14,124,40,8
-    COMBOBOX        IDC_OPL,54,121,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    CONTROL         "ミ砰羘(&T)",IDC_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,230,106,49,10
-    PUSHBUTTON      "纐粄砞﹚(&D)",IDC_DEFAULT,348,185,50,14
-    CONTROL         "吏露羘 &OPL",IDC_SURROUNDOPL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,230,124,55,10
-    LTEXT           "�癟�妓硉瞯�                      Hz",IDC_STATIC,285,106,109,8
-    EDITTEXT        IDC_SAMPLERATE,342,103,40,14,ES_AUTOHSCROLL | ES_NUMBER
-    LTEXT           "OPL �妓硉瞯�                       Hz",IDC_STATIC,115,124,110,8
-    EDITTEXT        IDC_OPLSR,172,121,40,14,ES_AUTOHSCROLL | ES_NUMBER
-    LTEXT           "�癟珇借�",IDC_STATIC,14,144,41,8
-    CONTROL         "",IDC_QUALITY,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,55,143,60,15
-    LTEXT           "�癟絯侥跋�",IDC_STATIC,293,124,48,8
-    EDITTEXT        IDC_AUDIOBUFFER,342,121,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    EDITTEXT        IDC_GAMEPATH,70,7,270,14,ES_AUTOHSCROLL | ES_READONLY
+    LTEXT           "CD �方�",IDC_STATIC,14,160,36,8
+    GROUPBOX        "�砰の粂ē砞﹚",IDC_STATIC,7,30,391,50
+    GROUPBOX        "陪ボ砞﹚",IDC_STATIC,7,110,391,30
+    CONTROL         "玂�羇绢ゑ(&K)",IDC_ASPECTRATIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,160,124,65,10
+    LTEXT           "BGM �方�",IDC_STATIC,115,160,41,8
+    CONTROL         "��币笆笴栏(&F)",IDC_FULLSCREEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,124,73,10
+    EDITTEXT        IDC_MSGFILE,70,61,270,14,ES_AUTOHSCROLL | ES_READONLY
+    GROUPBOX        "�癟砞﹚",IDC_STATIC,7,140,391,85
+    COMBOBOX        IDC_CD,54,157,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    COMBOBOX        IDC_BGM,157,157,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    LTEXT           "OPL 摸��",IDC_STATIC,14,178,40,8
+    COMBOBOX        IDC_OPL,54,175,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    CONTROL         "ミ砰羘(&T)",IDC_STEREO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,230,160,49,10
+    PUSHBUTTON      "纐粄砞﹚(&D)",IDC_DEFAULT,348,229,50,14
+    CONTROL         "吏露羘 &OPL",IDC_SURROUNDOPL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,230,178,55,10
+    LTEXT           "�癟�妓硉瞯�                      Hz",IDC_STATIC,285,160,109,8
+    EDITTEXT        IDC_SAMPLERATE,342,157,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "OPL �妓硉瞯�                       Hz",IDC_STATIC,115,178,110,8
+    EDITTEXT        IDC_OPLSR,172,175,40,14,ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "�癟珇借�",IDC_STATIC,14,198,41,8
+    CONTROL         "",IDC_QUALITY,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,55,197,60,15
+    LTEXT           "�癟絯侥跋�",IDC_STATIC,293,178,48,8
+    EDITTEXT        IDC_AUDIOBUFFER,342,175,40,14,ES_AUTOHSCROLL | ES_NUMBER
     PUSHBUTTON      "聅凝(&B)",IDC_BRGAME,348,7,50,14
-    LTEXT           "粂ē郎�",IDC_STATIC,14,60,33,8
-    CONTROL         "币ノ牟�徊�(&T)",IDC_TOUCHOVERLAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,310,60,73,10
-    LTEXT           "�                              蔼",IDC_STATIC,47,158,77,8
-    LTEXT           "�贾�秖�",IDC_STATIC,131,144,41,8
-    CONTROL         "",IDC_MUSICVOLUME,"msctls_trackbar32",WS_TABSTOP,172,143,80,15
-    LTEXT           "�                                      蔼",IDC_STATIC,165,158,93,8
-    LTEXT           "���秖�",IDC_STATIC,266,145,41,8
-    CONTROL         "",IDC_SOUNDVOLUME,"msctls_trackbar32",WS_TABSTOP,307,144,80,15
-    LTEXT           "�                                      蔼",IDC_STATIC,300,158,93,8
+    CONTROL         "�璹粂ē郎�",IDC_USEMSGFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,64,56,8
+    CONTROL         "币ノ牟�徊�(&T)",IDC_TOUCHOVERLAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,309,124,73,10
+    LTEXT           "�                              蔼",IDC_STATIC,47,212,77,8
+    LTEXT           "�贾�秖�",IDC_STATIC,131,198,41,8
+    CONTROL         "",IDC_MUSICVOLUME,"msctls_trackbar32",WS_TABSTOP,172,197,80,15
+    LTEXT           "�                                      蔼",IDC_STATIC,165,212,93,8
+    LTEXT           "���秖�",IDC_STATIC,266,199,41,8
+    CONTROL         "",IDC_SOUNDVOLUME,"msctls_trackbar32",WS_TABSTOP,307,198,80,15
+    LTEXT           "�                                      蔼",IDC_STATIC,300,212,93,8
+    EDITTEXT        IDC_FONTFILE,70,43,270,14,ES_AUTOHSCROLL | ES_READONLY
+    CONTROL         "�璹�砰郎�",IDC_USEFONTFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,46,56,8
+    GROUPBOX        "ら粁癘魁砞﹚",IDC_STATIC,7,80,391,30
+    LTEXT           "癘魁���",IDC_STATIC,14,95,41,8
+    COMBOBOX        IDC_LOGLEVEL,57,92,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    CONTROL         "癘魁�郎�",IDC_USELOGFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,95,50,8
+    EDITTEXT        IDC_LOGFILE,170,92,170,14,ES_AUTOHSCROLL | ES_READONLY
+    PUSHBUTTON      "聅凝(&B)",IDC_BRFONT,348,43,50,14,WS_DISABLED
+    PUSHBUTTON      "聅凝(&B)",IDC_BRMSG,348,61,50,14,WS_DISABLED
+    PUSHBUTTON      "聅凝(&B)",IDC_BRLOG,348,92,50,14,WS_DISABLED
 END
 
 
@@ -443,7 +440,7 @@ BEGIN
         LEFTMARGIN, 7
         RIGHTMARGIN, 398
         TOPMARGIN, 7
-        BOTTOMMARGIN, 199
+        BOTTOMMARGIN, 243
     END
 END
 #endif    // APSTUDIO_INVOKED
@@ -462,44 +459,32 @@ END
 
 /////////////////////////////////////////////////////////////////////////////
 //
-// Dialog Info
+// String Table
 //
 
-IDD_LAUNCHER DLGINIT
+STRINGTABLE
 BEGIN
-    IDC_CD, 0x403, 4, 0,
-0x504d, 0x0033, 
-    IDC_CD, 0x403, 4, 0,
-0x474f, 0x0047, 
-    IDC_BGM, 0x403, 4, 0,
-0x4952, 0x0058, 
-    IDC_BGM, 0x403, 5, 0,
-0x494d, 0x4944, "\000", 
-    IDC_BGM, 0x403, 4, 0,
-0x504d, 0x0033, 
-    IDC_BGM, 0x403, 4, 0,
-0x474f, 0x0047, 
-    IDC_OPL, 0x403, 7, 0,
-0x4f44, 0x4253, 0x584f, "\000", 
-    IDC_OPL, 0x403, 5, 0,
-0x414d, 0x454d, "\000", 
-    0
+    IDS_CONFIRM             "絋粄"
 END
 
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// String Table
-//
+STRINGTABLE
+BEGIN
+    IDC_BRGAME              "ゴ秨笴栏戈方郎Ж"
+    IDC_BRMSG               "匡拒�璹粂ē郎"
+END
 
 STRINGTABLE
 BEGIN
-    IDS_CONFIRM             "絋粄"
+    IDC_MSGFILE             "粂ē郎(*.msg)|*.msg||"
+    IDC_LOGLEVEL            "冈灿;秸刚;獺�;牡�;岿粇;璓㏑"
+    IDC_BRFONT              "匡拒�璹�砰郎"
+    IDC_FONTFILE            "�砰郎(*.bdf)|*.bdf||"
 END
 
 STRINGTABLE
 BEGIN
-    IDC_BRGAME              "ゴ秨笴栏戈方郎Ж"
+    IDC_BRLOG               "匡拒ノ�ら粁癘魁�郎"
+    IDC_LOGFILE             "ら粁郎(*.log, *.txt)|*.log;*.txt||"
 END
 
 #endif    // 中文(繁体,中国台湾) resources

+ 3 - 3
win32/sdlpal.vcxproj.filters

@@ -658,9 +658,6 @@
     <ClInclude Include="..\liboggvorbis\include\ogg\config_types.h">
       <Filter>liboggvorbis\include\ogg</Filter>
     </ClInclude>
-    <ClInclude Include="resource.h">
-      <Filter>platform</Filter>
-    </ClInclude>
     <ClInclude Include="..\palcfg.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -670,6 +667,9 @@
     <ClInclude Include="pal_config.h">
       <Filter>platform</Filter>
     </ClInclude>
+    <ClInclude Include="resource.h">
+      <Filter>Resource Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="..\sdlpal.ico">

+ 104 - 16
win32/win32.cpp

@@ -91,16 +91,24 @@ int WINAPI LoadStringEx(
 	}
 }
 
+std::wstring LoadResourceString(UINT uID)
+{
+	auto hrc = FindResourceEx(g_hInstance, RT_STRING, MAKEINTRESOURCE((uID >> 4) + 1), g_wLanguage);
+	if (hrc)
+	{
+		auto begin = (LPCWSTR)LockResource(LoadResource(g_hInstance, hrc));
+		for (int idx = 0; idx < (int)(uID & 0xf); idx++)
+			begin += *begin + 1;
+		return std::wstring(begin + 1, *begin);
+	}
+	return L"";
+}
+
 void SaveSettings(HWND hwndDlg, BOOL fWriteFile)
 {
 	int textLen;
 
-	if (IsDlgButtonChecked(hwndDlg, IDC_CHS))
-		gConfig.uCodePage = CP_GBK;
-	else
-		gConfig.uCodePage = CP_BIG5;
-
-	if ((textLen = GetWindowTextLengthA(GetDlgItem(hwndDlg, IDC_MSGFILE))) > 0)
+	if (IsDlgButtonChecked(hwndDlg, IDC_USEMSGFILE) && (textLen = GetWindowTextLengthA(GetDlgItem(hwndDlg, IDC_MSGFILE))) > 0)
 	{
 		gConfig.pszMsgFile = (char*)realloc(gConfig.pszMsgFile, textLen + 1);
 		GetDlgItemTextA(hwndDlg, IDC_MSGFILE, gConfig.pszMsgFile, textLen + 1);
@@ -109,6 +117,27 @@ void SaveSettings(HWND hwndDlg, BOOL fWriteFile)
 	{
 		free(gConfig.pszMsgFile); gConfig.pszMsgFile = nullptr;
 	}
+
+	if (IsDlgButtonChecked(hwndDlg, IDC_USELOGFILE) && (textLen = GetWindowTextLengthA(GetDlgItem(hwndDlg, IDC_LOGFILE))) > 0)
+	{
+		gConfig.pszLogFile = (char*)realloc(gConfig.pszLogFile, textLen + 1);
+		GetDlgItemTextA(hwndDlg, IDC_LOGFILE, gConfig.pszLogFile, textLen + 1);
+	}
+	else
+	{
+		free(gConfig.pszLogFile); gConfig.pszLogFile = nullptr;
+	}
+
+	if (IsDlgButtonChecked(hwndDlg, IDC_USEFONTFILE) && (textLen = GetWindowTextLengthA(GetDlgItem(hwndDlg, IDC_FONTFILE))) > 0)
+	{
+		gConfig.pszFontFile = (char*)realloc(gConfig.pszFontFile, textLen + 1);
+		GetDlgItemTextA(hwndDlg, IDC_FONTFILE, gConfig.pszFontFile, textLen + 1);
+	}
+	else
+	{
+		free(gConfig.pszFontFile); gConfig.pszFontFile = nullptr;
+	}
+
 	if ((textLen = GetWindowTextLength(GetDlgItem(hwndDlg, IDC_GAMEPATH))) > 0)
 	{
 		gConfig.pszGamePath = (char*)realloc(gConfig.pszGamePath, textLen + 1);
@@ -121,17 +150,18 @@ void SaveSettings(HWND hwndDlg, BOOL fWriteFile)
 
 	gConfig.fFullScreen = IsDlgButtonChecked(hwndDlg, IDC_FULLSCREEN);
 	gConfig.fUseTouchOverlay = IsDlgButtonChecked(hwndDlg, IDC_TOUCHOVERLAY);
-	gConfig.fUseEmbeddedFonts = IsDlgButtonChecked(hwndDlg, IDC_EMBEDFONT);
 	gConfig.fKeepAspectRatio = IsDlgButtonChecked(hwndDlg, IDC_ASPECTRATIO);
 	gConfig.eCDType = (MUSICTYPE)(ComboBox_GetCurSel(hwndDlg, IDC_CD) + MUSIC_MP3);
 	gConfig.eMusicType = (MUSICTYPE)ComboBox_GetCurSel(hwndDlg, IDC_BGM);
 	gConfig.eOPLType = (OPLTYPE)(ComboBox_GetCurSel(hwndDlg, IDC_OPL));
+	gConfig.iLogLevel = (LOGLEVEL)(ComboBox_GetCurSel(hwndDlg, IDC_LOGLEVEL));
 	gConfig.iAudioChannels = IsDlgButtonChecked(hwndDlg, IDC_STEREO) ? 2 : 1;
 	gConfig.iSampleRate = GetDlgItemInt(hwndDlg, IDC_SAMPLERATE, nullptr, FALSE);
 	gConfig.wAudioBufferSize = GetDlgItemInt(hwndDlg, IDC_AUDIOBUFFER, nullptr, FALSE);
 	gConfig.iMusicVolume = TrackBar_GetPos(hwndDlg, IDC_MUSICVOLUME);
 	gConfig.iSoundVolume = TrackBar_GetPos(hwndDlg, IDC_SOUNDVOLUME);
 	gConfig.iResampleQuality = TrackBar_GetPos(hwndDlg, IDC_QUALITY);
+
 	if (gConfig.eMusicType == MUSIC_RIX)
 	{
 		gConfig.fUseSurroundOPL = IsDlgButtonChecked(hwndDlg, IDC_SURROUNDOPL);
@@ -149,18 +179,23 @@ void ResetControls(HWND hwndDlg)
 	EnableDlgItem(hwndDlg, IDC_SURROUNDOPL, gConfig.eMusicType == MUSIC_RIX);
 	EnableDlgItem(hwndDlg, IDC_OPLSR, gConfig.eMusicType == MUSIC_RIX);
 
-	CheckRadioButton(hwndDlg, IDC_CHT, IDC_CHS, IDC_CHT + gConfig.uCodePage);
-
 	CheckDlgButton(hwndDlg, IDC_FULLSCREEN, gConfig.fFullScreen);
 	CheckDlgButton(hwndDlg, IDC_TOUCHOVERLAY, gConfig.fUseTouchOverlay);
-	CheckDlgButton(hwndDlg, IDC_EMBEDFONT, gConfig.fUseEmbeddedFonts);
 	CheckDlgButton(hwndDlg, IDC_ASPECTRATIO, gConfig.fKeepAspectRatio);
 	CheckDlgButton(hwndDlg, IDC_SURROUNDOPL, gConfig.fUseSurroundOPL);
 	CheckDlgButton(hwndDlg, IDC_STEREO, gConfig.iAudioChannels == 2);
 
+	CheckDlgButton(hwndDlg, IDC_USEMSGFILE, gConfig.pszMsgFile != nullptr);
+	EnableDlgItem(hwndDlg, IDC_BRMSG, gConfig.pszMsgFile != nullptr);
+	CheckDlgButton(hwndDlg, IDC_USEFONTFILE, gConfig.pszFontFile != nullptr);
+	EnableDlgItem(hwndDlg, IDC_BRFONT, gConfig.pszFontFile != nullptr);
+	CheckDlgButton(hwndDlg, IDC_USELOGFILE, gConfig.pszLogFile != nullptr);
+	EnableDlgItem(hwndDlg, IDC_BRLOG, gConfig.pszLogFile != nullptr);
+
 	ComboBox_SetCurSel(hwndDlg, IDC_CD, gConfig.eCDType - MUSIC_MP3);
 	ComboBox_SetCurSel(hwndDlg, IDC_BGM, gConfig.eMusicType);
 	ComboBox_SetCurSel(hwndDlg, IDC_OPL, gConfig.eOPLType);
+	ComboBox_SetCurSel(hwndDlg, IDC_LOGLEVEL, gConfig.iLogLevel);
 
 	SetDlgItemText(hwndDlg, IDC_SAMPLERATE, _itot(gConfig.iSampleRate, buffer, 10));
 	SetDlgItemText(hwndDlg, IDC_OPLSR, _itot(gConfig.iOPLSampleRate, buffer, 10));
@@ -168,6 +203,8 @@ void ResetControls(HWND hwndDlg)
 
 	if (gConfig.pszGamePath) SetDlgItemTextA(hwndDlg, IDC_GAMEPATH, gConfig.pszGamePath);
 	if (gConfig.pszMsgFile) SetDlgItemTextA(hwndDlg, IDC_MSGFILE, gConfig.pszMsgFile);
+	if (gConfig.pszFontFile) SetDlgItemTextA(hwndDlg, IDC_FONTFILE, gConfig.pszFontFile);
+	if (gConfig.pszLogFile) SetDlgItemTextA(hwndDlg, IDC_LOGFILE, gConfig.pszLogFile);
 
 	TrackBar_SetPos(hwndDlg, IDC_QUALITY, gConfig.iResampleQuality, TRUE);
 	TrackBar_SetPos(hwndDlg, IDC_MUSICVOLUME, gConfig.iMusicVolume, TRUE);
@@ -178,6 +215,27 @@ INT_PTR InitProc(HWND hwndDlg, HWND hwndCtrl, LPARAM lParam)
 {
 	InitCommonControls();
 
+	auto log_levels = LoadResourceString(IDC_LOGLEVEL);
+	for (size_t pos = 0; pos != std::string::npos; )
+	{
+		std::wstring item;
+		auto next = log_levels.find(L';', pos);
+		if (next != std::string::npos)
+		{
+			item.assign(log_levels.c_str() + pos, next - pos);
+			pos = next + 1;
+		}
+		else
+		{
+			item.assign(log_levels.c_str() + pos);
+			pos = next;
+		}
+		if (item.length() > 0)
+		{
+			ComboBox_AddString(hwndDlg, IDC_LOGLEVEL, item.c_str());
+		}
+	}
+
 	ComboBox_AddString(hwndDlg, IDC_CD, TEXT("MP3"));
 	ComboBox_AddString(hwndDlg, IDC_CD, TEXT("OGG"));
 
@@ -231,7 +289,7 @@ INT_PTR ButtonProc(HWND hwndDlg, WORD idControl, HWND hwndCtrl)
 	{
 		TCHAR szName[MAX_PATH * 2], szTitle[200];
 		BROWSEINFO bi = { hwndDlg, nullptr, szName, szTitle, BIF_USENEWUI, nullptr, NULL, 0 };
-		LoadStringEx(g_hInstance, idControl, g_wLanguage, szTitle, 200);
+		LoadStringEx(g_hInstance, IDC_BRGAME, g_wLanguage, szTitle, 200);
 		auto pidl = SHBrowseForFolder(&bi);
 		if (pidl)
 		{
@@ -243,6 +301,34 @@ INT_PTR ButtonProc(HWND hwndDlg, WORD idControl, HWND hwndCtrl)
 		return TRUE;
 	}
 
+	case IDC_BRFONT:
+	case IDC_BRMSG:
+	case IDC_BRLOG:
+	{
+		TCHAR szFilePath[MAX_PATH * 2] = { 0 };
+		auto filter = LoadResourceString(idControl + 1);
+		auto title = LoadResourceString(idControl);
+		for (auto i = filter.begin(); i != filter.end(); *i = (*i == '|') ? '\0' : *i, i++);
+		OPENFILENAME ofn = {
+			sizeof(OPENFILENAME), hwndDlg, nullptr,
+			filter.c_str(), nullptr, 0, 0,
+			szFilePath, sizeof(szFilePath) / sizeof(TCHAR),
+			nullptr, 0, nullptr, title.c_str(),
+			OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | (idControl != IDC_BRLOG ? OFN_FILEMUSTEXIST : (DWORD)0)
+		};
+		if (idControl == IDC_BRLOG ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn))
+		{
+			SetDlgItemText(hwndDlg, idControl + 1, ofn.lpstrFile);
+		}
+		return TRUE;
+	}
+
+	case IDC_USEMSGFILE:
+	case IDC_USEFONTFILE:
+	case IDC_USELOGFILE:
+		EnableDlgItem(hwndDlg, idControl + 1, IsDlgButtonChecked(hwndDlg, idControl));
+		return TRUE;
+
 	default: return FALSE;
 	}
 }
@@ -283,19 +369,21 @@ INT_PTR CALLBACK LauncherDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPAR
 	}
 }
 
+typedef LANGID(__stdcall *GETLANGUAGEID)(void);
+
 extern "C" int UTIL_Platform_Init(int argc, char* argv[])
 {
+	// Try to get Vista+ API at runtime, and falls back to XP's API if not found
+	GETLANGUAGEID GetLanguage = (GETLANGUAGEID)GetProcAddress(GetModuleHandle(TEXT("Kernel32.dll")), "GetThreadUILanguage");
+	if (!GetLanguage) GetLanguage = GetUserDefaultLangID;
+
 	// Defaults log to debug output
 	UTIL_LogAddOutputCallback([](LOGLEVEL, const char* str, const char*)->void {
 		OutputDebugStringA(str);
 	}, PAL_DEFAULT_LOGLEVEL);
 
 	g_hInstance = GetModuleHandle(nullptr);
-#if !defined(__MINGW32__) || _WIN32_WINNT > _WIN32_WINNT_WS03 // compile time switch; use `make CCFLAGS=-D_WIN32_WINNT=_WIN32_WINNT_VISTA` for vista+ only automatic language detection
-	g_wLanguage = GetThreadUILanguage();
-#else // default XP compatible CodePage detection hack.
-	g_wLanguage = GetSystemDefaultLangID();
-#endif
+	g_wLanguage = GetLanguage();
 	if (PRIMARYLANGID(g_wLanguage) == LANG_CHINESE)
 	{
 		if (SUBLANGID(g_wLanguage) == SUBLANG_CHINESE_SIMPLIFIED || SUBLANGID(g_wLanguage) == SUBLANG_CHINESE_SINGAPORE)

+ 57 - 17
winrt/SDLPal.Common/MainPage.xaml

@@ -12,57 +12,97 @@
             <StackPanel VerticalAlignment="Top" Margin="10,0,20,10">
                 <TextBlock Text="SDLPAL" FontSize="48" />
                 <TextBlock x:Uid="Title" Text="设置模式" FontSize="28" VerticalAlignment="Center" />
-                <TextBox x:Name="tbGamePath" x:Uid="GamePath" TextWrapping="Wrap" VerticalAlignment="Top" Header="游戏资源文件夹" IsReadOnly="True" PlaceholderText="未选择游戏资源文件夹"/>
-                <Button x:Name="btnBrowseGame" x:Uid="ButtonBrowse" Content="浏览文件夹" HorizontalAlignment="Left" VerticalAlignment="Top" Click="btnBrowse_Click" />
-                <ToggleSwitch x:Name="tsUseEmbedFont" x:Uid="EmbeddedFont" Header="使用游戏资源内置字体" OffContent="否" OnContent="是" />
-                <ToggleSwitch x:Name="tsLanguage" x:Uid="Language" Header="游戏资源语言" OffContent="繁体中文" OnContent="简体中文" />
-                <TextBox x:Name="tbMsgFile" x:Uid="MessageFile" Header="自定义语言文件" TextWrapping="Wrap" VerticalAlignment="Top" PlaceholderText="无自定义语言文件" IsReadOnly="True"/>
-                <StackPanel Orientation="Horizontal">
-                    <Button x:Name="btnBrowseMsgFile" x:Uid="ButtonBrowseFile" Content="选择语言文件" Click="btnBrowseFile_Click" />
-                    <Button x:Name="btnClearMsgFile" x:Uid="ButtonClearFile" Content="禁用语言文件" Margin="20,0,0,0" Click="btnClearFile_Click" />
-                </StackPanel>
+                <Grid>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox x:Name="tbGamePath" x:Uid="GamePath" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" Header="游戏资源文件夹" IsReadOnly="True" PlaceholderText="未选择游戏资源文件夹"/>
+                    <Button x:Name="btnBrowseGame" x:Uid="ButtonBrowse" Grid.Column="1" Content="浏览" HorizontalAlignment="Left" VerticalAlignment="Bottom" Click="btnBrowseFolder_Click" />
+                </Grid>
+                <CheckBox x:Name="cbUseMsgFile" x:Uid="UseMessageFile" Content="自定义语言文件" Checked="cbUseFile_CheckChanged" Unchecked="cbUseFile_CheckChanged" />
+                <Grid x:Name="gridMsgFile">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox x:Name="tbMsgFile" x:Uid="MessageFile" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" PlaceholderText="无自定义语言文件" IsReadOnly="True"/>
+                    <Button x:Name="btnBrowseMsgFile" x:Uid="ButtonBrowse" Grid.Column="1" Content="浏览" HorizontalAlignment="Right" Click="btnBrowseFileOpen_Click" />
+                </Grid>
+                <CheckBox x:Name="cbUseFontFile" x:Uid="UseFontFile" Content="自定义字体文件" Checked="cbUseFile_CheckChanged" Unchecked="cbUseFile_CheckChanged" />
+                <Grid x:Name="gridFontFile">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox x:Name="tbFontFile" x:Uid="FontFile" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" PlaceholderText="无自定义字体文件" IsReadOnly="True"/>
+                    <Button x:Name="btnBrowseFontFile" x:Uid="ButtonBrowse" Grid.Column="1" Content="浏览" HorizontalAlignment="Right" Click="btnBrowseFileOpen_Click" />
+                </Grid>
+                <ComboBox x:Name="cbLogLevel" x:Uid="LogLevel" HorizontalAlignment="Stretch" Header="日志记录级别" PlaceholderText="选择日志记录级别">
+                    <ComboBoxItem x:Uid="Verbose" Content="详细"/>
+                    <ComboBoxItem x:Uid="Debug" Content="调试"/>
+                    <ComboBoxItem x:Uid="Information" Content="信息"/>
+                    <ComboBoxItem x:Uid="Warning" Content="警告"/>
+                    <ComboBoxItem x:Uid="Error" Content="错误"/>
+                    <ComboBoxItem x:Uid="Fatal" Content="致命"/>
+                </ComboBox>
+                <CheckBox x:Name="cbUseLogFile" x:Uid="UseLogFile" Content="记录日志到文件" Checked="cbUseFile_CheckChanged" Unchecked="cbUseFile_CheckChanged" />
+                <Grid x:Name="gridLogFile">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox x:Name="tbLogFile" x:Uid="LogFile" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Top" PlaceholderText="无日志文件" IsReadOnly="True"/>
+                    <Button x:Name="btnBrowseLogFile" x:Uid="ButtonBrowse" Grid.Column="1" Content="浏览" HorizontalAlignment="Right" Click="btnBrowseFileSave_Click" />
+                </Grid>
                 <ToggleSwitch x:Name="tsTouchOverlay" x:Uid="TouchOverlay" Header="启用触屏辅助" OffContent="否" OnContent="是" />
                 <ToggleSwitch x:Name="tsKeepAspect" x:Uid="AspectRatio" Header="保持纵横比" OffContent="否" OnContent="是" />
                 <ToggleSwitch x:Name="tsStereo" x:Uid="Stereo" Header="立体声" OffContent="否" OnContent="是" />
                 <Slider x:Name="slMusicVolume" x:Uid="MusicVolume" Header="音乐音量" TickPlacement="Inline" TickFrequency="10" />
                 <Slider x:Name="slSoundVolume" x:Uid="SoundVolume" Header="音效音量" TickPlacement="Inline" TickFrequency="10" />
                 <Slider x:Name="slQuality" x:Uid="Quality" Header="音频质量" Maximum="4" LargeChange="1" TickFrequency="1" />
-                <ComboBox x:Name="cbSampleRate" x:Uid="Samplerate" HorizontalAlignment="Stretch" Header="音频输出采样率" PlaceholderText="音频输出采样率">
+                <ComboBox x:Name="cbSampleRate" x:Uid="Samplerate" HorizontalAlignment="Stretch" Header="音频输出采样率" PlaceholderText="选择音频输出采样率">
                     <ComboBoxItem Content="11025"/>
                     <ComboBoxItem Content="22050"/>
                     <ComboBoxItem Content="44100"/>
                 </ComboBox>
-                <ComboBox x:Name="cbAudioBuffer" x:Uid="AudioBuffer" HorizontalAlignment="Stretch" Header="音频缓冲区大小" PlaceholderText="音频缓冲区大小">
+                <ComboBox x:Name="cbAudioBuffer" x:Uid="AudioBuffer" HorizontalAlignment="Stretch" Header="音频缓冲区大小" PlaceholderText="选择音频缓冲区大小">
                     <ComboBoxItem Content="512"/>
                     <ComboBoxItem Content="1024"/>
                     <ComboBoxItem Content="2048"/>
                     <ComboBoxItem Content="4096"/>
                     <ComboBoxItem Content="8192"/>
                 </ComboBox>
-                <ComboBox x:Name="cbCD" x:Uid="CD" HorizontalAlignment="Stretch" Header="CD 音轨格式" PlaceholderText="CD 音轨格式">
+                <ComboBox x:Name="cbCD" x:Uid="CD" HorizontalAlignment="Stretch" Header="CD 音轨格式" PlaceholderText="选择 CD 音轨格式">
                     <ComboBoxItem Content="MP3"/>
                     <ComboBoxItem Content="OGG"/>
                 </ComboBox>
-                <ComboBox x:Name="cbBGM" x:Uid="BGM" HorizontalAlignment="Stretch" Header="背景音乐格式" PlaceholderText="背景音乐格式" SelectionChanged="cbBGM_SelectionChanged">
+                <ComboBox x:Name="cbBGM" x:Uid="BGM" HorizontalAlignment="Stretch" Header="背景音乐格式" PlaceholderText="选择背景音乐格式" SelectionChanged="cbBGM_SelectionChanged">
                     <ComboBoxItem Content="MIDI"/>
                     <ComboBoxItem Content="RIX"/>
                     <ComboBoxItem Content="MP3"/>
                     <ComboBoxItem Content="OGG"/>
                 </ComboBox>
-                <ComboBox x:Name="cbOPL" x:Uid="OPL" HorizontalAlignment="Stretch" Header="OPL 模拟器" PlaceholderText="OPL 模拟器">
+                <ComboBox x:Name="cbOPL" x:Uid="OPL" HorizontalAlignment="Stretch" Header="OPL 模拟器" PlaceholderText="选择 OPL 模拟器">
                     <ComboBoxItem Content="DOSBOX"/>
                     <ComboBoxItem Content="MAME"/>
                     <ComboBoxItem Content="DOSBOXNEW"/>
                 </ComboBox>
-                <ComboBox x:Name="cbOPLSR" x:Uid="OPLSR" HorizontalAlignment="Stretch" Header="OPL 模拟器采样率" PlaceholderText="OPL 模拟器采样率">
+                <ComboBox x:Name="cbOPLSR" x:Uid="OPLSR" HorizontalAlignment="Stretch" Header="OPL 模拟器采样率" PlaceholderText="选择 OPL 模拟器采样率">
                     <ComboBoxItem Content="12429"/>
                     <ComboBoxItem Content="24858"/>
                     <ComboBoxItem Content="49716"/>
                 </ComboBox>
                 <ToggleSwitch x:Name="tsSurroundOPL" x:Uid="SurroundOPL" Header="环绕声 OPL" OffContent="否" OnContent="是" />
                 <Grid VerticalAlignment="Top">
-                    <Button x:Name="btnReset" x:Uid="ButtonDefault" Content="默认设置" HorizontalAlignment="Left" Click="btnReset_Click" />
-                    <Button x:Name="btnFinish" x:Uid="ButtonFinish" Content="完成设置" HorizontalAlignment="Right" Click="btnFinish_Click" />
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition />
+                        <ColumnDefinition />
+                        <ColumnDefinition />
+                    </Grid.ColumnDefinitions>
+                    <Button x:Name="btnDefault" x:Uid="ButtonDefault" Grid.Column="0" Content="默认设置" HorizontalAlignment="Left" Click="btnDefault_Click" />
+                    <Button x:Name="btnReset" x:Uid="ButtonReset" Grid.Column="1" Content="还原设置" HorizontalAlignment="Center" Click="btnReset_Click" />
+                    <Button x:Name="btnFinish" x:Uid="ButtonFinish" Grid.Column="2" Content="完成设置" HorizontalAlignment="Right" Click="btnFinish_Click" />
                 </Grid>
             </StackPanel>
         </ScrollViewer>

+ 123 - 33
winrt/SDLPal.Common/MainPage.xaml.cpp

@@ -23,27 +23,74 @@ using namespace Windows::UI::Xaml::Input;
 using namespace Windows::UI::Xaml::Media;
 using namespace Windows::UI::Xaml::Navigation;
 
+static Platform::String^ msg_file_exts[] = { ".msg" };
+static Platform::String^ font_file_exts[] = { ".bdf" };
+static Platform::String^ log_file_exts[] = { ".log" };
+
 MainPage::MainPage()
 {
 	InitializeComponent();
+
+	m_controls = ref new Platform::Collections::Map<Platform::String^, ButtonAttribute^>();
+	m_controls->Insert(btnBrowseMsgFile->Name, ref new ButtonAttribute(tbMsgFile, ref new Platform::Array<Platform::String^>(msg_file_exts, sizeof(msg_file_exts) / sizeof(msg_file_exts[0]))));
+	m_controls->Insert(btnBrowseFontFile->Name, ref new ButtonAttribute(tbFontFile, ref new Platform::Array<Platform::String^>(font_file_exts, sizeof(font_file_exts) / sizeof(font_file_exts[0]))));
+	m_controls->Insert(btnBrowseLogFile->Name, ref new ButtonAttribute(tbLogFile, ref new Platform::Array<Platform::String^>(log_file_exts, sizeof(log_file_exts) / sizeof(log_file_exts[0]))));
+	m_controls->Insert(cbUseMsgFile->Name, ref new ButtonAttribute(gridMsgFile, nullptr));
+	m_controls->Insert(cbUseFontFile->Name, ref new ButtonAttribute(gridFontFile, nullptr));
+	m_controls->Insert(cbUseLogFile->Name, ref new ButtonAttribute(gridLogFile, nullptr));
+
+	m_acl[PALCFG_GAMEPATH] = ref new AccessListEntry(tbGamePath, nullptr, ConvertString(PAL_ConfigName(PALCFG_GAMEPATH)));
+	m_acl[PALCFG_SAVEPATH] = ref new AccessListEntry(tbGamePath, nullptr, ConvertString(PAL_ConfigName(PALCFG_SAVEPATH)));
+	m_acl[PALCFG_MESSAGEFILE] = ref new AccessListEntry(tbMsgFile, cbUseMsgFile, ConvertString(PAL_ConfigName(PALCFG_MESSAGEFILE)));
+	m_acl[PALCFG_FONTFILE] = ref new AccessListEntry(tbFontFile, cbUseFontFile, ConvertString(PAL_ConfigName(PALCFG_FONTFILE)));
+	m_acl[PALCFG_LOGFILE] = ref new AccessListEntry(tbLogFile, cbUseLogFile, ConvertString(PAL_ConfigName(PALCFG_LOGFILE)));
+
 	LoadControlContents();
 
 	m_resLdr = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
-	auto app = static_cast<App^>(Application::Current);
-#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
-	app->Page = this;
-#endif
-	if (app->LastCrashed)
+	if (static_cast<App^>(Application::Current)->LastCrashed)
+	{
 		(ref new Windows::UI::Popups::MessageDialog(m_resLdr->GetString("MBCrashContent")))->ShowAsync();
+	}
 }
 
-void SDLPal::MainPage::LoadControlContents()
+void SDLPal::MainPage::LoadControlContents(bool loadDefault)
 {
-	if (gConfig.pszGamePath) tbGamePath->Text = ConvertString(gConfig.pszGamePath);
-	if (gConfig.pszMsgFile) tbMsgFile->Text = ConvertString(gConfig.pszMsgFile);
+	for (auto i = m_acl.begin(); i != m_acl.end(); i++)
+	{
+		auto item = i->second;
+		item->text->Text = "";
+		item->text->Tag = nullptr;
+		if (item->check)
+		{
+			item->check->IsChecked = false;
+			m_controls->Lookup(item->check->Name)->Object->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
+		}
+	}
+
+	if (!loadDefault)
+	{
+		// Always load folder/files from FutureAccessList
+		std::list<Platform::String^> invalid_tokens;
+		auto fal = Windows::Storage::AccessCache::StorageApplicationPermissions::FutureAccessList;
+		for each (auto entry in fal->Entries)
+		{
+			auto& ace = m_acl[PAL_ConfigIndex(ConvertString(entry.Token).c_str())];
+			ace->text->Tag = AWait(fal->GetItemAsync(entry.Token), g_eventHandle);
+			if (ace->text->Tag)
+				ace->text->Text = entry.Metadata;
+			else
+				invalid_tokens.push_back(entry.Token);
+			if (ace->check)
+			{
+				auto grid = m_controls->Lookup(ace->check->Name)->Object;
+				ace->check->IsChecked = (ace->text->Tag != nullptr);
+				grid->Visibility = ace->check->IsChecked->Value ? Windows::UI::Xaml::Visibility::Visible : Windows::UI::Xaml::Visibility::Collapsed;
+			}
+		}
+		for (auto i = invalid_tokens.begin(); i != invalid_tokens.end(); fal->Remove(*i++));
+	}
 
-	tsLanguage->IsOn = (gConfig.uCodePage == CP_GBK);
-	tsUseEmbedFont->IsOn = (gConfig.fUseEmbeddedFonts == TRUE);
 	tsKeepAspect->IsOn = (gConfig.fKeepAspectRatio == TRUE);
 	tsStereo->IsOn = (gConfig.iAudioChannels == 2);
 	tsSurroundOPL->IsOn = (gConfig.fUseSurroundOPL == TRUE);
@@ -52,6 +99,7 @@ void SDLPal::MainPage::LoadControlContents()
 	slMusicVolume->Value = gConfig.iMusicVolume;
 	slSoundVolume->Value = gConfig.iSoundVolume;
 	slQuality->Value = gConfig.iResampleQuality;
+	cbLogLevel->SelectedIndex = (int)gConfig.iLogLevel;
 
 	cbCD->SelectedIndex = (gConfig.eCDType == MUSIC_MP3) ? 0 : 1;
 	cbBGM->SelectedIndex = (gConfig.eMusicType <= MUSIC_OGG) ? gConfig.eMusicType : MUSIC_RIX;
@@ -82,17 +130,12 @@ void SDLPal::MainPage::LoadControlContents()
 
 void SDLPal::MainPage::SaveControlContents()
 {
-	std::wstring path;
-
-	if (gConfig.pszGamePath) free(gConfig.pszGamePath);
-	path.assign(tbGamePath->Text->Data());
-	if (path.back() != '\\') path.append(L"\\");
-	gConfig.pszGamePath = strdup(ConvertString(path).c_str());
-
-	if (gConfig.pszMsgFile) { free(gConfig.pszMsgFile); gConfig.pszMsgFile = NULL; }
-	gConfig.pszMsgFile = (tbMsgFile->Text->Length() > 0) ? strdup(ConvertString(tbMsgFile->Text).c_str()) : nullptr;
+	// All folders/files are not stored in config file, as they are store in FutureAcessList
+	if (gConfig.pszGamePath) { free(gConfig.pszGamePath); gConfig.pszGamePath = nullptr; }
+	if (gConfig.pszMsgFile) { free(gConfig.pszMsgFile); gConfig.pszMsgFile = nullptr; }
+	if (gConfig.pszFontFile) { free(gConfig.pszFontFile); gConfig.pszFontFile = nullptr; }
+	if (gConfig.pszLogFile) { free(gConfig.pszLogFile); gConfig.pszLogFile = nullptr; }
 
-	gConfig.fUseEmbeddedFonts = tsUseEmbedFont->IsOn ? TRUE : FALSE;
 	gConfig.fKeepAspectRatio = tsKeepAspect->IsOn ? TRUE : FALSE;
 	gConfig.iAudioChannels = tsStereo->IsOn ? 2 : 1;
 	gConfig.fUseSurroundOPL = tsSurroundOPL->IsOn ? TRUE : FALSE;
@@ -101,7 +144,7 @@ void SDLPal::MainPage::SaveControlContents()
 	gConfig.iMusicVolume = (int)slMusicVolume->Value;
 	gConfig.iSoundVolume = (int)slSoundVolume->Value;
 	gConfig.iResampleQuality = (int)slQuality->Value;
-	gConfig.uCodePage = tsLanguage->IsOn ? CP_GBK : CP_BIG5;
+	gConfig.iLogLevel = (LOGLEVEL)cbLogLevel->SelectedIndex;
 
 	gConfig.eCDType = (MUSICTYPE)(MUSIC_MP3 + cbCD->SelectedIndex);
 	gConfig.eMusicType = (MUSICTYPE)cbBGM->SelectedIndex;
@@ -120,10 +163,15 @@ void SDLPal::MainPage::cbBGM_SelectionChanged(Platform::Object^ sender, Windows:
 	tsSurroundOPL->Visibility = visibility;
 }
 
+void SDLPal::MainPage::btnDefault_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
+{
+	PAL_LoadConfig(FALSE);
+	LoadControlContents(true);
+}
 
 void SDLPal::MainPage::btnReset_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
 {
-	PAL_LoadConfig(FALSE);
+	PAL_LoadConfig(TRUE);
 	LoadControlContents();
 }
 
@@ -131,9 +179,16 @@ void SDLPal::MainPage::btnFinish_Click(Platform::Object^ sender, Windows::UI::Xa
 {
 	if (tbGamePath->Text->Length() > 0)
 	{
-		auto mru_list = Windows::Storage::AccessCache::StorageApplicationPermissions::MostRecentlyUsedList;
-		if (tbGamePath->Tag) mru_list->Add(safe_cast<Windows::Storage::StorageFolder^>(tbGamePath->Tag), tbGamePath->Text);
-		if (tbMsgFile->Tag) mru_list->Add(safe_cast<Windows::Storage::StorageFile^>(tbMsgFile->Tag), tbMsgFile->Text);
+		auto fal = Windows::Storage::AccessCache::StorageApplicationPermissions::FutureAccessList;
+		for (auto i = m_acl.begin(); i != m_acl.end(); i++)
+		{
+			auto item = i->second;
+			auto check = item->check ? item->check->IsChecked->Value : true;
+			if (check && item->text->Tag)
+				fal->AddOrReplace(item->token, safe_cast<Windows::Storage::IStorageItem^>(item->text->Tag), item->text->Text);
+			else if (fal->ContainsItem(item->token))
+				fal->Remove(item->token);
+		}
 
 		SaveControlContents();
 		gConfig.fLaunchSetting = FALSE;
@@ -166,37 +221,72 @@ void SDLPal::MainPage::SetPath(Windows::Storage::StorageFolder^ folder)
 	}
 }
 
-void SDLPal::MainPage::SetFile(Windows::Storage::StorageFile^ file)
+void SDLPal::MainPage::SetFile(Windows::UI::Xaml::Controls::TextBox^ target, Windows::Storage::StorageFile^ file)
 {
-	if (file)
+	if (target && file)
 	{
-		tbMsgFile->Text = file->Path;
-		tbMsgFile->Tag = file;
+		target->Text = file->Path;
+		target->Tag = file;
 	}
 }
 
-void SDLPal::MainPage::btnBrowse_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
+void SDLPal::MainPage::btnBrowseFolder_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
 {
 	auto picker = ref new Windows::Storage::Pickers::FolderPicker();
 	picker->FileTypeFilter->Append("*");
 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
+	picker->ContinuationData->Insert("Page", this);
 	picker->PickFolderAndContinue();
 #else
 	concurrency::create_task(picker->PickSingleFolderAsync()).then([this](Windows::Storage::StorageFolder^ folder) { SetPath(folder); });
 #endif
 }
 
-void SDLPal::MainPage::btnBrowseFile_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
+void SDLPal::MainPage::btnBrowseFileOpen_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
 {
+	auto button = static_cast<Windows::UI::Xaml::Controls::Button^>(sender);
+	auto target = m_controls->Lookup(button->Name);
 	auto picker = ref new Windows::Storage::Pickers::FileOpenPicker();
-	picker->FileTypeFilter->Append("*");
+	picker->FileTypeFilter->ReplaceAll(target->Filter);
 #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
+	picker->ContinuationData->Insert("Page", this);
+	picker->ContinuationData->Insert("Target", target->Object);
 	picker->PickSingleFileAndContinue();
 #else
-	concurrency::create_task(picker->PickSingleFileAsync()).then([this](Windows::Storage::StorageFile^ file) { SetFile(file); });
+	concurrency::create_task(picker->PickSingleFileAsync()).then(
+		[this, target](Windows::Storage::StorageFile^ file) {
+			SetFile(static_cast<Windows::UI::Xaml::Controls::TextBox^>(target->Object), file);
+		}
+	);
 #endif
 }
 
+void SDLPal::MainPage::btnBrowseFileSave_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
+{
+	auto button = static_cast<Windows::UI::Xaml::Controls::Button^>(sender);
+	auto target = m_controls->Lookup(button->Name);
+	auto picker = ref new Windows::Storage::Pickers::FileSavePicker();
+	picker->FileTypeChoices->Insert(m_resLdr->GetString("LogFileType"), ref new Platform::Collections::Vector<Platform::String^>(target->Filter));
+#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
+	picker->ContinuationData->Insert("Page", this);
+	picker->ContinuationData->Insert("Target", target->Object);
+	picker->PickSaveFileAndContinue();
+#else
+	concurrency::create_task(picker->PickSaveFileAsync()).then(
+		[this, target](Windows::Storage::StorageFile^ file) {
+		SetFile(static_cast<Windows::UI::Xaml::Controls::TextBox^>(target->Object), file);
+	}
+	);
+#endif
+}
+
+void SDLPal::MainPage::cbUseFile_CheckChanged(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
+{
+	auto checker = static_cast<Windows::UI::Xaml::Controls::CheckBox^>(sender);
+	auto attr = m_controls->Lookup(checker->Name);
+	attr->Object->Visibility = checker->IsChecked->Value ? Windows::UI::Xaml::Visibility::Visible : Windows::UI::Xaml::Visibility::Collapsed;
+}
+
 void SDLPal::MainPage::Page_Loaded(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
 {
 #if NTDDI_VERSION >= NTDDI_WIN10

+ 42 - 4
winrt/SDLPal.Common/MainPage.xaml.h

@@ -6,9 +6,42 @@
 #pragma once
 
 #include "MainPage.g.h"
+#include <map>
+#include "../../palcfg.h"
+
+#ifdef main
+# undef main
+#endif
+
 
 namespace SDLPal
 {
+	ref struct ButtonAttribute sealed
+	{
+		property Windows::UI::Xaml::FrameworkElement^ Object;
+		property Platform::Array<Platform::String^>^  Filter;
+
+		ButtonAttribute(Windows::UI::Xaml::FrameworkElement^ o, const Platform::Array<Platform::String^>^ f)
+		{
+			Object = o;
+			Filter = f;
+		}
+	};
+
+	ref struct AccessListEntry sealed
+	{
+		property Windows::UI::Xaml::Controls::TextBox^  text;
+		property Windows::UI::Xaml::Controls::CheckBox^ check;
+		property Platform::String^                      token;
+
+		AccessListEntry(Windows::UI::Xaml::Controls::TextBox^ t, Windows::UI::Xaml::Controls::CheckBox^ c, Platform::String^ s)
+		{
+			text = t;
+			check = c;
+			token = s;
+		}
+	};
+
 	/// <summary>
 	/// 可用于自身或导航至 Frame 内部的空白页。
 	/// </summary>
@@ -18,21 +51,26 @@ namespace SDLPal
 		MainPage();
 
 		void SetPath(Windows::Storage::StorageFolder^ folder);
-		void SetFile(Windows::Storage::StorageFile^ file);
+		void SetFile(Windows::UI::Xaml::Controls::TextBox^ target, Windows::Storage::StorageFile^ file);
 
 	protected:
-		void LoadControlContents();
+		void LoadControlContents(bool loadDefault = false);
 		void SaveControlContents();
 
 	private:
+		Platform::Collections::Map<Platform::String^, ButtonAttribute^>^ m_controls;
 		Windows::ApplicationModel::Resources::ResourceLoader^ m_resLdr;
+		std::map<PALCFG_ITEM, AccessListEntry^> m_acl;
 
-		void btnBrowse_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
+		void btnBrowseFolder_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
 		void cbBGM_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e);
+		void btnDefault_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
 		void btnReset_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
 		void btnFinish_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
-		void btnBrowseFile_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
+		void btnBrowseFileOpen_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
+		void btnBrowseFileSave_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
 		void btnClearFile_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
 		void Page_Loaded(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
+		void cbUseFile_CheckChanged(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
 	};
 }

+ 3 - 0
winrt/SDLPal.Common/SDLPal.Common.def

@@ -14,3 +14,6 @@ EXPORTS
 	fputs=WRT_fputs
 	fgetc=WRT_fgetc
 	fgets=WRT_fgets
+
+	get_special_files_map
+	get_special_folders_map

+ 11 - 6
winrt/SDLPal.Common/StringHelper.h

@@ -65,12 +65,17 @@ static void ConvertString(const std::string& src, std::wstring& dst)
 
 static Platform::String^ ConvertString(const char* src)
 {
-	int len = MultiByteToWideChar(CP_ACP, 0, src, -1, nullptr, 0);
-	auto wc = new wchar_t[len];
-	MultiByteToWideChar(CP_ACP, 0, src, -1, wc, len);
-	auto dst = ref new Platform::String(wc);
-	delete[] wc;
-	return dst;
+	if (src)
+	{
+		int len = MultiByteToWideChar(CP_ACP, 0, src, -1, nullptr, 0);
+		auto wc = new wchar_t[len];
+		MultiByteToWideChar(CP_ACP, 0, src, -1, wc, len);
+		auto dst = ref new Platform::String(wc);
+		delete[] wc;
+		return dst;
+	}
+	else
+		return "";
 }
 
 static Platform::String^ ConvertString(const std::string& src)

+ 47 - 38
winrt/SDLPal.Common/Strings/en/Resources.resw

@@ -133,22 +133,16 @@
     <value>Audio buffer size</value>
   </data>
   <data name="AudioBuffer.PlaceholderText" xml:space="preserve">
-    <value>Audio buffer size</value>
+    <value>Choose audio buffer size</value>
   </data>
   <data name="BGM.Header" xml:space="preserve">
     <value>Format of BGM</value>
   </data>
   <data name="BGM.PlaceholderText" xml:space="preserve">
-    <value>Format of BGM</value>
+    <value>Choose format of BGM</value>
   </data>
   <data name="ButtonBrowse.Content" xml:space="preserve">
-    <value>Browse folder</value>
-  </data>
-  <data name="ButtonBrowseFile.Content" xml:space="preserve">
-    <value>Select</value>
-  </data>
-  <data name="ButtonClearFile.Content" xml:space="preserve">
-    <value>Disable</value>
+    <value>Browse</value>
   </data>
   <data name="ButtonDefault.Content" xml:space="preserve">
     <value>Default setting</value>
@@ -156,29 +150,26 @@
   <data name="ButtonFinish.Content" xml:space="preserve">
     <value>Finish setting</value>
   </data>
+  <data name="ButtonReset.Content" xml:space="preserve">
+    <value>Reset setting</value>
+  </data>
   <data name="CD.Header" xml:space="preserve">
     <value>Format of CD track</value>
   </data>
   <data name="CD.PlaceholderText" xml:space="preserve">
-    <value>Format of CD track</value>
+    <value>Choose format of CD track</value>
   </data>
-  <data name="DOS.Header" xml:space="preserve">
-    <value>Game resource version</value>
+  <data name="Debug.Content" xml:space="preserve">
+    <value>Debug</value>
   </data>
-  <data name="DOS.OffContent" xml:space="preserve">
-    <value>WIN95</value>
+  <data name="Error.Content" xml:space="preserve">
+    <value>Error</value>
   </data>
-  <data name="DOS.OnContent" xml:space="preserve">
-    <value>DOS</value>
+  <data name="Fatal.Content" xml:space="preserve">
+    <value>Fatal</value>
   </data>
-  <data name="EmbeddedFont.Header" xml:space="preserve">
-    <value>Use the font embedded in game resource</value>
-  </data>
-  <data name="EmbeddedFont.OffContent" xml:space="preserve">
-    <value>No</value>
-  </data>
-  <data name="EmbeddedFont.OnContent" xml:space="preserve">
-    <value>Yes</value>
+  <data name="FontFile.PlaceholderText" xml:space="preserve">
+    <value>No customized font file</value>
   </data>
   <data name="GamePath.Header" xml:space="preserve">
     <value>Folder of game resource</value>
@@ -186,14 +177,20 @@
   <data name="GamePath.PlaceholderText" xml:space="preserve">
     <value>No folder selected</value>
   </data>
-  <data name="Language.Header" xml:space="preserve">
-    <value>Game resource language</value>
+  <data name="Information.Content" xml:space="preserve">
+    <value>Informational</value>
+  </data>
+  <data name="LogFile.PlaceholderText" xml:space="preserve">
+    <value>No logging file</value>
   </data>
-  <data name="Language.OffContent" xml:space="preserve">
-    <value>Traditional Chinese</value>
+  <data name="LogFileType" xml:space="preserve">
+    <value>Log files</value>
   </data>
-  <data name="Language.OnContent" xml:space="preserve">
-    <value>Simplified Chinese</value>
+  <data name="LogLevel.Header" xml:space="preserve">
+    <value>Logging level</value>
+  </data>
+  <data name="LogLevel.PlaceholderText" xml:space="preserve">
+    <value>Choose logging level</value>
   </data>
   <data name="MBCrashContent" xml:space="preserve">
     <value>The program is abnormally terminated last time. The setting page has been launched for you. Please check if there are any incorrect settings.</value>
@@ -208,9 +205,6 @@ You can use the main menu option inside the game to return to this page.</value>
   <data name="MBExitTitle" xml:space="preserve">
     <value>Setting finished</value>
   </data>
-  <data name="MessageFile.Header" xml:space="preserve">
-    <value>Customized message file</value>
-  </data>
   <data name="MessageFile.PlaceholderText" xml:space="preserve">
     <value>No customized message file</value>
   </data>
@@ -218,16 +212,16 @@ You can use the main menu option inside the game to return to this page.</value>
     <value>Music volume</value>
   </data>
   <data name="OPL.Header" xml:space="preserve">
-    <value>Type of OPL simulator</value>
+    <value>Type of OPL emulator</value>
   </data>
   <data name="OPL.PlaceholderText" xml:space="preserve">
-    <value>Type of OPL simulator</value>
+    <value>Choose type of OPL emulator</value>
   </data>
   <data name="OPLSR.Header" xml:space="preserve">
-    <value>Sample rate of OPL simulator</value>
+    <value>Sample rate of OPL emulator</value>
   </data>
   <data name="OPLSR.PlaceholderText" xml:space="preserve">
-    <value>Sample rate of OPL simulator</value>
+    <value>Choose sample rate of OPL emulator</value>
   </data>
   <data name="Publisher" xml:space="preserve">
     <value>SDLPAL development team</value>
@@ -239,7 +233,7 @@ You can use the main menu option inside the game to return to this page.</value>
     <value>Sample rate of audio output</value>
   </data>
   <data name="Samplerate.PlaceholderText" xml:space="preserve">
-    <value>Sample rate of audio output</value>
+    <value>Choose sample rate of audio output</value>
   </data>
   <data name="SoundVolume.Header" xml:space="preserve">
     <value>Sound volume</value>
@@ -274,4 +268,19 @@ You can use the main menu option inside the game to return to this page.</value>
   <data name="TouchOverlay.OnContent" xml:space="preserve">
     <value>Yes</value>
   </data>
+  <data name="UseFontFile.Content" xml:space="preserve">
+    <value>Use customized font file</value>
+  </data>
+  <data name="UseLogFile.Content" xml:space="preserve">
+    <value>Log to file</value>
+  </data>
+  <data name="UseMessageFile.Content" xml:space="preserve">
+    <value>Customized message file</value>
+  </data>
+  <data name="Verbose.Content" xml:space="preserve">
+    <value>Verbose</value>
+  </data>
+  <data name="Warning.Content" xml:space="preserve">
+    <value>Warning</value>
+  </data>
 </root>

+ 45 - 36
winrt/SDLPal.Common/Strings/zh-hans/Resources.resw

@@ -133,22 +133,16 @@
     <value>音频缓冲区大小</value>
   </data>
   <data name="AudioBuffer.PlaceholderText" xml:space="preserve">
-    <value>音频缓冲区大小</value>
+    <value>选择音频缓冲区大小</value>
   </data>
   <data name="BGM.Header" xml:space="preserve">
     <value>背景音乐格式</value>
   </data>
   <data name="BGM.PlaceholderText" xml:space="preserve">
-    <value>背景音乐格式</value>
+    <value>选择背景音乐格式</value>
   </data>
   <data name="ButtonBrowse.Content" xml:space="preserve">
-    <value>浏览文件夹</value>
-  </data>
-  <data name="ButtonBrowseFile.Content" xml:space="preserve">
-    <value>选择语言文件</value>
-  </data>
-  <data name="ButtonClearFile.Content" xml:space="preserve">
-    <value>禁用语言文件</value>
+    <value>浏览</value>
   </data>
   <data name="ButtonDefault.Content" xml:space="preserve">
     <value>默认设置</value>
@@ -156,29 +150,26 @@
   <data name="ButtonFinish.Content" xml:space="preserve">
     <value>完成设置</value>
   </data>
+  <data name="ButtonReset.Content" xml:space="preserve">
+    <value>还原设置</value>
+  </data>
   <data name="CD.Header" xml:space="preserve">
     <value>CD 音轨格式</value>
   </data>
   <data name="CD.PlaceholderText" xml:space="preserve">
-    <value>CD 音轨格式</value>
+    <value>选择 CD 音轨格式</value>
   </data>
-  <data name="DOS.Header" xml:space="preserve">
-    <value>游戏资源版本</value>
+  <data name="Debug.Content" xml:space="preserve">
+    <value>调试</value>
   </data>
-  <data name="DOS.OffContent" xml:space="preserve">
-    <value>WIN95</value>
+  <data name="Error.Content" xml:space="preserve">
+    <value>错误</value>
   </data>
-  <data name="DOS.OnContent" xml:space="preserve">
-    <value>DOS</value>
+  <data name="Fatal.Content" xml:space="preserve">
+    <value>致命</value>
   </data>
-  <data name="EmbeddedFont.Header" xml:space="preserve">
-    <value>使用游戏资源内置字体</value>
-  </data>
-  <data name="EmbeddedFont.OffContent" xml:space="preserve">
-    <value>否</value>
-  </data>
-  <data name="EmbeddedFont.OnContent" xml:space="preserve">
-    <value>是</value>
+  <data name="FontFile.PlaceholderText" xml:space="preserve">
+    <value>无自定义字体文件</value>
   </data>
   <data name="GamePath.Header" xml:space="preserve">
     <value>游戏资源文件夹</value>
@@ -186,14 +177,20 @@
   <data name="GamePath.PlaceholderText" xml:space="preserve">
     <value>未选择文件夹</value>
   </data>
-  <data name="Language.Header" xml:space="preserve">
-    <value>游戏资源语言</value>
+  <data name="Information.Content" xml:space="preserve">
+    <value>信息</value>
+  </data>
+  <data name="LogFile.PlaceholderText" xml:space="preserve">
+    <value>无日志文件</value>
+  </data>
+  <data name="LogFileType" xml:space="preserve">
+    <value>日志文件</value>
   </data>
-  <data name="Language.OffContent" xml:space="preserve">
-    <value>繁体中文</value>
+  <data name="LogLevel.Header" xml:space="preserve">
+    <value>日志记录级别</value>
   </data>
-  <data name="Language.OnContent" xml:space="preserve">
-    <value>简体中文</value>
+  <data name="LogLevel.PlaceholderText" xml:space="preserve">
+    <value>选择日志记录级别</value>
   </data>
   <data name="MBCrashContent" xml:space="preserve">
     <value>上次程序异常退出,已显示设置页供您检查设置是否正确。</value>
@@ -208,9 +205,6 @@
   <data name="MBExitTitle" xml:space="preserve">
     <value>设置已完成</value>
   </data>
-  <data name="MessageFile.Header" xml:space="preserve">
-    <value>自定义语言文件</value>
-  </data>
   <data name="MessageFile.PlaceholderText" xml:space="preserve">
     <value>无自定义语言文件</value>
   </data>
@@ -221,13 +215,13 @@
     <value>OPL 模拟器</value>
   </data>
   <data name="OPL.PlaceholderText" xml:space="preserve">
-    <value>OPL 模拟器</value>
+    <value>选择 OPL 模拟器</value>
   </data>
   <data name="OPLSR.Header" xml:space="preserve">
     <value>OPL 模拟器采样率</value>
   </data>
   <data name="OPLSR.PlaceholderText" xml:space="preserve">
-    <value>OPL 模拟器采样率</value>
+    <value>选择 OPL 模拟器采样率</value>
   </data>
   <data name="Publisher" xml:space="preserve">
     <value>SDLPAL 开发组</value>
@@ -239,7 +233,7 @@
     <value>音频输出采样率</value>
   </data>
   <data name="Samplerate.PlaceholderText" xml:space="preserve">
-    <value>音频输出采样率</value>
+    <value>选择音频输出采样率</value>
   </data>
   <data name="SoundVolume.Header" xml:space="preserve">
     <value>音效音量</value>
@@ -274,4 +268,19 @@
   <data name="TouchOverlay.OnContent" xml:space="preserve">
     <value>是</value>
   </data>
+  <data name="UseFontFile.Content" xml:space="preserve">
+    <value>自定义字体文件</value>
+  </data>
+  <data name="UseLogFile.Content" xml:space="preserve">
+    <value>记录日志到文件</value>
+  </data>
+  <data name="UseMessageFile.Content" xml:space="preserve">
+    <value>自定义语言文件</value>
+  </data>
+  <data name="Verbose.Content" xml:space="preserve">
+    <value>详细</value>
+  </data>
+  <data name="Warning.Content" xml:space="preserve">
+    <value>警告</value>
+  </data>
 </root>

+ 45 - 36
winrt/SDLPal.Common/Strings/zh-hant/Resources.resw

@@ -133,22 +133,16 @@
     <value>音訊緩衝區大小</value>
   </data>
   <data name="AudioBuffer.PlaceholderText" xml:space="preserve">
-    <value>音訊緩衝區大小</value>
+    <value>選擇音訊緩衝區大小</value>
   </data>
   <data name="BGM.Header" xml:space="preserve">
     <value>背景音樂格式</value>
   </data>
   <data name="BGM.PlaceholderText" xml:space="preserve">
-    <value>背景音樂格式</value>
+    <value>選擇背景音樂格式</value>
   </data>
   <data name="ButtonBrowse.Content" xml:space="preserve">
-    <value>瀏覽檔案夾</value>
-  </data>
-  <data name="ButtonBrowseFile.Content" xml:space="preserve">
-    <value>選擇語言檔案</value>
-  </data>
-  <data name="ButtonClearFile.Content" xml:space="preserve">
-    <value>禁用語言檔案</value>
+    <value>瀏覽</value>
   </data>
   <data name="ButtonDefault.Content" xml:space="preserve">
     <value>默認設定</value>
@@ -156,29 +150,26 @@
   <data name="ButtonFinish.Content" xml:space="preserve">
     <value>完成設定</value>
   </data>
+  <data name="ButtonReset.Content" xml:space="preserve">
+    <value>還原設定</value>
+  </data>
   <data name="CD.Header" xml:space="preserve">
     <value>CD 音軌格式</value>
   </data>
   <data name="CD.PlaceholderText" xml:space="preserve">
-    <value>CD 音軌格式</value>
+    <value>選擇 CD 音軌格式</value>
   </data>
-  <data name="DOS.Header" xml:space="preserve">
-    <value>遊戲資源版本</value>
+  <data name="Debug.Content" xml:space="preserve">
+    <value>調試</value>
   </data>
-  <data name="DOS.OffContent" xml:space="preserve">
-    <value>WIN95</value>
+  <data name="Error.Content" xml:space="preserve">
+    <value>錯誤</value>
   </data>
-  <data name="DOS.OnContent" xml:space="preserve">
-    <value>DOS</value>
+  <data name="Fatal.Content" xml:space="preserve">
+    <value>致命</value>
   </data>
-  <data name="EmbeddedFont.Header" xml:space="preserve">
-    <value>使用遊戲資源內置字體</value>
-  </data>
-  <data name="EmbeddedFont.OffContent" xml:space="preserve">
-    <value>否</value>
-  </data>
-  <data name="EmbeddedFont.OnContent" xml:space="preserve">
-    <value>是</value>
+  <data name="FontFile.PlaceholderText" xml:space="preserve">
+    <value>無自訂字體檔案</value>
   </data>
   <data name="GamePath.Header" xml:space="preserve">
     <value>遊戲資源檔案夾</value>
@@ -186,14 +177,20 @@
   <data name="GamePath.PlaceholderText" xml:space="preserve">
     <value>未選擇檔案夾</value>
   </data>
-  <data name="Language.Header" xml:space="preserve">
-    <value>遊戲資源語言</value>
+  <data name="Information.Content" xml:space="preserve">
+    <value>信息</value>
+  </data>
+  <data name="LogFile.PlaceholderText" xml:space="preserve">
+    <value>無日誌檔案</value>
+  </data>
+  <data name="LogFileType" xml:space="preserve">
+    <value>日誌檔案</value>
   </data>
-  <data name="Language.OffContent" xml:space="preserve">
-    <value>繁體中文</value>
+  <data name="LogLevel.Header" xml:space="preserve">
+    <value>日誌記錄級別</value>
   </data>
-  <data name="Language.OnContent" xml:space="preserve">
-    <value>簡體中文</value>
+  <data name="LogLevel.PlaceholderText" xml:space="preserve">
+    <value>選擇日誌記錄級別</value>
   </data>
   <data name="MBCrashContent" xml:space="preserve">
     <value>上次程式異常退出,已顯示設定頁供您檢查設定是否正確。</value>
@@ -208,9 +205,6 @@
   <data name="MBExitTitle" xml:space="preserve">
     <value>設定已完成</value>
   </data>
-  <data name="MessageFile.Header" xml:space="preserve">
-    <value>自訂語言檔案</value>
-  </data>
   <data name="MessageFile.PlaceholderText" xml:space="preserve">
     <value>無自訂語言檔案</value>
   </data>
@@ -221,13 +215,13 @@
     <value>OPL 模擬器</value>
   </data>
   <data name="OPL.PlaceholderText" xml:space="preserve">
-    <value>OPL 模擬器</value>
+    <value>選擇 OPL 模擬器</value>
   </data>
   <data name="OPLSR.Header" xml:space="preserve">
     <value>OPL 模擬器取樣速率</value>
   </data>
   <data name="OPLSR.PlaceholderText" xml:space="preserve">
-    <value>OPL 模擬器取樣速率</value>
+    <value>選擇 OPL 模擬器取樣速率</value>
   </data>
   <data name="Publisher" xml:space="preserve">
     <value>SDLPAL 開發組</value>
@@ -239,7 +233,7 @@
     <value>音訊輸出取樣速率</value>
   </data>
   <data name="Samplerate.PlaceholderText" xml:space="preserve">
-    <value>音訊輸出取樣速率</value>
+    <value>選擇音訊輸出取樣速率</value>
   </data>
   <data name="SoundVolume.Header" xml:space="preserve">
     <value>音效音量</value>
@@ -274,4 +268,19 @@
   <data name="TouchOverlay.OnContent" xml:space="preserve">
     <value>是</value>
   </data>
+  <data name="UseFontFile.Content" xml:space="preserve">
+    <value>自訂字體檔案</value>
+  </data>
+  <data name="UseLogFile.Content" xml:space="preserve">
+    <value>記錄日誌到檔案</value>
+  </data>
+  <data name="UseMessageFile.Content" xml:space="preserve">
+    <value>自訂語言檔案</value>
+  </data>
+  <data name="Verbose.Content" xml:space="preserve">
+    <value>詳細</value>
+  </data>
+  <data name="Warning.Content" xml:space="preserve">
+    <value>警告</value>
+  </data>
 </root>

+ 93 - 26
winrt/SDLPal.Common/WinRTIO.cpp

@@ -25,6 +25,7 @@
 
 #include <wrl.h>
 #include <string>
+#include <map>
 #include <DXGI.h>
 #include <shcore.h>
 #include <unordered_set>
@@ -63,6 +64,9 @@ struct WRT_FILE
 	~WRT_FILE() { if (stream) stream->Release(); DeleteCriticalSection(&cs); }
 };
 
+static std::map<std::string, Windows::Storage::StorageFile^> g_specialFiles;
+static std::map<std::string, Windows::Storage::StorageFolder^> g_specialFolders;
+
 class CriticalSection
 {
 public:
@@ -85,39 +89,46 @@ private:
 	HANDLE _eventHandle;
 };
 
-extern "C"
-errno_t WRT_fopen_s(WRT_FILE ** pFile, const char * _Filename, const char * _Mode)
+static const std::string get_directory(const char* _Filename)
 {
-	if (nullptr == _Filename || nullptr == _Mode || nullptr == pFile) return EINVAL;
-
 	auto ptr = _Filename;
 	while (*ptr == '/' || *ptr == '\\') ptr++;
 
-	Platform::String^ directory = ref new Platform::String((ptr != _Filename) ? L"\\" : L"");
-	Platform::String^ filename;
+	std::string directory((ptr != _Filename) ? "\\" : "");
 	while (*ptr)
 	{
-		std::wstring temp;
+		std::string temp;
 		auto pos = ptr;
 		while (*pos && *pos != '/' && *pos != '\\') pos++;
 		if (*pos)
 		{
-			ConvertString(std::string(ptr, pos - ptr + 1), temp); temp[pos - ptr] = L'\\';
-			directory = Platform::String::Concat(directory, ref new Platform::String(temp.c_str()));
-		}
-		else
-		{
-			ConvertString(std::string(ptr, pos - ptr), temp);
-			filename = ref new Platform::String(temp.c_str());
+			directory.append(ptr, pos - ptr);
+			directory.append("\\");
 		}
 		while (*pos == '/' || *pos == '\\') pos++;
 		ptr = pos;
 	}
 
-	if (directory->Length() == 0)
-	{
-		directory = Windows::Storage::ApplicationData::Current->LocalFolder->Path;
-	}
+	return directory;
+}
+
+static const std::string get_filename(const char* _Filename)
+{
+	auto ptr = _Filename + strlen(_Filename);
+	while (ptr > _Filename && *ptr != '/' && *ptr != '\\') ptr--;
+	if (*ptr == '/' || *ptr == '\\') ptr++;
+	return ptr;
+}
+
+extern "C" std::map<std::string, Windows::Storage::StorageFile^>* get_special_files_map() { return &g_specialFiles; }
+extern "C" std::map<std::string, Windows::Storage::StorageFolder^>* get_special_folders_map() { return &g_specialFolders; }
+
+extern "C"
+errno_t WRT_fopen_s(WRT_FILE ** pFile, const char * _Filename, const char * _Mode)
+{
+	if (nullptr == _Filename || nullptr == _Mode || nullptr == pFile) return EINVAL;
+
+	*pFile = nullptr;
 
 	bool r, w, b = false;
 	switch (*_Mode)
@@ -125,7 +136,7 @@ errno_t WRT_fopen_s(WRT_FILE ** pFile, const char * _Filename, const char * _Mod
 	case 'a': w = true; r = false; break;
 	case 'w': w = true; r = false; break;
 	case 'r': w = false; r = true; break;
-	default: delete filename; return EINVAL;
+	default: return EINVAL;
 	}
 	for (size_t i = 1; i < strlen(_Mode); i++)
 	{
@@ -134,26 +145,82 @@ errno_t WRT_fopen_s(WRT_FILE ** pFile, const char * _Filename, const char * _Mod
 		case '+': r = w = true; break;
 		case 'b': b = true; break;
 		case 't': b = false; break;
-		default: delete filename; return EINVAL;
+		default: return EINVAL;
 		}
 	}
-	*pFile = nullptr;
+
+	Event eventHandle;
+
+	// If the file belongs to so-called 'special files' (i.e., specified in configuration file), then return its object directly
+	if (g_specialFiles.find(_Filename) != g_specialFiles.end())
+	{
+		auto file = g_specialFiles[_Filename];
+		try
+		{
+			*pFile = new WRT_FILE(AWait(file->OpenAsync(w ? Windows::Storage::FileAccessMode::ReadWrite : Windows::Storage::FileAccessMode::Read), eventHandle), r, w, b);
+		}
+		catch (Platform::AccessDeniedException^)
+		{
+			return EACCES;
+		}
+		catch (Platform::Exception^)
+		{
+			return EIO;
+		}
+		return 0;
+	}
+
+	auto directory = get_directory(_Filename);
+	auto filename = get_filename(_Filename);
+	Windows::Storage::StorageFolder^ folder = nullptr;
+
+	// If the file's folder belongs to so-called 'special folders' (i.e., specified in configuration file), then use the cache folder object
+	for (auto i = g_specialFolders.begin(); directory.length() > 0 && i != g_specialFolders.end(); i++)
+	{
+		if (_strnicmp(i->first.c_str(), directory.c_str(), i->first.size()) == 0)
+		{
+			folder = i->second;
+			if (directory.length() > i->first.length())
+			{
+				size_t pos = i->first.length(), next = directory.find('\\', pos);
+				while (next != std::string::npos)
+				{
+					folder = AWait(folder->GetFolderAsync(ConvertString(std::string(&directory[pos], next - pos))), eventHandle);
+					next = directory.find('\\', pos = next + 1);
+				}
+				if (pos < directory.length())
+				{
+					folder = AWait(folder->GetFolderAsync(ConvertString(std::string(&directory[pos], directory.length() - pos))), eventHandle);
+				}
+			}
+		}
+	}
+
+	// The try get folder directly by its full path
+	if (!folder && directory.length())
+	{
+		folder = AWait(Windows::Storage::StorageFolder::GetFolderFromPathAsync(ConvertString(directory)), eventHandle);
+	}
+
+	// As a last sort, use app's local folder
+	if (!folder)
+	{
+		folder = Windows::Storage::ApplicationData::Current->LocalFolder;
+	}
 
 	try
 	{
-		Event eventHandle;
-		Windows::Storage::StorageFolder^ folder = AWait(Windows::Storage::StorageFolder::GetFolderFromPathAsync(directory), eventHandle);
 		Windows::Storage::StorageFile^ file = nullptr;
 		switch (*_Mode)
 		{
 		case 'a':
-			file = AWait(folder->CreateFileAsync(filename, Windows::Storage::CreationCollisionOption::OpenIfExists), eventHandle);
+			file = AWait(folder->CreateFileAsync(ConvertString(filename), Windows::Storage::CreationCollisionOption::OpenIfExists), eventHandle);
 			break;
 		case 'w':
-			file = AWait(folder->CreateFileAsync(filename, Windows::Storage::CreationCollisionOption::ReplaceExisting), eventHandle);
+			file = AWait(folder->CreateFileAsync(ConvertString(filename), Windows::Storage::CreationCollisionOption::ReplaceExisting), eventHandle);
 			break;
 		case 'r':
-			file = AWait(folder->GetFileAsync(filename), eventHandle);
+			file = AWait(folder->GetFileAsync(ConvertString(filename)), eventHandle);
 			break;
 		}
 		if (file)

+ 38 - 27
winrt/SDLPal.Common/WinRTUtil.cpp

@@ -2,6 +2,8 @@
 
 #include <wrl.h>
 #include <string>
+#include <map>
+#include <list>
 #include <DXGI.h>
 #include <ppltasks.h>
 #include "../SDLPal.Common/AsyncHelper.h"
@@ -11,35 +13,11 @@
 #include "SDL.h"
 #include "SDL_endian.h"
 
-static std::string g_basepath, g_configpath;
+static std::string g_configpath;
 
 extern HANDLE g_eventHandle;
-
-extern "C"
-LPCSTR UTIL_BasePath(VOID)
-{
-	if (g_basepath.empty())
-	{
-		auto mru_list = Windows::Storage::AccessCache::StorageApplicationPermissions::MostRecentlyUsedList;
-		for each (auto entry in mru_list->Entries)
-		{
-			if (dynamic_cast<Windows::Storage::StorageFolder^>(AWait(mru_list->GetItemAsync(entry.Token), g_eventHandle)) != nullptr)
-			{
-				auto localfolder = entry.Metadata;
-				if (localfolder->End()[-1] != L'\\') localfolder += "\\";
-				ConvertString(localfolder, g_basepath);
-				break;
-			}
-		}
-	}
-	return g_basepath.c_str();
-}
-
-extern "C"
-LPCSTR UTIL_SavePath(VOID)
-{
-	return UTIL_BasePath();
-}
+extern "C" std::map<std::string, Windows::Storage::StorageFile^>* get_special_files_map();
+extern "C" std::map<std::string, Windows::Storage::StorageFolder^>* get_special_folders_map();
 
 extern "C"
 LPCSTR UTIL_ConfigPath(VOID)
@@ -174,6 +152,39 @@ INT UTIL_Platform_Init(int argc, char* argv[])
 
 	SDL_AddEventWatch(WinRT_EventFilter, nullptr);
 
+	std::list<Platform::String^> invalid_tokens;
+	auto& files = *get_special_files_map();
+	auto& folders = *get_special_folders_map();
+	auto fal = Windows::Storage::AccessCache::StorageApplicationPermissions::FutureAccessList;
+	for each (auto entry in fal->Entries)
+	{
+		auto item = AWait(fal->GetItemAsync(entry.Token), g_eventHandle);
+		if (!item)
+		{
+			invalid_tokens.push_back(entry.Token);
+			continue;
+		}
+
+		if (dynamic_cast<Windows::Storage::StorageFolder^>(item) != nullptr)
+		{
+			auto localfolder = entry.Metadata;
+			if (localfolder->End()[-1] != L'\\') localfolder += "\\";
+			auto folder = ConvertString(localfolder);
+			folders[folder] = dynamic_cast<Windows::Storage::StorageFolder^>(item);
+			PAL_SetConfigItem(PAL_ConfigIndex(ConvertString(entry.Token).c_str()), ConfigValue{ folder.c_str() });
+			continue;
+		}
+
+		if (dynamic_cast<Windows::Storage::StorageFile^>(item) != nullptr)
+		{
+			auto file = ConvertString(entry.Metadata);
+			files[file] = dynamic_cast<Windows::Storage::StorageFile^>(item);
+			PAL_SetConfigItem(PAL_ConfigIndex(ConvertString(entry.Token).c_str()), ConfigValue{ file.c_str() });
+			continue;
+		}
+	}
+	for (auto i = invalid_tokens.begin(); i != invalid_tokens.end(); fal->Remove(*i++));
+
 	return 0;
 }
 

+ 22 - 12
winrt/SDLPal.WindowsPhone/App.xaml.cpp

@@ -128,19 +128,29 @@ void App::RootFrame_FirstNavigated(Object^ sender, NavigationEventArgs^ e)
 
 void SDLPal::App::OnActivated(Windows::ApplicationModel::Activation::IActivatedEventArgs ^ args)
 {
-	switch (args->Kind)
+	if (dynamic_cast<IContinuationActivatedEventArgs^>(args) != nullptr)
 	{
-	case ActivationKind::PickFolderContinuation:
-	{
-		static_cast<SDLPal::MainPage^>(Page)->SetPath(safe_cast<IFolderPickerContinuationEventArgs^>(args)->Folder);
-		break;
-	}
-	case ActivationKind::PickFileContinuation:
-	{
-		auto files = safe_cast<IFileOpenPickerContinuationEventArgs^>(args)->Files;
-		if (files->Size > 0) static_cast<SDLPal::MainPage^>(Page)->SetFile(files->GetAt(0));
-		break;
-	}
+		auto contdata = dynamic_cast<IContinuationActivatedEventArgs^>(args)->ContinuationData;
+		auto page = dynamic_cast<SDLPal::MainPage^>(contdata->Lookup("Page"));
+		auto target = contdata->HasKey("Target") ? dynamic_cast<Windows::UI::Xaml::Controls::TextBox^>(contdata->Lookup("Target")) : nullptr;
+		switch (args->Kind)
+		{
+		case ActivationKind::PickFolderContinuation:
+			page->SetPath(dynamic_cast<IFolderPickerContinuationEventArgs^>(args)->Folder);
+			break;
+		case ActivationKind::PickFileContinuation:
+			if (dynamic_cast<IFileOpenPickerContinuationEventArgs^>(args)->Files->Size > 0)
+			{
+				page->SetFile(target, dynamic_cast<IFileOpenPickerContinuationEventArgs^>(args)->Files->GetAt(0));
+			}
+			break;
+		case ActivationKind::PickSaveFileContinuation:
+			if (dynamic_cast<IFileSavePickerContinuationEventArgs^>(args)->File)
+			{
+				page->SetFile(target, dynamic_cast<IFileSavePickerContinuationEventArgs^>(args)->File);
+			}
+			break;
+		}
 	}
 	Application::OnActivated(args);
 }

+ 0 - 1
winrt/SDLPal.WindowsPhone/App.xaml.h

@@ -17,7 +17,6 @@ namespace SDLPal
 	internal:
 		App();
 
-		property Windows::UI::Xaml::Controls::Page^ Page;
 		property bool LastCrashed;
 
 	protected:

+ 2 - 3
winrt/pal_config.h

@@ -25,10 +25,9 @@
 
 #pragma once
 
-#define PAL_PREFIX            UTIL_BasePath()
-#define PAL_SAVE_PREFIX       UTIL_SavePath()
+#define PAL_PREFIX            ""
+#define PAL_SAVE_PREFIX       ""
 #define PAL_CONFIG_PREFIX     UTIL_ConfigPath()
-#define PAL_SCREENSHOT_PREFIX UTIL_ScreenShotPath()
 #define PAL_HAS_TOUCH         1
 #define PAL_AUDIO_DEFAULT_BUFFER_SIZE   4096
 #define PAL_DEFAULT_WINDOW_WIDTH   320