Browse Source

Configuration: WinRT GUI update & bug fix

1. Update WinRT GUI to reflect most-recent configuration changes

2. Always use the WinRT's FutureAccessList object rather then the
configuration file to store pathes to prevent potential access denies.
LouYihua 7 years ago
parent
commit
46eff377c0

+ 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>

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

@@ -26,24 +26,67 @@ using namespace Windows::UI::Xaml::Navigation;
 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^>{ ".bdf" }));
+	m_controls->Insert(btnBrowseFontFile->Name, ref new ButtonAttribute(tbFontFile, ref new Platform::Array<Platform::String^>{ ".msg" }));
+	m_controls->Insert(btnBrowseLogFile->Name, ref new ButtonAttribute(tbLogFile, ref new Platform::Array<Platform::String^>{ ".log" }));
+	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 +95,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 +126,12 @@ void SDLPal::MainPage::LoadControlContents()
 
 void SDLPal::MainPage::SaveControlContents()
 {
-	std::wstring path;
+	// 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; }
 
-	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;
-
-	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 +140,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 +159,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 +175,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 +217,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;
 }
 

+ 14 - 4
winrt/SDLPal.WindowsPhone/App.xaml.cpp

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

+ 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